dicomweb_server/
lib.rs

1use std::collections::{BTreeMap, HashMap};
2// use std::fmt::Write;
3use std::io::Write;
4
5use async_std::path::Path;
6use async_trait::async_trait;
7use dicom::core::header::Header;
8use dicom::core::Tag;
9use dicom::object::open_file;
10use dicom::object::{DefaultDicomObject, InMemDicomObject};
11use dicomweb_util::encode::{encode_dicom_to_json, DICOMJsonObject};
12use log::info;
13use serde_json::{json, Value};
14use std::io::{self, BufWriter, Cursor};
15use tide::log::debug;
16use tide::Response;
17
18// http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_10.6.html#table_10.6.1-5
19pub const STUDYTAGS: [Tag; 9] = [
20    Tag(0x0008, 0x0020),
21    Tag(0x0008, 0x0030),
22    Tag(0x0008, 0x0050),
23    Tag(0x0008, 0x0061),
24    Tag(0x0008, 0x0090),
25    Tag(0x0010, 0x0010),
26    Tag(0x0010, 0x0020),
27    Tag(0x0020, 0x000D),
28    Tag(0x0020, 0x0010),
29];
30
31pub const SERIESTAGS: [Tag; 6] = [
32    Tag(0x0008, 0x0060),
33    Tag(0x0020, 0x000E),
34    Tag(0x0020, 0x0011),
35    Tag(0x0040, 0x0244),
36    Tag(0x0040, 0x0245),
37    Tag(0x0040, 0x0275),
38    // Tag(0x0040, 0x0009),
39    // Tag(0x0040, 0x1001),
40];
41
42pub const INSTANCETAGS: [Tag; 3] = [
43    Tag(0x0008, 0x0016),
44    Tag(0x0008, 0x0018),
45    Tag(0x0020, 0x0013),
46];
47
48pub struct DICOMwebServer<T> {
49    app: tide::Server<T>,
50}
51
52impl<T> DICOMwebServer<T>
53where
54    T: DICOMServer + Clone + Send + Sync + 'static,
55{
56    pub fn with_dicom_server(server: T) -> Self {
57        let mut app = tide::with_state(server);
58
59        app.with(tide::log::LogMiddleware::new());
60        let qido = app.state().get_qido_prefix().to_string()
61            + if !app.state().get_qido_prefix().is_empty() {
62                "/"
63            } else {
64                ""
65            };
66        let wado = app.state().get_wado_prefix().to_string()
67            + if !app.state().get_wado_prefix().is_empty() {
68                "/"
69            } else {
70                ""
71            };
72
73        app.at(&("/".to_string() + &qido + "studies"))
74            .get(Self::search_studies);
75
76        app.at(&("/".to_string() + &qido + "studies/:study_instance_uid/series"))
77            .get(Self::search_series);
78
79        app.at(&("/".to_string()
80            + &qido
81            + "studies/:study_instance_uid/series/:series_instance_uid/instances"))
82            .get(Self::search_instances);
83
84        app.at(&("/".to_string()
85            + &wado
86            + "studies/:study_instance_uid/series/:series_instance_uid/instances/:sop_instance_uid"))
87            .get(Self::retrieve_instance);
88
89        DICOMwebServer { app }
90    }
91
92    async fn search_studies(req: tide::Request<T>) -> tide::Result {
93        let server = req.state();
94        let dicoms = server.search_studies().await;
95
96        let res: Vec<DICOMJsonObject> = dicoms.into_iter().map(encode_dicom_to_json).collect();
97
98        let mut res = Response::from(json!(res));
99        res.set_content_type("application/dicom+json");
100        Ok(res)
101    }
102
103    async fn search_series(mut req: tide::Request<T>) -> tide::Result {
104        let server = req.state();
105        let study_instance_uid = req.param("study_instance_uid")?;
106        let dicoms = server.search_series(study_instance_uid).await;
107
108        let res: Vec<DICOMJsonObject> = dicoms.into_iter().map(encode_dicom_to_json).collect();
109
110        let mut res = Response::from(json!(res));
111        res.set_content_type("application/dicom+json");
112        Ok(res)
113    }
114
115    async fn search_instances(mut req: tide::Request<T>) -> tide::Result {
116        let server = req.state();
117        let study_instance_uid = req.param("study_instance_uid")?;
118        let series_instance_uid = req.param("series_instance_uid")?;
119        let dicoms = server
120            .search_instances(study_instance_uid, series_instance_uid)
121            .await;
122
123        let res: Vec<DICOMJsonObject> = dicoms.into_iter().map(encode_dicom_to_json).collect();
124
125        let mut res = Response::from(json!(res));
126        res.set_content_type("application/dicom+json");
127        Ok(res)
128    }
129
130    async fn retrieve_instance(mut req: tide::Request<T>) -> tide::Result {
131        let server = req.state();
132        let study_instance_uid = req.param("study_instance_uid")?;
133        let series_instance_uid = req.param("series_instance_uid")?;
134        let sop_instance_uid = req.param("sop_instance_uid")?;
135        let dicom = server
136            .retrieve_instance(study_instance_uid, series_instance_uid, sop_instance_uid)
137            .await;
138
139        if let Some(obj) = dicom {
140            let mut res = Response::new(200);
141            let boundary = "ab69a3d5-542c-49e1-884b-8e135e104893";
142            res.set_content_type(
143                format!(
144                    "multipart/related; type=\"application/dicom\"; boundary={}",
145                    boundary
146                )
147                .as_str(),
148            );
149
150            let mut body_payload = Cursor::new(Vec::with_capacity(1024 * 1024));
151            obj.write_all(&mut body_payload).unwrap();
152
153            let mut body_header = Cursor::new(Vec::with_capacity(4 * 80));
154            write!(body_header, "--{}\r\n", boundary).unwrap();
155            write!(
156                body_header,
157                "Content-Type: multipart/related; type=\"application/dicom\"; boundary={}\r\n",
158                boundary
159            )
160            .unwrap();
161            write!(
162                body_header,
163                "Content-Length: {}\r\n",
164                body_payload.position()
165            )
166            .unwrap();
167            write!(body_header, "\r\n").unwrap();
168
169            write!(body_payload, "\r\n--{}--", boundary).unwrap();
170
171            let mut body = body_header.into_inner();
172            let payload_vec = body_payload.into_inner();
173            body.extend(payload_vec);
174
175            res.set_body(body);
176            Ok(res)
177        } else {
178            let mut res = Response::new(404);
179            Ok(res)
180        }
181    }
182
183    pub async fn listen(self, listener: &str) -> io::Result<()> {
184        self.app.listen(listener).await?;
185        Ok(())
186    }
187}
188
189#[async_trait]
190pub trait DICOMServer {
191    type State: DICOMServer;
192
193    fn get_qido_prefix(&self) -> &str;
194    fn get_wado_prefix(&self) -> &str;
195
196    async fn search_studies(&self) -> Vec<InMemDicomObject>;
197    async fn search_series(&self, study_instance_uid: &str) -> Vec<InMemDicomObject>;
198    async fn search_instances(
199        &self,
200        study_instance_uid: &str,
201        series_instance_uid: &str,
202    ) -> Vec<InMemDicomObject>;
203
204    async fn retrieve_instance(
205        &self,
206        study_instance_uid: &str,
207        series_instance_uid: &str,
208        sop_instance_uid: &str,
209    ) -> Option<DefaultDicomObject>;
210}
211
212#[cfg(test)]
213mod tests {
214    #[test]
215    fn it_works() {
216        assert_eq!(2 + 2, 4);
217    }
218}