Mercurial > hg > orthanc
comparison OrthancServer/OrthancRestApi2.cpp @ 227:209ca3f6db62
dicom-scu from rest
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 30 Nov 2012 10:57:34 +0100 |
parents | 8a26a8e85edf |
children |
comparison
equal
deleted
inserted
replaced
226:8a26a8e85edf | 227:209ca3f6db62 |
---|---|
30 **/ | 30 **/ |
31 | 31 |
32 | 32 |
33 #include "OrthancRestApi2.h" | 33 #include "OrthancRestApi2.h" |
34 | 34 |
35 #include "../Core/HttpServer/FilesystemHttpSender.h" | |
36 #include "../Core/Uuid.h" | |
37 #include "DicomProtocol/DicomUserConnection.h" | |
38 #include "FromDcmtkBridge.h" | |
35 #include "OrthancInitialization.h" | 39 #include "OrthancInitialization.h" |
36 #include "FromDcmtkBridge.h" | |
37 #include "../Core/Uuid.h" | |
38 #include "../Core/HttpServer/FilesystemHttpSender.h" | |
39 #include "ServerToolbox.h" | 40 #include "ServerToolbox.h" |
40 | 41 |
41 #include <dcmtk/dcmdata/dcistrmb.h> | 42 #include <dcmtk/dcmdata/dcistrmb.h> |
42 #include <dcmtk/dcmdata/dcfilefo.h> | 43 #include <dcmtk/dcmdata/dcfilefo.h> |
43 #include <boost/lexical_cast.hpp> | 44 #include <boost/lexical_cast.hpp> |
44 #include <glog/logging.h> | 45 #include <glog/logging.h> |
45 | 46 |
46 | 47 |
47 #define RETRIEVE_CONTEXT(call) \ | 48 #define RETRIEVE_CONTEXT(call) \ |
48 OrthancRestApi2& contextApi = \ | 49 OrthancRestApi2& contextApi = \ |
49 dynamic_cast<OrthancRestApi2&>(call.GetContext()); \ | 50 dynamic_cast<OrthancRestApi2&>(call.GetContext()); \ |
50 ServerContext& context = contextApi.GetContext() | 51 ServerContext& context = contextApi.GetContext() |
52 | |
53 #define RETRIEVE_MODALITIES(call) \ | |
54 const OrthancRestApi2::Modalities& modalities = \ | |
55 dynamic_cast<OrthancRestApi2&>(call.GetContext()).GetModalities(); | |
56 | |
51 | 57 |
52 | 58 |
53 namespace Orthanc | 59 namespace Orthanc |
54 { | 60 { |
61 // DICOM SCU ---------------------------------------------------------------- | |
62 | |
63 static void ConnectToModality(DicomUserConnection& connection, | |
64 const std::string& name) | |
65 { | |
66 std::string aet, address; | |
67 int port; | |
68 GetDicomModality(name, aet, address, port); | |
69 connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); | |
70 connection.SetDistantApplicationEntityTitle(aet); | |
71 connection.SetDistantHost(address); | |
72 connection.SetDistantPort(port); | |
73 connection.Open(); | |
74 } | |
75 | |
76 static bool MergeQueryAndTemplate(DicomMap& result, | |
77 const std::string& postData) | |
78 { | |
79 Json::Value query; | |
80 Json::Reader reader; | |
81 | |
82 if (!reader.parse(postData, query) || | |
83 query.type() != Json::objectValue) | |
84 { | |
85 return false; | |
86 } | |
87 | |
88 Json::Value::Members members = query.getMemberNames(); | |
89 for (size_t i = 0; i < members.size(); i++) | |
90 { | |
91 DicomTag t = FromDcmtkBridge::FindTag(members[i]); | |
92 result.SetValue(t, query[members[i]].asString()); | |
93 } | |
94 | |
95 return true; | |
96 } | |
97 | |
98 static void DicomFindPatient(RestApi::PostCall& call) | |
99 { | |
100 DicomMap m; | |
101 DicomMap::SetupFindPatientTemplate(m); | |
102 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
103 { | |
104 return; | |
105 } | |
106 | |
107 DicomUserConnection connection; | |
108 ConnectToModality(connection, call.GetUriComponent("id", "")); | |
109 | |
110 DicomFindAnswers answers; | |
111 connection.FindPatient(answers, m); | |
112 | |
113 Json::Value result; | |
114 answers.ToJson(result); | |
115 call.GetOutput().AnswerJson(result); | |
116 } | |
117 | |
118 static void DicomFindStudy(RestApi::PostCall& call) | |
119 { | |
120 DicomMap m; | |
121 DicomMap::SetupFindStudyTemplate(m); | |
122 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
123 { | |
124 return; | |
125 } | |
126 | |
127 if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && | |
128 m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) | |
129 { | |
130 return; | |
131 } | |
132 | |
133 DicomUserConnection connection; | |
134 ConnectToModality(connection, call.GetUriComponent("id", "")); | |
135 | |
136 DicomFindAnswers answers; | |
137 connection.FindStudy(answers, m); | |
138 | |
139 Json::Value result; | |
140 answers.ToJson(result); | |
141 call.GetOutput().AnswerJson(result); | |
142 } | |
143 | |
144 static void DicomFindSeries(RestApi::PostCall& call) | |
145 { | |
146 DicomMap m; | |
147 DicomMap::SetupFindSeriesTemplate(m); | |
148 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
149 { | |
150 return; | |
151 } | |
152 | |
153 if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && | |
154 m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || | |
155 m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) | |
156 { | |
157 return; | |
158 } | |
159 | |
160 DicomUserConnection connection; | |
161 ConnectToModality(connection, call.GetUriComponent("id", "")); | |
162 | |
163 DicomFindAnswers answers; | |
164 connection.FindSeries(answers, m); | |
165 | |
166 Json::Value result; | |
167 answers.ToJson(result); | |
168 call.GetOutput().AnswerJson(result); | |
169 } | |
170 | |
171 static void DicomFind(RestApi::PostCall& call) | |
172 { | |
173 DicomMap m; | |
174 DicomMap::SetupFindPatientTemplate(m); | |
175 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
176 { | |
177 return; | |
178 } | |
179 | |
180 DicomUserConnection connection; | |
181 ConnectToModality(connection, call.GetUriComponent("id", "")); | |
182 | |
183 DicomFindAnswers patients; | |
184 connection.FindPatient(patients, m); | |
185 | |
186 // Loop over the found patients | |
187 Json::Value result = Json::arrayValue; | |
188 for (size_t i = 0; i < patients.GetSize(); i++) | |
189 { | |
190 Json::Value patient(Json::objectValue); | |
191 FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i)); | |
192 | |
193 DicomMap::SetupFindStudyTemplate(m); | |
194 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
195 { | |
196 return; | |
197 } | |
198 m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); | |
199 | |
200 DicomFindAnswers studies; | |
201 connection.FindStudy(studies, m); | |
202 | |
203 patient["Studies"] = Json::arrayValue; | |
204 | |
205 // Loop over the found studies | |
206 for (size_t j = 0; j < studies.GetSize(); j++) | |
207 { | |
208 Json::Value study(Json::objectValue); | |
209 FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); | |
210 | |
211 DicomMap::SetupFindSeriesTemplate(m); | |
212 if (!MergeQueryAndTemplate(m, call.GetPostBody())) | |
213 { | |
214 return; | |
215 } | |
216 m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); | |
217 m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); | |
218 | |
219 DicomFindAnswers series; | |
220 connection.FindSeries(series, m); | |
221 | |
222 // Loop over the found series | |
223 study["Series"] = Json::arrayValue; | |
224 for (size_t k = 0; k < series.GetSize(); k++) | |
225 { | |
226 Json::Value series2(Json::objectValue); | |
227 FromDcmtkBridge::ToJson(series2, series.GetAnswer(k)); | |
228 study["Series"].append(series2); | |
229 } | |
230 | |
231 patient["Studies"].append(study); | |
232 } | |
233 | |
234 result.append(patient); | |
235 } | |
236 | |
237 call.GetOutput().AnswerJson(result); | |
238 } | |
239 | |
240 | |
241 static void DicomStore(RestApi::PostCall& call) | |
242 { | |
243 RETRIEVE_CONTEXT(call); | |
244 | |
245 DicomUserConnection connection; | |
246 ConnectToModality(connection, call.GetUriComponent("id", "")); | |
247 | |
248 Json::Value found; | |
249 if (context.GetIndex().LookupResource(found, call.GetPostBody(), ResourceType_Series)) | |
250 { | |
251 // The UUID corresponds to a series | |
252 for (Json::Value::ArrayIndex i = 0; i < found["Instances"].size(); i++) | |
253 { | |
254 std::string instanceId = found["Instances"][i].asString(); | |
255 std::string dicom; | |
256 context.ReadFile(dicom, instanceId, AttachedFileType_Dicom); | |
257 connection.Store(dicom); | |
258 } | |
259 | |
260 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
261 } | |
262 else if (context.GetIndex().LookupResource(found, call.GetPostBody(), ResourceType_Instance)) | |
263 { | |
264 // The UUID corresponds to an instance | |
265 std::string instanceId = call.GetPostBody(); | |
266 std::string dicom; | |
267 context.ReadFile(dicom, instanceId, AttachedFileType_Dicom); | |
268 connection.Store(dicom); | |
269 | |
270 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
271 } | |
272 else | |
273 { | |
274 // The POST body is not a known resource, assume that it | |
275 // contains a raw DICOM instance | |
276 connection.Store(call.GetPostBody()); | |
277 call.GetOutput().AnswerBuffer("{}", "application/json"); | |
278 } | |
279 } | |
280 | |
281 | |
282 | |
55 // System information ------------------------------------------------------- | 283 // System information ------------------------------------------------------- |
56 | 284 |
57 static void ServeRoot(RestApi::GetCall& call) | 285 static void ServeRoot(RestApi::GetCall& call) |
58 { | 286 { |
59 call.GetOutput().Redirect("app/explorer.html"); | 287 call.GetOutput().Redirect("app/explorer.html"); |
64 RETRIEVE_CONTEXT(call); | 292 RETRIEVE_CONTEXT(call); |
65 | 293 |
66 Json::Value result = Json::objectValue; | 294 Json::Value result = Json::objectValue; |
67 result["Version"] = ORTHANC_VERSION; | 295 result["Version"] = ORTHANC_VERSION; |
68 result["Name"] = GetGlobalStringParameter("Name", ""); | 296 result["Name"] = GetGlobalStringParameter("Name", ""); |
69 result["TotalCompressedSize"] = boost::lexical_cast<std::string>(context.GetIndex().GetTotalCompressedSize()); | 297 result["TotalCompressedSize"] = boost::lexical_cast<std::string> |
70 result["TotalUncompressedSize"] = boost::lexical_cast<std::string>(context.GetIndex().GetTotalUncompressedSize()); | 298 (context.GetIndex().GetTotalCompressedSize()); |
299 result["TotalUncompressedSize"] = boost::lexical_cast<std::string> | |
300 (context.GetIndex().GetTotalUncompressedSize()); | |
301 | |
71 call.GetOutput().AnswerJson(result); | 302 call.GetOutput().AnswerJson(result); |
72 } | 303 } |
73 | 304 |
74 | 305 |
75 // List all the patients, studies, series or instances ---------------------- | 306 // List all the patients, studies, series or instances ---------------------- |
211 template <enum ImageExtractionMode mode> | 442 template <enum ImageExtractionMode mode> |
212 static void GetImage(RestApi::GetCall& call) | 443 static void GetImage(RestApi::GetCall& call) |
213 { | 444 { |
214 RETRIEVE_CONTEXT(call); | 445 RETRIEVE_CONTEXT(call); |
215 | 446 |
216 CompressionType compressionType; | |
217 std::string fileUuid; | |
218 std::string publicId = call.GetUriComponent("id", ""); | |
219 std::string frameId = call.GetUriComponent("frame", "0"); | 447 std::string frameId = call.GetUriComponent("frame", "0"); |
220 | 448 |
221 unsigned int frame; | 449 unsigned int frame; |
222 try | 450 try |
223 { | 451 { |
226 catch (boost::bad_lexical_cast) | 454 catch (boost::bad_lexical_cast) |
227 { | 455 { |
228 return; | 456 return; |
229 } | 457 } |
230 | 458 |
231 if (context.GetIndex().GetFile(fileUuid, compressionType, publicId, AttachedFileType_Dicom)) | 459 std::string publicId = call.GetUriComponent("id", ""); |
232 { | 460 std::string dicomContent, png; |
233 assert(compressionType == CompressionType_None); | 461 context.ReadFile(dicomContent, publicId, AttachedFileType_Dicom); |
234 | 462 |
235 std::string dicomContent, png; | 463 try |
236 context.GetFileStorage().ReadFile(dicomContent, fileUuid); | 464 { |
237 | 465 FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); |
238 try | 466 call.GetOutput().AnswerBuffer(png, "image/png"); |
239 { | 467 } |
240 FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); | 468 catch (OrthancException& e) |
241 call.GetOutput().AnswerBuffer(png, "image/png"); | 469 { |
242 } | 470 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) |
243 catch (OrthancException& e) | 471 { |
244 { | 472 // The frame number is out of the range for this DICOM |
245 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) | 473 // instance, the resource is not existent |
474 } | |
475 else | |
476 { | |
477 std::string root = ""; | |
478 for (size_t i = 1; i < call.GetFullUri().size(); i++) | |
246 { | 479 { |
247 // The frame number is out of the range for this DICOM | 480 root += "../"; |
248 // instance, the resource is not existent | |
249 } | 481 } |
250 else | 482 |
251 { | 483 call.GetOutput().Redirect(root + "app/images/unsupported.png"); |
252 std::string root = ""; | |
253 for (size_t i = 1; i < call.GetFullUri().size(); i++) | |
254 { | |
255 root += "../"; | |
256 } | |
257 | |
258 call.GetOutput().Redirect(root + "app/images/unsupported.png"); | |
259 } | |
260 } | 484 } |
261 } | 485 } |
262 } | 486 } |
263 | 487 |
264 | 488 |
316 } | 540 } |
317 | 541 |
318 | 542 |
319 // DICOM bridge ------------------------------------------------------------- | 543 // DICOM bridge ------------------------------------------------------------- |
320 | 544 |
545 static bool IsExistingModality(const OrthancRestApi2::Modalities& modalities, | |
546 const std::string& id) | |
547 { | |
548 return modalities.find(id) != modalities.end(); | |
549 } | |
550 | |
321 static void ListModalities(RestApi::GetCall& call) | 551 static void ListModalities(RestApi::GetCall& call) |
322 { | 552 { |
323 const OrthancRestApi2::Modalities& m = | 553 RETRIEVE_MODALITIES(call); |
324 dynamic_cast<OrthancRestApi2&>(call.GetContext()).GetModalities(); | |
325 | 554 |
326 Json::Value result = Json::arrayValue; | 555 Json::Value result = Json::arrayValue; |
327 | |
328 for (OrthancRestApi2::Modalities::const_iterator | 556 for (OrthancRestApi2::Modalities::const_iterator |
329 it = m.begin(); it != m.end(); it++) | 557 it = modalities.begin(); it != modalities.end(); it++) |
330 { | 558 { |
331 result.append(*it); | 559 result.append(*it); |
332 } | 560 } |
333 | 561 |
334 call.GetOutput().AnswerJson(result); | 562 call.GetOutput().AnswerJson(result); |
563 } | |
564 | |
565 | |
566 static void ListModalityOperations(RestApi::GetCall& call) | |
567 { | |
568 RETRIEVE_MODALITIES(call); | |
569 | |
570 std::string id = call.GetUriComponent("id", ""); | |
571 if (IsExistingModality(modalities, id)) | |
572 { | |
573 Json::Value result = Json::arrayValue; | |
574 result.append("find-patient"); | |
575 result.append("find-study"); | |
576 result.append("find-series"); | |
577 result.append("find"); | |
578 result.append("store"); | |
579 call.GetOutput().AnswerJson(result); | |
580 } | |
335 } | 581 } |
336 | 582 |
337 | 583 |
338 | 584 |
339 // Registration of the various REST handlers -------------------------------- | 585 // Registration of the various REST handlers -------------------------------- |
344 GetListOfDicomModalities(modalities_); | 590 GetListOfDicomModalities(modalities_); |
345 | 591 |
346 Register("/", ServeRoot); | 592 Register("/", ServeRoot); |
347 Register("/system", GetSystemInformation); | 593 Register("/system", GetSystemInformation); |
348 Register("/changes", GetChanges); | 594 Register("/changes", GetChanges); |
349 Register("/modalities", ListModalities); | |
350 | 595 |
351 Register("/instances", UploadDicomFile); | 596 Register("/instances", UploadDicomFile); |
352 Register("/instances", ListResources<ResourceType_Instance>); | 597 Register("/instances", ListResources<ResourceType_Instance>); |
353 Register("/patients", ListResources<ResourceType_Patient>); | 598 Register("/patients", ListResources<ResourceType_Patient>); |
354 Register("/series", ListResources<ResourceType_Series>); | 599 Register("/series", ListResources<ResourceType_Series>); |
373 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | 618 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); |
374 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); | 619 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); |
375 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); | 620 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); |
376 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | 621 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); |
377 | 622 |
623 Register("/modalities", ListModalities); | |
624 Register("/modalities/{id}", ListModalityOperations); | |
625 Register("/modalities/{id}/find-patient", DicomFindPatient); | |
626 Register("/modalities/{id}/find-study", DicomFindStudy); | |
627 Register("/modalities/{id}/find-series", DicomFindSeries); | |
628 Register("/modalities/{id}/find", DicomFind); | |
629 Register("/modalities/{id}/store", DicomStore); | |
630 | |
378 // TODO : "content" | 631 // TODO : "content" |
379 } | 632 } |
380 } | 633 } |