1use std::collections::{BTreeMap, HashMap};
2use 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
18pub 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 ];
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}