comparison OrthancServer/OrthancRestApi.cpp @ 229:f2349bab5fe7

rename
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 30 Nov 2012 11:00:40 +0100
parents OrthancServer/OrthancRestApi2.cpp@209ca3f6db62
children ae2367145b49
comparison
equal deleted inserted replaced
228:1af3bc092db8 229:f2349bab5fe7
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
4 * Belgium
5 *
6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * In addition, as a special exception, the copyright holders of this
12 * program give permission to link the code of its release with the
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it
14 * that use the same license as the "OpenSSL" library), and distribute
15 * the linked executables. You must obey the GNU General Public License
16 * in all respects for all of the code used other than "OpenSSL". If you
17 * modify file(s) with this exception, you may extend this exception to
18 * your version of the file(s), but you are not obligated to do so. If
19 * you do not wish to do so, delete this exception statement from your
20 * version. If you delete this exception statement from all source files
21 * in the program, then also delete it here.
22 *
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 **/
31
32
33 #include "OrthancRestApi2.h"
34
35 #include "../Core/HttpServer/FilesystemHttpSender.h"
36 #include "../Core/Uuid.h"
37 #include "DicomProtocol/DicomUserConnection.h"
38 #include "FromDcmtkBridge.h"
39 #include "OrthancInitialization.h"
40 #include "ServerToolbox.h"
41
42 #include <dcmtk/dcmdata/dcistrmb.h>
43 #include <dcmtk/dcmdata/dcfilefo.h>
44 #include <boost/lexical_cast.hpp>
45 #include <glog/logging.h>
46
47
48 #define RETRIEVE_CONTEXT(call) \
49 OrthancRestApi2& contextApi = \
50 dynamic_cast<OrthancRestApi2&>(call.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
57
58
59 namespace Orthanc
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
283 // System information -------------------------------------------------------
284
285 static void ServeRoot(RestApi::GetCall& call)
286 {
287 call.GetOutput().Redirect("app/explorer.html");
288 }
289
290 static void GetSystemInformation(RestApi::GetCall& call)
291 {
292 RETRIEVE_CONTEXT(call);
293
294 Json::Value result = Json::objectValue;
295 result["Version"] = ORTHANC_VERSION;
296 result["Name"] = GetGlobalStringParameter("Name", "");
297 result["TotalCompressedSize"] = boost::lexical_cast<std::string>
298 (context.GetIndex().GetTotalCompressedSize());
299 result["TotalUncompressedSize"] = boost::lexical_cast<std::string>
300 (context.GetIndex().GetTotalUncompressedSize());
301
302 call.GetOutput().AnswerJson(result);
303 }
304
305
306 // List all the patients, studies, series or instances ----------------------
307
308 template <enum ResourceType resourceType>
309 static void ListResources(RestApi::GetCall& call)
310 {
311 RETRIEVE_CONTEXT(call);
312
313 Json::Value result;
314 context.GetIndex().GetAllUuids(result, resourceType);
315 call.GetOutput().AnswerJson(result);
316 }
317
318 template <enum ResourceType resourceType>
319 static void GetSingleResource(RestApi::GetCall& call)
320 {
321 RETRIEVE_CONTEXT(call);
322
323 Json::Value result;
324 if (context.GetIndex().LookupResource(result, call.GetUriComponent("id", ""), resourceType))
325 {
326 call.GetOutput().AnswerJson(result);
327 }
328 }
329
330 template <enum ResourceType resourceType>
331 static void DeleteSingleResource(RestApi::DeleteCall& call)
332 {
333 RETRIEVE_CONTEXT(call);
334
335 Json::Value result;
336 if (context.GetIndex().DeleteResource(result, call.GetUriComponent("id", ""), resourceType))
337 {
338 call.GetOutput().AnswerJson(result);
339 }
340 }
341
342
343 // Changes API --------------------------------------------------------------
344
345 static void GetChanges(RestApi::GetCall& call)
346 {
347 RETRIEVE_CONTEXT(call);
348
349 static const unsigned int MAX_RESULTS = 100;
350 ServerIndex& index = context.GetIndex();
351
352 //std::string filter = GetArgument(getArguments, "filter", "");
353 int64_t since;
354 unsigned int limit;
355 try
356 {
357 since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0"));
358 limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0"));
359 }
360 catch (boost::bad_lexical_cast)
361 {
362 return;
363 }
364
365 if (limit == 0 || limit > MAX_RESULTS)
366 {
367 limit = MAX_RESULTS;
368 }
369
370 Json::Value result;
371 if (index.GetChanges(result, since, limit))
372 {
373 call.GetOutput().AnswerJson(result);
374 }
375 }
376
377
378
379 // Get information about a single instance ----------------------------------
380
381 static void GetInstanceFile(RestApi::GetCall& call)
382 {
383 RETRIEVE_CONTEXT(call);
384
385 std::string publicId = call.GetUriComponent("id", "");
386 context.AnswerFile(call.GetOutput(), publicId, AttachedFileType_Dicom);
387 }
388
389
390 template <bool simplify>
391 static void GetInstanceTags(RestApi::GetCall& call)
392 {
393 RETRIEVE_CONTEXT(call);
394
395 std::string publicId = call.GetUriComponent("id", "");
396
397 Json::Value full;
398 context.ReadJson(full, publicId);
399
400 if (simplify)
401 {
402 Json::Value simplified;
403 SimplifyTags(simplified, full);
404 call.GetOutput().AnswerJson(simplified);
405 }
406 else
407 {
408 call.GetOutput().AnswerJson(full);
409 }
410 }
411
412
413 static void ListFrames(RestApi::GetCall& call)
414 {
415 RETRIEVE_CONTEXT(call);
416
417 Json::Value instance;
418 if (context.GetIndex().LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance))
419 {
420 unsigned int numberOfFrames = 1;
421
422 try
423 {
424 Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"];
425 numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString());
426 }
427 catch (...)
428 {
429 }
430
431 Json::Value result = Json::arrayValue;
432 for (unsigned int i = 0; i < numberOfFrames; i++)
433 {
434 result.append(i);
435 }
436
437 call.GetOutput().AnswerJson(result);
438 }
439 }
440
441
442 template <enum ImageExtractionMode mode>
443 static void GetImage(RestApi::GetCall& call)
444 {
445 RETRIEVE_CONTEXT(call);
446
447 std::string frameId = call.GetUriComponent("frame", "0");
448
449 unsigned int frame;
450 try
451 {
452 frame = boost::lexical_cast<unsigned int>(frameId);
453 }
454 catch (boost::bad_lexical_cast)
455 {
456 return;
457 }
458
459 std::string publicId = call.GetUriComponent("id", "");
460 std::string dicomContent, png;
461 context.ReadFile(dicomContent, publicId, AttachedFileType_Dicom);
462
463 try
464 {
465 FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode);
466 call.GetOutput().AnswerBuffer(png, "image/png");
467 }
468 catch (OrthancException& e)
469 {
470 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange)
471 {
472 // The frame number is out of the range for this DICOM
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++)
479 {
480 root += "../";
481 }
482
483 call.GetOutput().Redirect(root + "app/images/unsupported.png");
484 }
485 }
486 }
487
488
489 // Upload of DICOM files through HTTP ---------------------------------------
490
491 static void UploadDicomFile(RestApi::PostCall& call)
492 {
493 RETRIEVE_CONTEXT(call);
494
495 const std::string& postData = call.GetPostBody();
496
497 LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP";
498
499 // Prepare an input stream for the memory buffer
500 DcmInputBufferStream is;
501 if (postData.size() > 0)
502 {
503 is.setBuffer(&postData[0], postData.size());
504 }
505 is.setEos();
506
507 DcmFileFormat dicomFile;
508 if (!dicomFile.read(is).good())
509 {
510 call.GetOutput().SignalError(Orthanc_HttpStatus_415_UnsupportedMediaType);
511 return;
512 }
513
514 DicomMap dicomSummary;
515 FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset());
516
517 DicomInstanceHasher hasher(dicomSummary);
518
519 Json::Value dicomJson;
520 FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset());
521
522 StoreStatus status = StoreStatus_Failure;
523 if (postData.size() > 0)
524 {
525 status = context.Store
526 (reinterpret_cast<const char*>(&postData[0]),
527 postData.size(), dicomSummary, dicomJson, "");
528 }
529
530 Json::Value result = Json::objectValue;
531
532 if (status != StoreStatus_Failure)
533 {
534 result["ID"] = hasher.HashInstance();
535 result["Path"] = GetBasePath(ResourceType_Instance, hasher.HashInstance());
536 }
537
538 result["Status"] = ToString(status);
539 call.GetOutput().AnswerJson(result);
540 }
541
542
543 // DICOM bridge -------------------------------------------------------------
544
545 static bool IsExistingModality(const OrthancRestApi2::Modalities& modalities,
546 const std::string& id)
547 {
548 return modalities.find(id) != modalities.end();
549 }
550
551 static void ListModalities(RestApi::GetCall& call)
552 {
553 RETRIEVE_MODALITIES(call);
554
555 Json::Value result = Json::arrayValue;
556 for (OrthancRestApi2::Modalities::const_iterator
557 it = modalities.begin(); it != modalities.end(); it++)
558 {
559 result.append(*it);
560 }
561
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 }
581 }
582
583
584
585 // Registration of the various REST handlers --------------------------------
586
587 OrthancRestApi2::OrthancRestApi2(ServerContext& context) :
588 context_(context)
589 {
590 GetListOfDicomModalities(modalities_);
591
592 Register("/", ServeRoot);
593 Register("/system", GetSystemInformation);
594 Register("/changes", GetChanges);
595
596 Register("/instances", UploadDicomFile);
597 Register("/instances", ListResources<ResourceType_Instance>);
598 Register("/patients", ListResources<ResourceType_Patient>);
599 Register("/series", ListResources<ResourceType_Series>);
600 Register("/studies", ListResources<ResourceType_Study>);
601
602 Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>);
603 Register("/instances/{id}", GetSingleResource<ResourceType_Instance>);
604 Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>);
605 Register("/patients/{id}", GetSingleResource<ResourceType_Patient>);
606 Register("/series/{id}", DeleteSingleResource<ResourceType_Series>);
607 Register("/series/{id}", GetSingleResource<ResourceType_Series>);
608 Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>);
609 Register("/studies/{id}", GetSingleResource<ResourceType_Study>);
610
611 Register("/instances/{id}/file", GetInstanceFile);
612 Register("/instances/{id}/tags", GetInstanceTags<false>);
613 Register("/instances/{id}/simplified-tags", GetInstanceTags<true>);
614 Register("/instances/{id}/frames", ListFrames);
615
616 Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>);
617 Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
618 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
619 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>);
620 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
621 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
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
631 // TODO : "content"
632 }
633 }