comparison OrthancServer/OrthancRestApi.cpp @ 58:6da7fc87efaa orthanc-renaming

renaming cont
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sun, 16 Sep 2012 09:24:13 +0200
parents OrthancServer/PalanthirRestApi.cpp@4bc019d2f969
children a70bb32802ae
comparison
equal deleted inserted replaced
57:4bc019d2f969 58:6da7fc87efaa
1 /**
2 * Palanthir - 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 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 **/
19
20
21 #include "PalanthirRestApi.h"
22
23 #include "PalanthirInitialization.h"
24 #include "FromDcmtkBridge.h"
25 #include "../Core/Uuid.h"
26
27 #include <dcmtk/dcmdata/dcistrmb.h>
28 #include <dcmtk/dcmdata/dcfilefo.h>
29 #include <boost/lexical_cast.hpp>
30
31 namespace Palanthir
32 {
33 static void SendJson(HttpOutput& output,
34 const Json::Value& value)
35 {
36 Json::StyledWriter writer;
37 std::string s = writer.write(value);
38 output.AnswerBufferWithContentType(s, "application/json");
39 }
40
41
42 static void SimplifyTagsRecursion(Json::Value& target,
43 const Json::Value& source)
44 {
45 assert(source.isObject());
46
47 target = Json::objectValue;
48 Json::Value::Members members = source.getMemberNames();
49
50 for (size_t i = 0; i < members.size(); i++)
51 {
52 const Json::Value& v = source[members[i]];
53 const std::string& name = v["Name"].asString();
54 const std::string& type = v["Type"].asString();
55
56 if (type == "String")
57 {
58 target[name] = v["Value"].asString();
59 }
60 else if (type == "TooLong" ||
61 type == "Null")
62 {
63 target[name] = Json::nullValue;
64 }
65 else if (type == "Sequence")
66 {
67 const Json::Value& array = v["Value"];
68 assert(array.isArray());
69
70 Json::Value children = Json::arrayValue;
71 for (size_t i = 0; i < array.size(); i++)
72 {
73 Json::Value c;
74 SimplifyTagsRecursion(c, array[i]);
75 children.append(c);
76 }
77
78 target[name] = children;
79 }
80 else
81 {
82 assert(0);
83 }
84 }
85 }
86
87
88 static void SimplifyTags(Json::Value& target,
89 const FileStorage& storage,
90 const std::string& fileUuid)
91 {
92 std::string s;
93 storage.ReadFile(s, fileUuid);
94
95 Json::Value source;
96 Json::Reader reader;
97 if (!reader.parse(s, source))
98 {
99 throw PalanthirException("Corrupted JSON file");
100 }
101
102 SimplifyTagsRecursion(target, source);
103 }
104
105
106 bool PalanthirRestApi::Store(Json::Value& result,
107 const std::string& postData)
108 {
109 // Prepare an input stream for the memory buffer
110 DcmInputBufferStream is;
111 if (postData.size() > 0)
112 {
113 is.setBuffer(&postData[0], postData.size());
114 }
115 is.setEos();
116
117 //printf("[%d]\n", postData.size());
118
119 DcmFileFormat dicomFile;
120 if (dicomFile.read(is).good())
121 {
122 DicomMap dicomSummary;
123 FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset());
124
125 Json::Value dicomJson;
126 FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset());
127
128 std::string instanceUuid;
129 StoreStatus status = StoreStatus_Failure;
130 if (postData.size() > 0)
131 {
132 status = index_.Store
133 (instanceUuid, storage_, reinterpret_cast<const char*>(&postData[0]),
134 postData.size(), dicomSummary, dicomJson, "");
135 }
136
137 switch (status)
138 {
139 case StoreStatus_Success:
140 result["ID"] = instanceUuid;
141 result["Path"] = "/instances/" + instanceUuid;
142 result["Status"] = "Success";
143 return true;
144
145 case StoreStatus_AlreadyStored:
146 result["ID"] = instanceUuid;
147 result["Path"] = "/instances/" + instanceUuid;
148 result["Status"] = "AlreadyStored";
149 return true;
150
151 default:
152 return false;
153 }
154 }
155
156 return false;
157 }
158
159 void PalanthirRestApi::ConnectToModality(DicomUserConnection& c,
160 const std::string& name)
161 {
162 std::string aet, address;
163 int port;
164 GetDicomModality(name, aet, address, port);
165 c.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "PALANTHIR"));
166 c.SetDistantApplicationEntityTitle(aet);
167 c.SetDistantHost(address);
168 c.SetDistantPort(port);
169 c.Open();
170 }
171
172 bool PalanthirRestApi::MergeQueryAndTemplate(DicomMap& result,
173 const std::string& postData)
174 {
175 Json::Value query;
176 Json::Reader reader;
177
178 if (!reader.parse(postData, query) ||
179 query.type() != Json::objectValue)
180 {
181 return false;
182 }
183
184 Json::Value::Members members = query.getMemberNames();
185 for (size_t i = 0; i < members.size(); i++)
186 {
187 DicomTag t = FromDcmtkBridge::FindTag(members[i]);
188 result.SetValue(t, query[members[i]].asString());
189 }
190
191 return true;
192 }
193
194 bool PalanthirRestApi::DicomFindPatient(Json::Value& result,
195 DicomUserConnection& c,
196 const std::string& postData)
197 {
198 DicomMap m;
199 DicomMap::SetupFindPatientTemplate(m);
200 if (!MergeQueryAndTemplate(m, postData))
201 {
202 return false;
203 }
204
205 DicomFindAnswers answers;
206 c.FindPatient(answers, m);
207 answers.ToJson(result);
208 return true;
209 }
210
211 bool PalanthirRestApi::DicomFindStudy(Json::Value& result,
212 DicomUserConnection& c,
213 const std::string& postData)
214 {
215 DicomMap m;
216 DicomMap::SetupFindStudyTemplate(m);
217 if (!MergeQueryAndTemplate(m, postData))
218 {
219 return false;
220 }
221
222 if (m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 &&
223 m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2)
224 {
225 return false;
226 }
227
228 DicomFindAnswers answers;
229 c.FindStudy(answers, m);
230 answers.ToJson(result);
231 return true;
232 }
233
234 bool PalanthirRestApi::DicomFindSeries(Json::Value& result,
235 DicomUserConnection& c,
236 const std::string& postData)
237 {
238 DicomMap m;
239 DicomMap::SetupFindSeriesTemplate(m);
240 if (!MergeQueryAndTemplate(m, postData))
241 {
242 return false;
243 }
244
245 if ((m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 &&
246 m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2) ||
247 m.GetValue(DicomTag::STUDY_UID).AsString().size() <= 2)
248 {
249 return false;
250 }
251
252 DicomFindAnswers answers;
253 c.FindSeries(answers, m);
254 answers.ToJson(result);
255 return true;
256 }
257
258 bool PalanthirRestApi::DicomFind(Json::Value& result,
259 DicomUserConnection& c,
260 const std::string& postData)
261 {
262 DicomMap m;
263 DicomMap::SetupFindPatientTemplate(m);
264 if (!MergeQueryAndTemplate(m, postData))
265 {
266 return false;
267 }
268
269 DicomFindAnswers patients;
270 c.FindPatient(patients, m);
271
272 // Loop over the found patients
273 result = Json::arrayValue;
274 for (size_t i = 0; i < patients.GetSize(); i++)
275 {
276 Json::Value patient(Json::objectValue);
277 FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i));
278
279 DicomMap::SetupFindStudyTemplate(m);
280 if (!MergeQueryAndTemplate(m, postData))
281 {
282 return false;
283 }
284 m.CopyTagIfExists(patients.GetAnswer(i), DicomTag::PATIENT_ID);
285
286 DicomFindAnswers studies;
287 c.FindStudy(studies, m);
288
289 patient["Studies"] = Json::arrayValue;
290
291 // Loop over the found studies
292 for (size_t j = 0; j < studies.GetSize(); j++)
293 {
294 Json::Value study(Json::objectValue);
295 FromDcmtkBridge::ToJson(study, studies.GetAnswer(j));
296
297 DicomMap::SetupFindSeriesTemplate(m);
298 if (!MergeQueryAndTemplate(m, postData))
299 {
300 return false;
301 }
302 m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::PATIENT_ID);
303 m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::STUDY_UID);
304
305 DicomFindAnswers series;
306 c.FindSeries(series, m);
307
308 // Loop over the found series
309 study["Series"] = Json::arrayValue;
310 for (size_t k = 0; k < series.GetSize(); k++)
311 {
312 Json::Value series2(Json::objectValue);
313 FromDcmtkBridge::ToJson(series2, series.GetAnswer(k));
314 study["Series"].append(series2);
315 }
316
317 patient["Studies"].append(study);
318 }
319
320 result.append(patient);
321 }
322
323 return true;
324 }
325
326
327
328 bool PalanthirRestApi::DicomStore(Json::Value& result,
329 DicomUserConnection& c,
330 const std::string& postData)
331 {
332 Json::Value found(Json::objectValue);
333
334 if (!Toolbox::IsUuid(postData))
335 {
336 // This is not a UUID, assume this is a DICOM instance
337 c.Store(postData);
338 }
339 else if (index_.GetSeries(found, postData))
340 {
341 // The UUID corresponds to a series
342 for (size_t i = 0; i < found["Instances"].size(); i++)
343 {
344 std::string uuid = found["Instances"][i].asString();
345 Json::Value instance(Json::objectValue);
346 if (index_.GetInstance(instance, uuid))
347 {
348 std::string content;
349 storage_.ReadFile(content, instance["FileUuid"].asString());
350 c.Store(content);
351 }
352 else
353 {
354 return false;
355 }
356 }
357 }
358 else if (index_.GetInstance(found, postData))
359 {
360 // The UUID corresponds to an instance
361 std::string content;
362 storage_.ReadFile(content, found["FileUuid"].asString());
363 c.Store(content);
364 }
365 else
366 {
367 return false;
368 }
369
370 return true;
371 }
372
373
374 PalanthirRestApi::PalanthirRestApi(ServerIndex& index,
375 const std::string& path) :
376 index_(index),
377 storage_(path)
378 {
379 GetListOfDicomModalities(modalities_);
380 }
381
382
383 void PalanthirRestApi::Handle(
384 HttpOutput& output,
385 const std::string& method,
386 const UriComponents& uri,
387 const Arguments& headers,
388 const Arguments& arguments,
389 const std::string& postData)
390 {
391 if (uri.size() == 0)
392 {
393 if (method == "GET")
394 {
395 output.Redirect("/app/explorer.html");
396 }
397 else
398 {
399 output.SendMethodNotAllowedError("GET");
400 }
401
402 return;
403 }
404
405 bool existingResource = false;
406 Json::Value result(Json::objectValue);
407
408
409 // List all the instances ---------------------------------------------------
410
411 if (uri.size() == 1 && uri[0] == "instances")
412 {
413 if (method == "GET")
414 {
415 result = Json::Value(Json::arrayValue);
416 index_.GetAllUuids(result, "Instances");
417 existingResource = true;
418 }
419 else if (method == "POST")
420 {
421 // Add a new instance to the storage
422 if (Store(result, postData))
423 {
424 SendJson(output, result);
425 return;
426 }
427 else
428 {
429 output.SendHeader(Palanthir_HttpStatus_415_UnsupportedMediaType);
430 return;
431 }
432 }
433 else
434 {
435 output.SendMethodNotAllowedError("GET,POST");
436 return;
437 }
438 }
439
440
441 // List all the patients, studies or series ---------------------------------
442
443 if (uri.size() == 1 &&
444 (uri[0] == "series" ||
445 uri[0] == "studies" ||
446 uri[0] == "patients"))
447 {
448 if (method == "GET")
449 {
450 result = Json::Value(Json::arrayValue);
451
452 if (uri[0] == "instances")
453 index_.GetAllUuids(result, "Instances");
454 else if (uri[0] == "series")
455 index_.GetAllUuids(result, "Series");
456 else if (uri[0] == "studies")
457 index_.GetAllUuids(result, "Studies");
458 else if (uri[0] == "patients")
459 index_.GetAllUuids(result, "Patients");
460
461 existingResource = true;
462 }
463 else
464 {
465 output.SendMethodNotAllowedError("GET");
466 return;
467 }
468 }
469
470
471 // Information about a single object ----------------------------------------
472
473 else if (uri.size() == 2 &&
474 (uri[0] == "instances" ||
475 uri[0] == "series" ||
476 uri[0] == "studies" ||
477 uri[0] == "patients"))
478 {
479 if (method == "GET")
480 {
481 if (uri[0] == "patients")
482 {
483 existingResource = index_.GetPatient(result, uri[1]);
484 }
485 else if (uri[0] == "studies")
486 {
487 existingResource = index_.GetStudy(result, uri[1]);
488 }
489 else if (uri[0] == "series")
490 {
491 existingResource = index_.GetSeries(result, uri[1]);
492 }
493 else if (uri[0] == "instances")
494 {
495 existingResource = index_.GetInstance(result, uri[1]);
496 }
497 }
498 else if (method == "DELETE")
499 {
500 if (uri[0] == "patients")
501 {
502 existingResource = index_.DeletePatient(result, uri[1]);
503 }
504 else if (uri[0] == "studies")
505 {
506 existingResource = index_.DeleteStudy(result, uri[1]);
507 }
508 else if (uri[0] == "series")
509 {
510 existingResource = index_.DeleteSeries(result, uri[1]);
511 }
512 else if (uri[0] == "instances")
513 {
514 existingResource = index_.DeleteInstance(result, uri[1]);
515 }
516
517 if (existingResource)
518 {
519 result["Status"] = "Success";
520 }
521 }
522 else
523 {
524 output.SendMethodNotAllowedError("GET,DELETE");
525 return;
526 }
527 }
528
529
530 // Get the DICOM or the JSON file of one instance ---------------------------
531
532 else if (uri.size() == 3 &&
533 uri[0] == "instances" &&
534 (uri[2] == "file" ||
535 uri[2] == "tags" ||
536 uri[2] == "simplified-tags"))
537 {
538 std::string fileUuid, contentType;
539 if (uri[2] == "file")
540 {
541 existingResource = index_.GetDicomFile(fileUuid, uri[1]);
542 contentType = "application/dicom";
543 }
544 else if (uri[2] == "tags" ||
545 uri[2] == "simplified-tags")
546 {
547 existingResource = index_.GetJsonFile(fileUuid, uri[1]);
548 contentType = "application/json";
549 }
550
551 if (existingResource)
552 {
553 if (uri[2] == "simplified-tags")
554 {
555 Json::Value v;
556 SimplifyTags(v, storage_, fileUuid);
557 SendJson(output, v);
558 return;
559 }
560 else
561 {
562 output.AnswerFile(storage_, fileUuid, contentType);
563 return;
564 }
565 }
566 }
567
568
569 else if (uri.size() == 3 &&
570 uri[0] == "instances" &&
571 uri[2] == "frames")
572 {
573 Json::Value instance(Json::objectValue);
574 existingResource = index_.GetInstance(instance, uri[1]);
575
576 if (existingResource)
577 {
578 result = Json::arrayValue;
579
580 unsigned int numberOfFrames = 1;
581 try
582 {
583 Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"];
584 numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString());
585 }
586 catch (boost::bad_lexical_cast)
587 {
588 }
589
590 for (unsigned int i = 0; i < numberOfFrames; i++)
591 {
592 result.append(i);
593 }
594 }
595 }
596
597
598 else if (uri[0] == "instances" &&
599 ((uri.size() == 3 &&
600 (uri[2] == "preview" ||
601 uri[2] == "image-uint8" ||
602 uri[2] == "image-uint16")) ||
603 (uri.size() == 5 &&
604 uri[2] == "frames" &&
605 (uri[4] == "preview" ||
606 uri[4] == "image-uint8" ||
607 uri[4] == "image-uint16"))))
608 {
609 std::string uuid;
610 existingResource = index_.GetDicomFile(uuid, uri[1]);
611
612 std::string action = uri[2];
613
614 unsigned int frame = 0;
615 if (existingResource &&
616 uri.size() == 5)
617 {
618 // Access to multi-frame image
619 action = uri[4];
620 try
621 {
622 frame = boost::lexical_cast<unsigned int>(uri[3]);
623 }
624 catch (boost::bad_lexical_cast)
625 {
626 existingResource = false;
627 }
628 }
629
630 if (existingResource)
631 {
632 std::string dicomContent, png;
633 storage_.ReadFile(dicomContent, uuid);
634 try
635 {
636 if (action == "preview")
637 {
638 FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, ImageExtractionMode_Preview);
639 }
640 else if (action == "image-uint8")
641 {
642 FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, ImageExtractionMode_UInt8);
643 }
644 else if (action == "image-uint16")
645 {
646 FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, ImageExtractionMode_UInt16);
647 }
648 else
649 {
650 throw PalanthirException(ErrorCode_InternalError);
651 }
652
653 output.AnswerBufferWithContentType(png, "image/png");
654 return;
655 }
656 catch (PalanthirException&)
657 {
658 output.Redirect("/app/images/Unsupported.png");
659 return;
660 }
661 }
662 }
663
664
665
666 // Changes API --------------------------------------------------------------
667
668 if (uri.size() == 1 && uri[0] == "changes")
669 {
670 if (method == "GET")
671 {
672 const static unsigned int MAX_RESULTS = 100;
673
674 std::string filter = GetArgument(arguments, "filter", "");
675 int64_t since;
676 unsigned int limit;
677 try
678 {
679 since = boost::lexical_cast<int64_t>(GetArgument(arguments, "since", "0"));
680 limit = boost::lexical_cast<unsigned int>(GetArgument(arguments, "limit", "0"));
681 }
682 catch (boost::bad_lexical_cast)
683 {
684 output.SendHeader(Palanthir_HttpStatus_400_BadRequest);
685 return;
686 }
687
688 if (limit == 0 || limit > MAX_RESULTS)
689 {
690 limit = MAX_RESULTS;
691 }
692
693 if (!index_.GetChanges(result, since, filter, limit))
694 {
695 output.SendHeader(Palanthir_HttpStatus_400_BadRequest);
696 return;
697 }
698
699 existingResource = true;
700 }
701 else
702 {
703 output.SendMethodNotAllowedError("GET");
704 return;
705 }
706 }
707
708
709 // DICOM bridge -------------------------------------------------------------
710
711 if (uri.size() == 1 &&
712 uri[0] == "modalities")
713 {
714 if (method == "GET")
715 {
716 result = Json::Value(Json::arrayValue);
717 existingResource = true;
718
719 for (Modalities::const_iterator it = modalities_.begin();
720 it != modalities_.end(); it++)
721 {
722 result.append(*it);
723 }
724 }
725 else
726 {
727 output.SendMethodNotAllowedError("GET");
728 return;
729 }
730 }
731
732 if ((uri.size() == 2 ||
733 uri.size() == 3) &&
734 uri[0] == "modalities")
735 {
736 if (modalities_.find(uri[1]) == modalities_.end())
737 {
738 // Unknown modality
739 }
740 else if (uri.size() == 2)
741 {
742 if (method != "GET")
743 {
744 output.SendMethodNotAllowedError("POST");
745 return;
746 }
747 else
748 {
749 existingResource = true;
750 result = Json::arrayValue;
751 result.append("find-patient");
752 result.append("find-study");
753 result.append("find-series");
754 result.append("find");
755 result.append("store");
756 }
757 }
758 else if (uri.size() == 3)
759 {
760 if (uri[2] != "find-patient" &&
761 uri[2] != "find-study" &&
762 uri[2] != "find-series" &&
763 uri[2] != "find" &&
764 uri[2] != "store")
765 {
766 // Unknown request
767 }
768 else if (method != "POST")
769 {
770 output.SendMethodNotAllowedError("POST");
771 return;
772 }
773 else
774 {
775 DicomUserConnection connection;
776 ConnectToModality(connection, uri[1]);
777 existingResource = true;
778
779 if ((uri[2] == "find-patient" && !DicomFindPatient(result, connection, postData)) ||
780 (uri[2] == "find-study" && !DicomFindStudy(result, connection, postData)) ||
781 (uri[2] == "find-series" && !DicomFindSeries(result, connection, postData)) ||
782 (uri[2] == "find" && !DicomFind(result, connection, postData)) ||
783 (uri[2] == "store" && !DicomStore(result, connection, postData)))
784 {
785 output.SendHeader(Palanthir_HttpStatus_400_BadRequest);
786 return;
787 }
788 }
789 }
790 }
791
792
793 if (existingResource)
794 {
795 SendJson(output, result);
796 }
797 else
798 {
799 output.SendHeader(Palanthir_HttpStatus_404_NotFound);
800 }
801 }
802 }