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 }