comparison PalanthirServer/PalantirRestApi.cpp @ 45:33d67e1ab173

r
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 05 Sep 2012 13:24:59 +0200
parents PalantirServer/PalantirRestApi.cpp@9be852ad33d2
children
comparison
equal deleted inserted replaced
43:9be852ad33d2 45:33d67e1ab173
1 /**
2 * Palantir - 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 "PalantirRestApi.h"
22
23 #include "PalantirInitialization.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 Palantir
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 PalantirException("Corrupted JSON file");
100 }
101
102 SimplifyTagsRecursion(target, source);
103 }
104
105
106 bool PalantirRestApi::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 PalantirRestApi::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", "PALANTIR"));
166 c.SetDistantApplicationEntityTitle(aet);
167 c.SetDistantHost(address);
168 c.SetDistantPort(port);
169 c.Open();
170 }
171
172 bool PalantirRestApi::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 PalantirRestApi::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 PalantirRestApi::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 PalantirRestApi::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 PalantirRestApi::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 PalantirRestApi::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 PalantirRestApi::PalantirRestApi(ServerIndex& index,
375 const std::string& path) :
376 index_(index),
377 storage_(path)
378 {
379 GetListOfDicomModalities(modalities_);
380 }
381
382
383 void PalantirRestApi::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(Palantir_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] == "preview" ||
572 uri[2] == "image-uint8" ||
573 uri[2] == "image-uint16"))
574 {
575 std::string uuid;
576 existingResource = index_.GetDicomFile(uuid, uri[1]);
577
578 if (existingResource)
579 {
580 std::string dicomContent, png;
581 storage_.ReadFile(dicomContent, uuid);
582 try
583 {
584 if (uri[2] == "preview")
585 {
586 FromDcmtkBridge::ExtractPngImage(png, dicomContent, ImageExtractionMode_Preview);
587 }
588 else if (uri[2] == "image-uint8")
589 {
590 FromDcmtkBridge::ExtractPngImage(png, dicomContent, ImageExtractionMode_UInt8);
591 }
592 else if (uri[2] == "image-uint16")
593 {
594 FromDcmtkBridge::ExtractPngImage(png, dicomContent, ImageExtractionMode_UInt16);
595 }
596 else
597 {
598 throw PalantirException(ErrorCode_InternalError);
599 }
600
601 output.AnswerBufferWithContentType(png, "image/png");
602 return;
603 }
604 catch (PalantirException&)
605 {
606 output.Redirect("/app/images/Unsupported.png");
607 return;
608 }
609 }
610 }
611
612
613
614 // Changes API --------------------------------------------------------------
615
616 if (uri.size() == 1 && uri[0] == "changes")
617 {
618 if (method == "GET")
619 {
620 const static unsigned int MAX_RESULTS = 100;
621
622 std::string filter = GetArgument(arguments, "filter", "");
623 int64_t since;
624 unsigned int limit;
625 try
626 {
627 since = boost::lexical_cast<int64_t>(GetArgument(arguments, "since", "0"));
628 limit = boost::lexical_cast<unsigned int>(GetArgument(arguments, "limit", "0"));
629 }
630 catch (boost::bad_lexical_cast)
631 {
632 output.SendHeader(Palantir_HttpStatus_400_BadRequest);
633 return;
634 }
635
636 if (limit == 0 || limit > MAX_RESULTS)
637 {
638 limit = MAX_RESULTS;
639 }
640
641 if (!index_.GetChanges(result, since, filter, limit))
642 {
643 output.SendHeader(Palantir_HttpStatus_400_BadRequest);
644 return;
645 }
646
647 existingResource = true;
648 }
649 else
650 {
651 output.SendMethodNotAllowedError("GET");
652 return;
653 }
654 }
655
656
657 // DICOM bridge -------------------------------------------------------------
658
659 if (uri.size() == 1 &&
660 uri[0] == "modalities")
661 {
662 if (method == "GET")
663 {
664 result = Json::Value(Json::arrayValue);
665 existingResource = true;
666
667 for (Modalities::const_iterator it = modalities_.begin();
668 it != modalities_.end(); it++)
669 {
670 result.append(*it);
671 }
672 }
673 else
674 {
675 output.SendMethodNotAllowedError("GET");
676 return;
677 }
678 }
679
680 if ((uri.size() == 2 ||
681 uri.size() == 3) &&
682 uri[0] == "modalities")
683 {
684 if (modalities_.find(uri[1]) == modalities_.end())
685 {
686 // Unknown modality
687 }
688 else if (uri.size() == 2)
689 {
690 if (method != "GET")
691 {
692 output.SendMethodNotAllowedError("POST");
693 return;
694 }
695 else
696 {
697 existingResource = true;
698 result = Json::arrayValue;
699 result.append("find-patient");
700 result.append("find-study");
701 result.append("find-series");
702 result.append("find");
703 result.append("store");
704 }
705 }
706 else if (uri.size() == 3)
707 {
708 if (uri[2] != "find-patient" &&
709 uri[2] != "find-study" &&
710 uri[2] != "find-series" &&
711 uri[2] != "find" &&
712 uri[2] != "store")
713 {
714 // Unknown request
715 }
716 else if (method != "POST")
717 {
718 output.SendMethodNotAllowedError("POST");
719 return;
720 }
721 else
722 {
723 DicomUserConnection connection;
724 ConnectToModality(connection, uri[1]);
725 existingResource = true;
726
727 if ((uri[2] == "find-patient" && !DicomFindPatient(result, connection, postData)) ||
728 (uri[2] == "find-study" && !DicomFindStudy(result, connection, postData)) ||
729 (uri[2] == "find-series" && !DicomFindSeries(result, connection, postData)) ||
730 (uri[2] == "find" && !DicomFind(result, connection, postData)) ||
731 (uri[2] == "store" && !DicomStore(result, connection, postData)))
732 {
733 output.SendHeader(Palantir_HttpStatus_400_BadRequest);
734 return;
735 }
736 }
737 }
738 }
739
740
741 if (existingResource)
742 {
743 SendJson(output, result);
744 }
745 else
746 {
747 output.SendHeader(Palantir_HttpStatus_404_NotFound);
748 }
749 }
750 }