Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | OrthancServer/OrthancRestApi/OrthancRestResources.cpp@d86bddb50972 |
children | 05b8fd21089c |
comparison
equal
deleted
inserted
replaced
4043:6c6239aec462 | 4044:d25f4c0fa160 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../PrecompiledHeadersServer.h" | |
35 #include "OrthancRestApi.h" | |
36 | |
37 #include "../../Core/Compression/GzipCompressor.h" | |
38 #include "../../Core/DicomFormat/DicomImageInformation.h" | |
39 #include "../../Core/DicomParsing/DicomWebJsonVisitor.h" | |
40 #include "../../Core/DicomParsing/FromDcmtkBridge.h" | |
41 #include "../../Core/DicomParsing/Internals/DicomImageDecoder.h" | |
42 #include "../../Core/HttpServer/HttpContentNegociation.h" | |
43 #include "../../Core/Images/Image.h" | |
44 #include "../../Core/Images/ImageProcessing.h" | |
45 #include "../../Core/Logging.h" | |
46 #include "../../Core/MultiThreading/Semaphore.h" | |
47 #include "../OrthancConfiguration.h" | |
48 #include "../Search/DatabaseLookup.h" | |
49 #include "../ServerContext.h" | |
50 #include "../ServerToolbox.h" | |
51 #include "../SliceOrdering.h" | |
52 | |
53 #include "../../Plugins/Engine/OrthancPlugins.h" | |
54 | |
55 // This "include" is mandatory for Release builds using Linux Standard Base | |
56 #include <boost/math/special_functions/round.hpp> | |
57 | |
58 | |
59 /** | |
60 * This semaphore is used to limit the number of concurrent HTTP | |
61 * requests on CPU-intensive routes of the REST API, in order to | |
62 * prevent exhaustion of resources (new in Orthanc 1.7.0). | |
63 **/ | |
64 static Orthanc::Semaphore throttlingSemaphore_(4); // TODO => PARAMETER? | |
65 | |
66 | |
67 namespace Orthanc | |
68 { | |
69 static void AnswerDicomAsJson(RestApiCall& call, | |
70 const Json::Value& dicom, | |
71 DicomToJsonFormat mode) | |
72 { | |
73 if (mode != DicomToJsonFormat_Full) | |
74 { | |
75 Json::Value simplified; | |
76 ServerToolbox::SimplifyTags(simplified, dicom, mode); | |
77 call.GetOutput().AnswerJson(simplified); | |
78 } | |
79 else | |
80 { | |
81 call.GetOutput().AnswerJson(dicom); | |
82 } | |
83 } | |
84 | |
85 | |
86 static DicomToJsonFormat GetDicomFormat(const RestApiGetCall& call) | |
87 { | |
88 if (call.HasArgument("simplify")) | |
89 { | |
90 return DicomToJsonFormat_Human; | |
91 } | |
92 else if (call.HasArgument("short")) | |
93 { | |
94 return DicomToJsonFormat_Short; | |
95 } | |
96 else | |
97 { | |
98 return DicomToJsonFormat_Full; | |
99 } | |
100 } | |
101 | |
102 | |
103 static void AnswerDicomAsJson(RestApiGetCall& call, | |
104 const Json::Value& dicom) | |
105 { | |
106 AnswerDicomAsJson(call, dicom, GetDicomFormat(call)); | |
107 } | |
108 | |
109 | |
110 static void ParseSetOfTags(std::set<DicomTag>& target, | |
111 const RestApiGetCall& call, | |
112 const std::string& argument) | |
113 { | |
114 target.clear(); | |
115 | |
116 if (call.HasArgument(argument)) | |
117 { | |
118 std::vector<std::string> tags; | |
119 Toolbox::TokenizeString(tags, call.GetArgument(argument, ""), ','); | |
120 | |
121 for (size_t i = 0; i < tags.size(); i++) | |
122 { | |
123 target.insert(FromDcmtkBridge::ParseTag(tags[i])); | |
124 } | |
125 } | |
126 } | |
127 | |
128 | |
129 // List all the patients, studies, series or instances ---------------------- | |
130 | |
131 static void AnswerListOfResources(RestApiOutput& output, | |
132 ServerIndex& index, | |
133 const std::list<std::string>& resources, | |
134 ResourceType level, | |
135 bool expand) | |
136 { | |
137 Json::Value answer = Json::arrayValue; | |
138 | |
139 for (std::list<std::string>::const_iterator | |
140 resource = resources.begin(); resource != resources.end(); ++resource) | |
141 { | |
142 if (expand) | |
143 { | |
144 Json::Value item; | |
145 if (index.LookupResource(item, *resource, level)) | |
146 { | |
147 answer.append(item); | |
148 } | |
149 } | |
150 else | |
151 { | |
152 answer.append(*resource); | |
153 } | |
154 } | |
155 | |
156 output.AnswerJson(answer); | |
157 } | |
158 | |
159 | |
160 template <enum ResourceType resourceType> | |
161 static void ListResources(RestApiGetCall& call) | |
162 { | |
163 ServerIndex& index = OrthancRestApi::GetIndex(call); | |
164 | |
165 std::list<std::string> result; | |
166 | |
167 if (call.HasArgument("limit") || | |
168 call.HasArgument("since")) | |
169 { | |
170 if (!call.HasArgument("limit")) | |
171 { | |
172 throw OrthancException(ErrorCode_BadRequest, | |
173 "Missing \"limit\" argument for GET request against: " + | |
174 call.FlattenUri()); | |
175 } | |
176 | |
177 if (!call.HasArgument("since")) | |
178 { | |
179 throw OrthancException(ErrorCode_BadRequest, | |
180 "Missing \"since\" argument for GET request against: " + | |
181 call.FlattenUri()); | |
182 } | |
183 | |
184 size_t since = boost::lexical_cast<size_t>(call.GetArgument("since", "")); | |
185 size_t limit = boost::lexical_cast<size_t>(call.GetArgument("limit", "")); | |
186 index.GetAllUuids(result, resourceType, since, limit); | |
187 } | |
188 else | |
189 { | |
190 index.GetAllUuids(result, resourceType); | |
191 } | |
192 | |
193 | |
194 AnswerListOfResources(call.GetOutput(), index, result, resourceType, call.HasArgument("expand")); | |
195 } | |
196 | |
197 template <enum ResourceType resourceType> | |
198 static void GetSingleResource(RestApiGetCall& call) | |
199 { | |
200 Json::Value result; | |
201 if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType)) | |
202 { | |
203 call.GetOutput().AnswerJson(result); | |
204 } | |
205 } | |
206 | |
207 template <enum ResourceType resourceType> | |
208 static void DeleteSingleResource(RestApiDeleteCall& call) | |
209 { | |
210 Json::Value result; | |
211 if (OrthancRestApi::GetContext(call).DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) | |
212 { | |
213 call.GetOutput().AnswerJson(result); | |
214 } | |
215 } | |
216 | |
217 | |
218 // Get information about a single patient ----------------------------------- | |
219 | |
220 static void IsProtectedPatient(RestApiGetCall& call) | |
221 { | |
222 std::string publicId = call.GetUriComponent("id", ""); | |
223 bool isProtected = OrthancRestApi::GetIndex(call).IsProtectedPatient(publicId); | |
224 call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", MimeType_PlainText); | |
225 } | |
226 | |
227 | |
228 static void SetPatientProtection(RestApiPutCall& call) | |
229 { | |
230 ServerContext& context = OrthancRestApi::GetContext(call); | |
231 | |
232 std::string publicId = call.GetUriComponent("id", ""); | |
233 | |
234 std::string body; | |
235 call.BodyToString(body); | |
236 body = Toolbox::StripSpaces(body); | |
237 | |
238 if (body == "0") | |
239 { | |
240 context.GetIndex().SetProtectedPatient(publicId, false); | |
241 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
242 } | |
243 else if (body == "1") | |
244 { | |
245 context.GetIndex().SetProtectedPatient(publicId, true); | |
246 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
247 } | |
248 else | |
249 { | |
250 // Bad request | |
251 } | |
252 } | |
253 | |
254 | |
255 // Get information about a single instance ---------------------------------- | |
256 | |
257 static void GetInstanceFile(RestApiGetCall& call) | |
258 { | |
259 ServerContext& context = OrthancRestApi::GetContext(call); | |
260 | |
261 std::string publicId = call.GetUriComponent("id", ""); | |
262 | |
263 IHttpHandler::Arguments::const_iterator accept = call.GetHttpHeaders().find("accept"); | |
264 if (accept != call.GetHttpHeaders().end()) | |
265 { | |
266 // New in Orthanc 1.5.4 | |
267 try | |
268 { | |
269 MimeType mime = StringToMimeType(accept->second.c_str()); | |
270 | |
271 if (mime == MimeType_DicomWebJson || | |
272 mime == MimeType_DicomWebXml) | |
273 { | |
274 DicomWebJsonVisitor visitor; | |
275 | |
276 { | |
277 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); | |
278 locker.GetDicom().Apply(visitor); | |
279 } | |
280 | |
281 if (mime == MimeType_DicomWebJson) | |
282 { | |
283 std::string s = visitor.GetResult().toStyledString(); | |
284 call.GetOutput().AnswerBuffer(s, MimeType_DicomWebJson); | |
285 } | |
286 else | |
287 { | |
288 std::string xml; | |
289 visitor.FormatXml(xml); | |
290 call.GetOutput().AnswerBuffer(xml, MimeType_DicomWebXml); | |
291 } | |
292 | |
293 return; | |
294 } | |
295 } | |
296 catch (OrthancException&) | |
297 { | |
298 } | |
299 } | |
300 | |
301 context.AnswerAttachment(call.GetOutput(), publicId, FileContentType_Dicom); | |
302 } | |
303 | |
304 | |
305 static void ExportInstanceFile(RestApiPostCall& call) | |
306 { | |
307 ServerContext& context = OrthancRestApi::GetContext(call); | |
308 | |
309 std::string publicId = call.GetUriComponent("id", ""); | |
310 | |
311 std::string dicom; | |
312 context.ReadDicom(dicom, publicId); | |
313 | |
314 std::string target; | |
315 call.BodyToString(target); | |
316 SystemToolbox::WriteFile(dicom, target); | |
317 | |
318 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
319 } | |
320 | |
321 | |
322 template <DicomToJsonFormat format> | |
323 static void GetInstanceTags(RestApiGetCall& call) | |
324 { | |
325 ServerContext& context = OrthancRestApi::GetContext(call); | |
326 | |
327 std::string publicId = call.GetUriComponent("id", ""); | |
328 | |
329 std::set<DicomTag> ignoreTagLength; | |
330 ParseSetOfTags(ignoreTagLength, call, "ignore-length"); | |
331 | |
332 if (format != DicomToJsonFormat_Full || | |
333 !ignoreTagLength.empty()) | |
334 { | |
335 Json::Value full; | |
336 context.ReadDicomAsJson(full, publicId, ignoreTagLength); | |
337 AnswerDicomAsJson(call, full, format); | |
338 } | |
339 else | |
340 { | |
341 // This path allows one to avoid the JSON decoding if no | |
342 // simplification is asked, and if no "ignore-length" argument | |
343 // is present | |
344 std::string full; | |
345 context.ReadDicomAsJson(full, publicId); | |
346 call.GetOutput().AnswerBuffer(full, MimeType_Json); | |
347 } | |
348 } | |
349 | |
350 | |
351 static void GetInstanceTagsBis(RestApiGetCall& call) | |
352 { | |
353 switch (GetDicomFormat(call)) | |
354 { | |
355 case DicomToJsonFormat_Human: | |
356 GetInstanceTags<DicomToJsonFormat_Human>(call); | |
357 break; | |
358 | |
359 case DicomToJsonFormat_Short: | |
360 GetInstanceTags<DicomToJsonFormat_Short>(call); | |
361 break; | |
362 | |
363 case DicomToJsonFormat_Full: | |
364 GetInstanceTags<DicomToJsonFormat_Full>(call); | |
365 break; | |
366 | |
367 default: | |
368 throw OrthancException(ErrorCode_InternalError); | |
369 } | |
370 } | |
371 | |
372 | |
373 static void ListFrames(RestApiGetCall& call) | |
374 { | |
375 std::string publicId = call.GetUriComponent("id", ""); | |
376 | |
377 unsigned int numberOfFrames; | |
378 | |
379 { | |
380 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); | |
381 numberOfFrames = locker.GetDicom().GetFramesCount(); | |
382 } | |
383 | |
384 Json::Value result = Json::arrayValue; | |
385 for (unsigned int i = 0; i < numberOfFrames; i++) | |
386 { | |
387 result.append(i); | |
388 } | |
389 | |
390 call.GetOutput().AnswerJson(result); | |
391 } | |
392 | |
393 | |
394 namespace | |
395 { | |
396 class ImageToEncode | |
397 { | |
398 private: | |
399 std::unique_ptr<ImageAccessor>& image_; | |
400 ImageExtractionMode mode_; | |
401 bool invert_; | |
402 MimeType format_; | |
403 std::string answer_; | |
404 | |
405 public: | |
406 ImageToEncode(std::unique_ptr<ImageAccessor>& image, | |
407 ImageExtractionMode mode, | |
408 bool invert) : | |
409 image_(image), | |
410 mode_(mode), | |
411 invert_(invert) | |
412 { | |
413 } | |
414 | |
415 void Answer(RestApiOutput& output) | |
416 { | |
417 output.AnswerBuffer(answer_, format_); | |
418 } | |
419 | |
420 void EncodeUsingPng() | |
421 { | |
422 format_ = MimeType_Png; | |
423 DicomImageDecoder::ExtractPngImage(answer_, image_, mode_, invert_); | |
424 } | |
425 | |
426 void EncodeUsingPam() | |
427 { | |
428 format_ = MimeType_Pam; | |
429 DicomImageDecoder::ExtractPamImage(answer_, image_, mode_, invert_); | |
430 } | |
431 | |
432 void EncodeUsingJpeg(uint8_t quality) | |
433 { | |
434 format_ = MimeType_Jpeg; | |
435 DicomImageDecoder::ExtractJpegImage(answer_, image_, mode_, invert_, quality); | |
436 } | |
437 }; | |
438 | |
439 class EncodePng : public HttpContentNegociation::IHandler | |
440 { | |
441 private: | |
442 ImageToEncode& image_; | |
443 | |
444 public: | |
445 EncodePng(ImageToEncode& image) : image_(image) | |
446 { | |
447 } | |
448 | |
449 virtual void Handle(const std::string& type, | |
450 const std::string& subtype) | |
451 { | |
452 assert(type == "image"); | |
453 assert(subtype == "png"); | |
454 image_.EncodeUsingPng(); | |
455 } | |
456 }; | |
457 | |
458 class EncodePam : public HttpContentNegociation::IHandler | |
459 { | |
460 private: | |
461 ImageToEncode& image_; | |
462 | |
463 public: | |
464 EncodePam(ImageToEncode& image) : image_(image) | |
465 { | |
466 } | |
467 | |
468 virtual void Handle(const std::string& type, | |
469 const std::string& subtype) | |
470 { | |
471 assert(type == "image"); | |
472 assert(subtype == "x-portable-arbitrarymap"); | |
473 image_.EncodeUsingPam(); | |
474 } | |
475 }; | |
476 | |
477 class EncodeJpeg : public HttpContentNegociation::IHandler | |
478 { | |
479 private: | |
480 ImageToEncode& image_; | |
481 unsigned int quality_; | |
482 | |
483 public: | |
484 EncodeJpeg(ImageToEncode& image, | |
485 const RestApiGetCall& call) : | |
486 image_(image) | |
487 { | |
488 std::string v = call.GetArgument("quality", "90" /* default JPEG quality */); | |
489 bool ok = false; | |
490 | |
491 try | |
492 { | |
493 quality_ = boost::lexical_cast<unsigned int>(v); | |
494 ok = (quality_ >= 1 && quality_ <= 100); | |
495 } | |
496 catch (boost::bad_lexical_cast&) | |
497 { | |
498 } | |
499 | |
500 if (!ok) | |
501 { | |
502 throw OrthancException( | |
503 ErrorCode_BadRequest, | |
504 "Bad quality for a JPEG encoding (must be a number between 0 and 100): " + v); | |
505 } | |
506 } | |
507 | |
508 virtual void Handle(const std::string& type, | |
509 const std::string& subtype) | |
510 { | |
511 assert(type == "image"); | |
512 assert(subtype == "jpeg"); | |
513 image_.EncodeUsingJpeg(quality_); | |
514 } | |
515 }; | |
516 } | |
517 | |
518 | |
519 namespace | |
520 { | |
521 class IDecodedFrameHandler : public boost::noncopyable | |
522 { | |
523 public: | |
524 virtual ~IDecodedFrameHandler() | |
525 { | |
526 } | |
527 | |
528 virtual void Handle(RestApiGetCall& call, | |
529 std::unique_ptr<ImageAccessor>& decoded, | |
530 const DicomMap& dicom) = 0; | |
531 | |
532 virtual bool RequiresDicomTags() const = 0; | |
533 | |
534 static void Apply(RestApiGetCall& call, | |
535 IDecodedFrameHandler& handler) | |
536 { | |
537 ServerContext& context = OrthancRestApi::GetContext(call); | |
538 | |
539 std::string frameId = call.GetUriComponent("frame", "0"); | |
540 | |
541 unsigned int frame; | |
542 try | |
543 { | |
544 frame = boost::lexical_cast<unsigned int>(frameId); | |
545 } | |
546 catch (boost::bad_lexical_cast&) | |
547 { | |
548 return; | |
549 } | |
550 | |
551 DicomMap dicom; | |
552 std::unique_ptr<ImageAccessor> decoded; | |
553 | |
554 try | |
555 { | |
556 std::string publicId = call.GetUriComponent("id", ""); | |
557 | |
558 decoded.reset(context.DecodeDicomFrame(publicId, frame)); | |
559 | |
560 if (decoded.get() == NULL) | |
561 { | |
562 throw OrthancException(ErrorCode_NotImplemented, | |
563 "Cannot decode DICOM instance with ID: " + publicId); | |
564 } | |
565 | |
566 if (handler.RequiresDicomTags()) | |
567 { | |
568 /** | |
569 * Retrieve a summary of the DICOM tags, which is | |
570 * necessary to deal with MONOCHROME1 photometric | |
571 * interpretation, and with windowing parameters. | |
572 **/ | |
573 ServerContext::DicomCacheLocker locker(context, publicId); | |
574 locker.GetDicom().ExtractDicomSummary(dicom); | |
575 } | |
576 } | |
577 catch (OrthancException& e) | |
578 { | |
579 if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || | |
580 e.GetErrorCode() == ErrorCode_UnknownResource) | |
581 { | |
582 // The frame number is out of the range for this DICOM | |
583 // instance, the resource is not existent | |
584 } | |
585 else | |
586 { | |
587 std::string root = ""; | |
588 for (size_t i = 1; i < call.GetFullUri().size(); i++) | |
589 { | |
590 root += "../"; | |
591 } | |
592 | |
593 call.GetOutput().Redirect(root + "app/images/unsupported.png"); | |
594 } | |
595 return; | |
596 } | |
597 | |
598 handler.Handle(call, decoded, dicom); | |
599 } | |
600 | |
601 | |
602 static void DefaultHandler(RestApiGetCall& call, | |
603 std::unique_ptr<ImageAccessor>& decoded, | |
604 ImageExtractionMode mode, | |
605 bool invert) | |
606 { | |
607 ImageToEncode image(decoded, mode, invert); | |
608 | |
609 HttpContentNegociation negociation; | |
610 EncodePng png(image); | |
611 negociation.Register(MIME_PNG, png); | |
612 | |
613 EncodeJpeg jpeg(image, call); | |
614 negociation.Register(MIME_JPEG, jpeg); | |
615 | |
616 EncodePam pam(image); | |
617 negociation.Register(MIME_PAM, pam); | |
618 | |
619 if (negociation.Apply(call.GetHttpHeaders())) | |
620 { | |
621 image.Answer(call.GetOutput()); | |
622 } | |
623 } | |
624 }; | |
625 | |
626 | |
627 class GetImageHandler : public IDecodedFrameHandler | |
628 { | |
629 private: | |
630 ImageExtractionMode mode_; | |
631 | |
632 public: | |
633 GetImageHandler(ImageExtractionMode mode) : | |
634 mode_(mode) | |
635 { | |
636 } | |
637 | |
638 virtual void Handle(RestApiGetCall& call, | |
639 std::unique_ptr<ImageAccessor>& decoded, | |
640 const DicomMap& dicom) ORTHANC_OVERRIDE | |
641 { | |
642 bool invert = false; | |
643 | |
644 if (mode_ == ImageExtractionMode_Preview) | |
645 { | |
646 DicomImageInformation info(dicom); | |
647 invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); | |
648 } | |
649 | |
650 DefaultHandler(call, decoded, mode_, invert); | |
651 } | |
652 | |
653 virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE | |
654 { | |
655 return mode_ == ImageExtractionMode_Preview; | |
656 } | |
657 }; | |
658 | |
659 | |
660 class RenderedFrameHandler : public IDecodedFrameHandler | |
661 { | |
662 private: | |
663 static void GetDicomParameters(bool& invert, | |
664 float& rescaleSlope, | |
665 float& rescaleIntercept, | |
666 float& windowWidth, | |
667 float& windowCenter, | |
668 const DicomMap& dicom) | |
669 { | |
670 DicomImageInformation info(dicom); | |
671 | |
672 invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); | |
673 | |
674 rescaleSlope = 1.0f; | |
675 rescaleIntercept = 0.0f; | |
676 | |
677 if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && | |
678 dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT)) | |
679 { | |
680 dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE); | |
681 dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); | |
682 } | |
683 | |
684 windowWidth = static_cast<float>(1 << info.GetBitsStored()) * rescaleSlope; | |
685 windowCenter = windowWidth / 2.0f + rescaleIntercept; | |
686 | |
687 if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && | |
688 dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH)) | |
689 { | |
690 dicom.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); | |
691 dicom.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); | |
692 } | |
693 } | |
694 | |
695 static void GetUserArguments(float& windowWidth /* inout */, | |
696 float& windowCenter /* inout */, | |
697 unsigned int& argWidth, | |
698 unsigned int& argHeight, | |
699 bool& smooth, | |
700 RestApiGetCall& call) | |
701 { | |
702 static const char* ARG_WINDOW_CENTER = "window-center"; | |
703 static const char* ARG_WINDOW_WIDTH = "window-width"; | |
704 static const char* ARG_WIDTH = "width"; | |
705 static const char* ARG_HEIGHT = "height"; | |
706 static const char* ARG_SMOOTH = "smooth"; | |
707 | |
708 if (call.HasArgument(ARG_WINDOW_WIDTH)) | |
709 { | |
710 try | |
711 { | |
712 windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, "")); | |
713 } | |
714 catch (boost::bad_lexical_cast&) | |
715 { | |
716 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
717 "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH)); | |
718 } | |
719 } | |
720 | |
721 if (call.HasArgument(ARG_WINDOW_CENTER)) | |
722 { | |
723 try | |
724 { | |
725 windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, "")); | |
726 } | |
727 catch (boost::bad_lexical_cast&) | |
728 { | |
729 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
730 "Bad value for argument: " + std::string(ARG_WINDOW_CENTER)); | |
731 } | |
732 } | |
733 | |
734 argWidth = 0; | |
735 argHeight = 0; | |
736 | |
737 if (call.HasArgument(ARG_WIDTH)) | |
738 { | |
739 try | |
740 { | |
741 int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_WIDTH, "")); | |
742 if (tmp < 0) | |
743 { | |
744 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
745 "Argument cannot be negative: " + std::string(ARG_WIDTH)); | |
746 } | |
747 else | |
748 { | |
749 argWidth = static_cast<unsigned int>(tmp); | |
750 } | |
751 } | |
752 catch (boost::bad_lexical_cast&) | |
753 { | |
754 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
755 "Bad value for argument: " + std::string(ARG_WIDTH)); | |
756 } | |
757 } | |
758 | |
759 if (call.HasArgument(ARG_HEIGHT)) | |
760 { | |
761 try | |
762 { | |
763 int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_HEIGHT, "")); | |
764 if (tmp < 0) | |
765 { | |
766 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
767 "Argument cannot be negative: " + std::string(ARG_HEIGHT)); | |
768 } | |
769 else | |
770 { | |
771 argHeight = static_cast<unsigned int>(tmp); | |
772 } | |
773 } | |
774 catch (boost::bad_lexical_cast&) | |
775 { | |
776 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
777 "Bad value for argument: " + std::string(ARG_HEIGHT)); | |
778 } | |
779 } | |
780 | |
781 smooth = false; | |
782 | |
783 if (call.HasArgument(ARG_SMOOTH)) | |
784 { | |
785 std::string value = call.GetArgument(ARG_SMOOTH, ""); | |
786 if (value == "0" || | |
787 value == "false") | |
788 { | |
789 smooth = false; | |
790 } | |
791 else if (value == "1" || | |
792 value == "true") | |
793 { | |
794 smooth = true; | |
795 } | |
796 else | |
797 { | |
798 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
799 "Argument must be Boolean: " + std::string(ARG_SMOOTH)); | |
800 } | |
801 } | |
802 } | |
803 | |
804 | |
805 public: | |
806 virtual void Handle(RestApiGetCall& call, | |
807 std::unique_ptr<ImageAccessor>& decoded, | |
808 const DicomMap& dicom) ORTHANC_OVERRIDE | |
809 { | |
810 bool invert; | |
811 float rescaleSlope, rescaleIntercept, windowWidth, windowCenter; | |
812 GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, dicom); | |
813 | |
814 unsigned int argWidth, argHeight; | |
815 bool smooth; | |
816 GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call); | |
817 | |
818 unsigned int targetWidth = decoded->GetWidth(); | |
819 unsigned int targetHeight = decoded->GetHeight(); | |
820 | |
821 if (decoded->GetWidth() != 0 && | |
822 decoded->GetHeight() != 0) | |
823 { | |
824 float ratio = 1; | |
825 | |
826 if (argWidth != 0 && | |
827 argHeight != 0) | |
828 { | |
829 float ratioX = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth()); | |
830 float ratioY = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight()); | |
831 ratio = std::min(ratioX, ratioY); | |
832 } | |
833 else if (argWidth != 0) | |
834 { | |
835 ratio = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth()); | |
836 } | |
837 else if (argHeight != 0) | |
838 { | |
839 ratio = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight()); | |
840 } | |
841 | |
842 targetWidth = boost::math::iround(ratio * static_cast<float>(decoded->GetWidth())); | |
843 targetHeight = boost::math::iround(ratio * static_cast<float>(decoded->GetHeight())); | |
844 } | |
845 | |
846 if (decoded->GetFormat() == PixelFormat_RGB24) | |
847 { | |
848 if (targetWidth == decoded->GetWidth() && | |
849 targetHeight == decoded->GetHeight()) | |
850 { | |
851 DefaultHandler(call, decoded, ImageExtractionMode_Preview, false); | |
852 } | |
853 else | |
854 { | |
855 std::unique_ptr<ImageAccessor> resized( | |
856 new Image(decoded->GetFormat(), targetWidth, targetHeight, false)); | |
857 | |
858 if (smooth && | |
859 (targetWidth < decoded->GetWidth() || | |
860 targetHeight < decoded->GetHeight())) | |
861 { | |
862 ImageProcessing::SmoothGaussian5x5(*decoded); | |
863 } | |
864 | |
865 ImageProcessing::Resize(*resized, *decoded); | |
866 DefaultHandler(call, resized, ImageExtractionMode_Preview, false); | |
867 } | |
868 } | |
869 else | |
870 { | |
871 // Grayscale image: (1) convert to Float32, (2) apply | |
872 // windowing to get a Grayscale8, (3) possibly resize | |
873 | |
874 Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false); | |
875 ImageProcessing::Convert(converted, *decoded); | |
876 | |
877 // Avoid divisions by zero | |
878 if (windowWidth <= 1.0f) | |
879 { | |
880 windowWidth = 1; | |
881 } | |
882 | |
883 if (std::abs(rescaleSlope) <= 0.1f) | |
884 { | |
885 rescaleSlope = 0.1f; | |
886 } | |
887 | |
888 const float scaling = 255.0f * rescaleSlope / windowWidth; | |
889 const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope; | |
890 | |
891 std::unique_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false)); | |
892 ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false); | |
893 | |
894 if (targetWidth == decoded->GetWidth() && | |
895 targetHeight == decoded->GetHeight()) | |
896 { | |
897 DefaultHandler(call, rescaled, ImageExtractionMode_UInt8, invert); | |
898 } | |
899 else | |
900 { | |
901 std::unique_ptr<ImageAccessor> resized( | |
902 new Image(PixelFormat_Grayscale8, targetWidth, targetHeight, false)); | |
903 | |
904 if (smooth && | |
905 (targetWidth < decoded->GetWidth() || | |
906 targetHeight < decoded->GetHeight())) | |
907 { | |
908 ImageProcessing::SmoothGaussian5x5(*rescaled); | |
909 } | |
910 | |
911 ImageProcessing::Resize(*resized, *rescaled); | |
912 DefaultHandler(call, resized, ImageExtractionMode_UInt8, invert); | |
913 } | |
914 } | |
915 } | |
916 | |
917 virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE | |
918 { | |
919 return true; | |
920 } | |
921 }; | |
922 } | |
923 | |
924 | |
925 template <enum ImageExtractionMode mode> | |
926 static void GetImage(RestApiGetCall& call) | |
927 { | |
928 Semaphore::Locker locker(throttlingSemaphore_); | |
929 | |
930 GetImageHandler handler(mode); | |
931 IDecodedFrameHandler::Apply(call, handler); | |
932 } | |
933 | |
934 | |
935 static void GetRenderedFrame(RestApiGetCall& call) | |
936 { | |
937 Semaphore::Locker locker(throttlingSemaphore_); | |
938 | |
939 RenderedFrameHandler handler; | |
940 IDecodedFrameHandler::Apply(call, handler); | |
941 } | |
942 | |
943 | |
944 static void GetMatlabImage(RestApiGetCall& call) | |
945 { | |
946 Semaphore::Locker locker(throttlingSemaphore_); | |
947 | |
948 ServerContext& context = OrthancRestApi::GetContext(call); | |
949 | |
950 std::string frameId = call.GetUriComponent("frame", "0"); | |
951 | |
952 unsigned int frame; | |
953 try | |
954 { | |
955 frame = boost::lexical_cast<unsigned int>(frameId); | |
956 } | |
957 catch (boost::bad_lexical_cast&) | |
958 { | |
959 return; | |
960 } | |
961 | |
962 std::string publicId = call.GetUriComponent("id", ""); | |
963 std::unique_ptr<ImageAccessor> decoded(context.DecodeDicomFrame(publicId, frame)); | |
964 | |
965 if (decoded.get() == NULL) | |
966 { | |
967 throw OrthancException(ErrorCode_NotImplemented, | |
968 "Cannot decode DICOM instance with ID: " + publicId); | |
969 } | |
970 else | |
971 { | |
972 std::string result; | |
973 decoded->ToMatlabString(result); | |
974 call.GetOutput().AnswerBuffer(result, MimeType_PlainText); | |
975 } | |
976 } | |
977 | |
978 | |
979 template <bool GzipCompression> | |
980 static void GetRawFrame(RestApiGetCall& call) | |
981 { | |
982 std::string frameId = call.GetUriComponent("frame", "0"); | |
983 | |
984 unsigned int frame; | |
985 try | |
986 { | |
987 frame = boost::lexical_cast<unsigned int>(frameId); | |
988 } | |
989 catch (boost::bad_lexical_cast&) | |
990 { | |
991 return; | |
992 } | |
993 | |
994 std::string publicId = call.GetUriComponent("id", ""); | |
995 std::string raw; | |
996 MimeType mime; | |
997 | |
998 { | |
999 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); | |
1000 locker.GetDicom().GetRawFrame(raw, mime, frame); | |
1001 } | |
1002 | |
1003 if (GzipCompression) | |
1004 { | |
1005 GzipCompressor gzip; | |
1006 std::string compressed; | |
1007 gzip.Compress(compressed, raw.empty() ? NULL : raw.c_str(), raw.size()); | |
1008 call.GetOutput().AnswerBuffer(compressed, MimeType_Gzip); | |
1009 } | |
1010 else | |
1011 { | |
1012 call.GetOutput().AnswerBuffer(raw, mime); | |
1013 } | |
1014 } | |
1015 | |
1016 | |
1017 static void GetResourceStatistics(RestApiGetCall& call) | |
1018 { | |
1019 static const uint64_t MEGA_BYTES = 1024 * 1024; | |
1020 | |
1021 std::string publicId = call.GetUriComponent("id", ""); | |
1022 | |
1023 ResourceType type; | |
1024 uint64_t diskSize, uncompressedSize, dicomDiskSize, dicomUncompressedSize; | |
1025 unsigned int countStudies, countSeries, countInstances; | |
1026 OrthancRestApi::GetIndex(call).GetResourceStatistics( | |
1027 type, diskSize, uncompressedSize, countStudies, countSeries, | |
1028 countInstances, dicomDiskSize, dicomUncompressedSize, publicId); | |
1029 | |
1030 Json::Value result = Json::objectValue; | |
1031 result["DiskSize"] = boost::lexical_cast<std::string>(diskSize); | |
1032 result["DiskSizeMB"] = static_cast<unsigned int>(diskSize / MEGA_BYTES); | |
1033 result["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize); | |
1034 result["UncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES); | |
1035 | |
1036 result["DicomDiskSize"] = boost::lexical_cast<std::string>(dicomDiskSize); | |
1037 result["DicomDiskSizeMB"] = static_cast<unsigned int>(dicomDiskSize / MEGA_BYTES); | |
1038 result["DicomUncompressedSize"] = boost::lexical_cast<std::string>(dicomUncompressedSize); | |
1039 result["DicomUncompressedSizeMB"] = static_cast<unsigned int>(dicomUncompressedSize / MEGA_BYTES); | |
1040 | |
1041 switch (type) | |
1042 { | |
1043 // Do NOT add "break" below this point! | |
1044 case ResourceType_Patient: | |
1045 result["CountStudies"] = countStudies; | |
1046 | |
1047 case ResourceType_Study: | |
1048 result["CountSeries"] = countSeries; | |
1049 | |
1050 case ResourceType_Series: | |
1051 result["CountInstances"] = countInstances; | |
1052 | |
1053 case ResourceType_Instance: | |
1054 default: | |
1055 break; | |
1056 } | |
1057 | |
1058 call.GetOutput().AnswerJson(result); | |
1059 } | |
1060 | |
1061 | |
1062 | |
1063 // Handling of metadata ----------------------------------------------------- | |
1064 | |
1065 static void CheckValidResourceType(RestApiCall& call) | |
1066 { | |
1067 std::string resourceType = call.GetUriComponent("resourceType", ""); | |
1068 StringToResourceType(resourceType.c_str()); | |
1069 } | |
1070 | |
1071 | |
1072 static void ListMetadata(RestApiGetCall& call) | |
1073 { | |
1074 CheckValidResourceType(call); | |
1075 | |
1076 std::string publicId = call.GetUriComponent("id", ""); | |
1077 std::map<MetadataType, std::string> metadata; | |
1078 | |
1079 OrthancRestApi::GetIndex(call).GetAllMetadata(metadata, publicId); | |
1080 | |
1081 Json::Value result; | |
1082 | |
1083 if (call.HasArgument("expand")) | |
1084 { | |
1085 result = Json::objectValue; | |
1086 | |
1087 for (std::map<MetadataType, std::string>::const_iterator | |
1088 it = metadata.begin(); it != metadata.end(); ++it) | |
1089 { | |
1090 std::string key = EnumerationToString(it->first); | |
1091 result[key] = it->second; | |
1092 } | |
1093 } | |
1094 else | |
1095 { | |
1096 result = Json::arrayValue; | |
1097 | |
1098 for (std::map<MetadataType, std::string>::const_iterator | |
1099 it = metadata.begin(); it != metadata.end(); ++it) | |
1100 { | |
1101 result.append(EnumerationToString(it->first)); | |
1102 } | |
1103 } | |
1104 | |
1105 call.GetOutput().AnswerJson(result); | |
1106 } | |
1107 | |
1108 | |
1109 static void GetMetadata(RestApiGetCall& call) | |
1110 { | |
1111 CheckValidResourceType(call); | |
1112 | |
1113 std::string publicId = call.GetUriComponent("id", ""); | |
1114 std::string name = call.GetUriComponent("name", ""); | |
1115 MetadataType metadata = StringToMetadata(name); | |
1116 | |
1117 std::string value; | |
1118 if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, metadata)) | |
1119 { | |
1120 call.GetOutput().AnswerBuffer(value, MimeType_PlainText); | |
1121 } | |
1122 } | |
1123 | |
1124 | |
1125 static void DeleteMetadata(RestApiDeleteCall& call) | |
1126 { | |
1127 CheckValidResourceType(call); | |
1128 | |
1129 std::string publicId = call.GetUriComponent("id", ""); | |
1130 std::string name = call.GetUriComponent("name", ""); | |
1131 MetadataType metadata = StringToMetadata(name); | |
1132 | |
1133 if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata | |
1134 { | |
1135 OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); | |
1136 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
1137 } | |
1138 else | |
1139 { | |
1140 call.GetOutput().SignalError(HttpStatus_403_Forbidden); | |
1141 } | |
1142 } | |
1143 | |
1144 | |
1145 static void SetMetadata(RestApiPutCall& call) | |
1146 { | |
1147 CheckValidResourceType(call); | |
1148 | |
1149 std::string publicId = call.GetUriComponent("id", ""); | |
1150 std::string name = call.GetUriComponent("name", ""); | |
1151 MetadataType metadata = StringToMetadata(name); | |
1152 | |
1153 std::string value; | |
1154 call.BodyToString(value); | |
1155 | |
1156 if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata | |
1157 { | |
1158 // It is forbidden to modify internal metadata | |
1159 OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); | |
1160 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
1161 } | |
1162 else | |
1163 { | |
1164 call.GetOutput().SignalError(HttpStatus_403_Forbidden); | |
1165 } | |
1166 } | |
1167 | |
1168 | |
1169 | |
1170 | |
1171 // Handling of attached files ----------------------------------------------- | |
1172 | |
1173 static void ListAttachments(RestApiGetCall& call) | |
1174 { | |
1175 std::string resourceType = call.GetUriComponent("resourceType", ""); | |
1176 std::string publicId = call.GetUriComponent("id", ""); | |
1177 std::list<FileContentType> attachments; | |
1178 OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str())); | |
1179 | |
1180 Json::Value result = Json::arrayValue; | |
1181 | |
1182 for (std::list<FileContentType>::const_iterator | |
1183 it = attachments.begin(); it != attachments.end(); ++it) | |
1184 { | |
1185 result.append(EnumerationToString(*it)); | |
1186 } | |
1187 | |
1188 call.GetOutput().AnswerJson(result); | |
1189 } | |
1190 | |
1191 | |
1192 static bool GetAttachmentInfo(FileInfo& info, RestApiCall& call) | |
1193 { | |
1194 CheckValidResourceType(call); | |
1195 | |
1196 std::string publicId = call.GetUriComponent("id", ""); | |
1197 std::string name = call.GetUriComponent("name", ""); | |
1198 FileContentType contentType = StringToContentType(name); | |
1199 | |
1200 return OrthancRestApi::GetIndex(call).LookupAttachment(info, publicId, contentType); | |
1201 } | |
1202 | |
1203 | |
1204 static void GetAttachmentOperations(RestApiGetCall& call) | |
1205 { | |
1206 FileInfo info; | |
1207 if (GetAttachmentInfo(info, call)) | |
1208 { | |
1209 Json::Value operations = Json::arrayValue; | |
1210 | |
1211 operations.append("compress"); | |
1212 operations.append("compressed-data"); | |
1213 | |
1214 if (info.GetCompressedMD5() != "") | |
1215 { | |
1216 operations.append("compressed-md5"); | |
1217 } | |
1218 | |
1219 operations.append("compressed-size"); | |
1220 operations.append("data"); | |
1221 operations.append("is-compressed"); | |
1222 | |
1223 if (info.GetUncompressedMD5() != "") | |
1224 { | |
1225 operations.append("md5"); | |
1226 } | |
1227 | |
1228 operations.append("size"); | |
1229 operations.append("uncompress"); | |
1230 | |
1231 if (info.GetCompressedMD5() != "" && | |
1232 info.GetUncompressedMD5() != "") | |
1233 { | |
1234 operations.append("verify-md5"); | |
1235 } | |
1236 | |
1237 call.GetOutput().AnswerJson(operations); | |
1238 } | |
1239 } | |
1240 | |
1241 | |
1242 template <int uncompress> | |
1243 static void GetAttachmentData(RestApiGetCall& call) | |
1244 { | |
1245 ServerContext& context = OrthancRestApi::GetContext(call); | |
1246 | |
1247 CheckValidResourceType(call); | |
1248 | |
1249 std::string publicId = call.GetUriComponent("id", ""); | |
1250 FileContentType type = StringToContentType(call.GetUriComponent("name", "")); | |
1251 | |
1252 if (uncompress) | |
1253 { | |
1254 context.AnswerAttachment(call.GetOutput(), publicId, type); | |
1255 } | |
1256 else | |
1257 { | |
1258 // Return the raw data (possibly compressed), as stored on the filesystem | |
1259 std::string content; | |
1260 context.ReadAttachment(content, publicId, type, false); | |
1261 call.GetOutput().AnswerBuffer(content, MimeType_Binary); | |
1262 } | |
1263 } | |
1264 | |
1265 | |
1266 static void GetAttachmentSize(RestApiGetCall& call) | |
1267 { | |
1268 FileInfo info; | |
1269 if (GetAttachmentInfo(info, call)) | |
1270 { | |
1271 call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedSize()), MimeType_PlainText); | |
1272 } | |
1273 } | |
1274 | |
1275 | |
1276 static void GetAttachmentCompressedSize(RestApiGetCall& call) | |
1277 { | |
1278 FileInfo info; | |
1279 if (GetAttachmentInfo(info, call)) | |
1280 { | |
1281 call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedSize()), MimeType_PlainText); | |
1282 } | |
1283 } | |
1284 | |
1285 | |
1286 static void GetAttachmentMD5(RestApiGetCall& call) | |
1287 { | |
1288 FileInfo info; | |
1289 if (GetAttachmentInfo(info, call) && | |
1290 info.GetUncompressedMD5() != "") | |
1291 { | |
1292 call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedMD5()), MimeType_PlainText); | |
1293 } | |
1294 } | |
1295 | |
1296 | |
1297 static void GetAttachmentCompressedMD5(RestApiGetCall& call) | |
1298 { | |
1299 FileInfo info; | |
1300 if (GetAttachmentInfo(info, call) && | |
1301 info.GetCompressedMD5() != "") | |
1302 { | |
1303 call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedMD5()), MimeType_PlainText); | |
1304 } | |
1305 } | |
1306 | |
1307 | |
1308 static void VerifyAttachment(RestApiPostCall& call) | |
1309 { | |
1310 ServerContext& context = OrthancRestApi::GetContext(call); | |
1311 CheckValidResourceType(call); | |
1312 | |
1313 std::string publicId = call.GetUriComponent("id", ""); | |
1314 std::string name = call.GetUriComponent("name", ""); | |
1315 | |
1316 FileInfo info; | |
1317 if (!GetAttachmentInfo(info, call) || | |
1318 info.GetCompressedMD5() == "" || | |
1319 info.GetUncompressedMD5() == "") | |
1320 { | |
1321 // Inexistent resource, or no MD5 available | |
1322 return; | |
1323 } | |
1324 | |
1325 bool ok = false; | |
1326 | |
1327 // First check whether the compressed data is correctly stored in the disk | |
1328 std::string data; | |
1329 context.ReadAttachment(data, publicId, StringToContentType(name), false); | |
1330 | |
1331 std::string actualMD5; | |
1332 Toolbox::ComputeMD5(actualMD5, data); | |
1333 | |
1334 if (actualMD5 == info.GetCompressedMD5()) | |
1335 { | |
1336 // The compressed data is OK. If a compression algorithm was | |
1337 // applied to it, now check the MD5 of the uncompressed data. | |
1338 if (info.GetCompressionType() == CompressionType_None) | |
1339 { | |
1340 ok = true; | |
1341 } | |
1342 else | |
1343 { | |
1344 context.ReadAttachment(data, publicId, StringToContentType(name), true); | |
1345 Toolbox::ComputeMD5(actualMD5, data); | |
1346 ok = (actualMD5 == info.GetUncompressedMD5()); | |
1347 } | |
1348 } | |
1349 | |
1350 if (ok) | |
1351 { | |
1352 LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has the right MD5"; | |
1353 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
1354 } | |
1355 else | |
1356 { | |
1357 LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has bad MD5!"; | |
1358 } | |
1359 } | |
1360 | |
1361 | |
1362 static void UploadAttachment(RestApiPutCall& call) | |
1363 { | |
1364 ServerContext& context = OrthancRestApi::GetContext(call); | |
1365 CheckValidResourceType(call); | |
1366 | |
1367 std::string publicId = call.GetUriComponent("id", ""); | |
1368 std::string name = call.GetUriComponent("name", ""); | |
1369 | |
1370 FileContentType contentType = StringToContentType(name); | |
1371 if (IsUserContentType(contentType) && // It is forbidden to modify internal attachments | |
1372 context.AddAttachment(publicId, StringToContentType(name), call.GetBodyData(), call.GetBodySize())) | |
1373 { | |
1374 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
1375 } | |
1376 else | |
1377 { | |
1378 call.GetOutput().SignalError(HttpStatus_403_Forbidden); | |
1379 } | |
1380 } | |
1381 | |
1382 | |
1383 static void DeleteAttachment(RestApiDeleteCall& call) | |
1384 { | |
1385 CheckValidResourceType(call); | |
1386 | |
1387 std::string publicId = call.GetUriComponent("id", ""); | |
1388 std::string name = call.GetUriComponent("name", ""); | |
1389 FileContentType contentType = StringToContentType(name); | |
1390 | |
1391 bool allowed; | |
1392 if (IsUserContentType(contentType)) | |
1393 { | |
1394 allowed = true; | |
1395 } | |
1396 else | |
1397 { | |
1398 OrthancConfiguration::ReaderLock lock; | |
1399 | |
1400 if (lock.GetConfiguration().GetBooleanParameter("StoreDicom", true) && | |
1401 contentType == FileContentType_DicomAsJson) | |
1402 { | |
1403 allowed = true; | |
1404 } | |
1405 else | |
1406 { | |
1407 // It is forbidden to delete internal attachments, except for | |
1408 // the "DICOM as JSON" summary as of Orthanc 1.2.0 (this summary | |
1409 // would be automatically reconstructed on the next GET call) | |
1410 allowed = false; | |
1411 } | |
1412 } | |
1413 | |
1414 if (allowed) | |
1415 { | |
1416 OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType); | |
1417 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
1418 } | |
1419 else | |
1420 { | |
1421 call.GetOutput().SignalError(HttpStatus_403_Forbidden); | |
1422 } | |
1423 } | |
1424 | |
1425 | |
1426 template <enum CompressionType compression> | |
1427 static void ChangeAttachmentCompression(RestApiPostCall& call) | |
1428 { | |
1429 CheckValidResourceType(call); | |
1430 | |
1431 std::string publicId = call.GetUriComponent("id", ""); | |
1432 std::string name = call.GetUriComponent("name", ""); | |
1433 FileContentType contentType = StringToContentType(name); | |
1434 | |
1435 OrthancRestApi::GetContext(call).ChangeAttachmentCompression(publicId, contentType, compression); | |
1436 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
1437 } | |
1438 | |
1439 | |
1440 static void IsAttachmentCompressed(RestApiGetCall& call) | |
1441 { | |
1442 FileInfo info; | |
1443 if (GetAttachmentInfo(info, call)) | |
1444 { | |
1445 std::string answer = (info.GetCompressionType() == CompressionType_None) ? "0" : "1"; | |
1446 call.GetOutput().AnswerBuffer(answer, MimeType_PlainText); | |
1447 } | |
1448 } | |
1449 | |
1450 | |
1451 // Raw access to the DICOM tags of an instance ------------------------------ | |
1452 | |
1453 static void GetRawContent(RestApiGetCall& call) | |
1454 { | |
1455 std::string id = call.GetUriComponent("id", ""); | |
1456 | |
1457 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); | |
1458 | |
1459 locker.GetDicom().SendPathValue(call.GetOutput(), call.GetTrailingUri()); | |
1460 } | |
1461 | |
1462 | |
1463 | |
1464 static bool ExtractSharedTags(Json::Value& shared, | |
1465 ServerContext& context, | |
1466 const std::string& publicId) | |
1467 { | |
1468 // Retrieve all the instances of this patient/study/series | |
1469 typedef std::list<std::string> Instances; | |
1470 Instances instances; | |
1471 context.GetIndex().GetChildInstances(instances, publicId); // (*) | |
1472 | |
1473 // Loop over the instances | |
1474 bool isFirst = true; | |
1475 shared = Json::objectValue; | |
1476 | |
1477 for (Instances::const_iterator it = instances.begin(); | |
1478 it != instances.end(); ++it) | |
1479 { | |
1480 // Get the tags of the current instance, in the simplified format | |
1481 Json::Value tags; | |
1482 | |
1483 try | |
1484 { | |
1485 context.ReadDicomAsJson(tags, *it); | |
1486 } | |
1487 catch (OrthancException&) | |
1488 { | |
1489 // Race condition: This instance has been removed since | |
1490 // (*). Ignore this instance. | |
1491 continue; | |
1492 } | |
1493 | |
1494 if (tags.type() != Json::objectValue) | |
1495 { | |
1496 return false; // Error | |
1497 } | |
1498 | |
1499 // Only keep the tags that are mapped to a string | |
1500 Json::Value::Members members = tags.getMemberNames(); | |
1501 for (size_t i = 0; i < members.size(); i++) | |
1502 { | |
1503 const Json::Value& tag = tags[members[i]]; | |
1504 if (tag.type() != Json::objectValue || | |
1505 tag["Type"].type() != Json::stringValue || | |
1506 tag["Type"].asString() != "String") | |
1507 { | |
1508 tags.removeMember(members[i]); | |
1509 } | |
1510 } | |
1511 | |
1512 if (isFirst) | |
1513 { | |
1514 // This is the first instance, keep its tags as such | |
1515 shared = tags; | |
1516 isFirst = false; | |
1517 } | |
1518 else | |
1519 { | |
1520 // Loop over all the members of the shared tags extracted so | |
1521 // far. If the value of one of these tags does not match its | |
1522 // value in the current instance, remove it. | |
1523 members = shared.getMemberNames(); | |
1524 for (size_t i = 0; i < members.size(); i++) | |
1525 { | |
1526 if (!tags.isMember(members[i]) || | |
1527 tags[members[i]]["Value"].asString() != shared[members[i]]["Value"].asString()) | |
1528 { | |
1529 shared.removeMember(members[i]); | |
1530 } | |
1531 } | |
1532 } | |
1533 } | |
1534 | |
1535 return true; | |
1536 } | |
1537 | |
1538 | |
1539 static void GetSharedTags(RestApiGetCall& call) | |
1540 { | |
1541 ServerContext& context = OrthancRestApi::GetContext(call); | |
1542 std::string publicId = call.GetUriComponent("id", ""); | |
1543 | |
1544 Json::Value sharedTags; | |
1545 if (ExtractSharedTags(sharedTags, context, publicId)) | |
1546 { | |
1547 // Success: Send the value of the shared tags | |
1548 AnswerDicomAsJson(call, sharedTags); | |
1549 } | |
1550 } | |
1551 | |
1552 | |
1553 static void GetModuleInternal(RestApiGetCall& call, | |
1554 ResourceType resourceType, | |
1555 DicomModule module) | |
1556 { | |
1557 if (!((resourceType == ResourceType_Patient && module == DicomModule_Patient) || | |
1558 (resourceType == ResourceType_Study && module == DicomModule_Patient) || | |
1559 (resourceType == ResourceType_Study && module == DicomModule_Study) || | |
1560 (resourceType == ResourceType_Series && module == DicomModule_Series) || | |
1561 (resourceType == ResourceType_Instance && module == DicomModule_Instance) || | |
1562 (resourceType == ResourceType_Instance && module == DicomModule_Image))) | |
1563 { | |
1564 throw OrthancException(ErrorCode_NotImplemented); | |
1565 } | |
1566 | |
1567 ServerContext& context = OrthancRestApi::GetContext(call); | |
1568 std::string publicId = call.GetUriComponent("id", ""); | |
1569 | |
1570 std::set<DicomTag> ignoreTagLength; | |
1571 ParseSetOfTags(ignoreTagLength, call, "ignore-length"); | |
1572 | |
1573 typedef std::set<DicomTag> ModuleTags; | |
1574 ModuleTags moduleTags; | |
1575 DicomTag::AddTagsForModule(moduleTags, module); | |
1576 | |
1577 Json::Value tags; | |
1578 | |
1579 if (resourceType != ResourceType_Instance) | |
1580 { | |
1581 // Retrieve all the instances of this patient/study/series | |
1582 typedef std::list<std::string> Instances; | |
1583 Instances instances; | |
1584 context.GetIndex().GetChildInstances(instances, publicId); | |
1585 | |
1586 if (instances.empty()) | |
1587 { | |
1588 return; // Error: No instance (should never happen) | |
1589 } | |
1590 | |
1591 // Select one child instance | |
1592 publicId = instances.front(); | |
1593 } | |
1594 | |
1595 context.ReadDicomAsJson(tags, publicId, ignoreTagLength); | |
1596 | |
1597 // Filter the tags of the instance according to the module | |
1598 Json::Value result = Json::objectValue; | |
1599 for (ModuleTags::const_iterator tag = moduleTags.begin(); tag != moduleTags.end(); ++tag) | |
1600 { | |
1601 std::string s = tag->Format(); | |
1602 if (tags.isMember(s)) | |
1603 { | |
1604 result[s] = tags[s]; | |
1605 } | |
1606 } | |
1607 | |
1608 AnswerDicomAsJson(call, result); | |
1609 } | |
1610 | |
1611 | |
1612 | |
1613 template <enum ResourceType resourceType, | |
1614 enum DicomModule module> | |
1615 static void GetModule(RestApiGetCall& call) | |
1616 { | |
1617 GetModuleInternal(call, resourceType, module); | |
1618 } | |
1619 | |
1620 | |
1621 namespace | |
1622 { | |
1623 typedef std::list< std::pair<ResourceType, std::string> > LookupResults; | |
1624 } | |
1625 | |
1626 | |
1627 static void AccumulateLookupResults(LookupResults& result, | |
1628 ServerIndex& index, | |
1629 const DicomTag& tag, | |
1630 const std::string& value, | |
1631 ResourceType level) | |
1632 { | |
1633 std::vector<std::string> tmp; | |
1634 index.LookupIdentifierExact(tmp, level, tag, value); | |
1635 | |
1636 for (size_t i = 0; i < tmp.size(); i++) | |
1637 { | |
1638 result.push_back(std::make_pair(level, tmp[i])); | |
1639 } | |
1640 } | |
1641 | |
1642 | |
1643 static void Lookup(RestApiPostCall& call) | |
1644 { | |
1645 std::string tag; | |
1646 call.BodyToString(tag); | |
1647 | |
1648 LookupResults resources; | |
1649 ServerIndex& index = OrthancRestApi::GetIndex(call); | |
1650 AccumulateLookupResults(resources, index, DICOM_TAG_PATIENT_ID, tag, ResourceType_Patient); | |
1651 AccumulateLookupResults(resources, index, DICOM_TAG_STUDY_INSTANCE_UID, tag, ResourceType_Study); | |
1652 AccumulateLookupResults(resources, index, DICOM_TAG_SERIES_INSTANCE_UID, tag, ResourceType_Series); | |
1653 AccumulateLookupResults(resources, index, DICOM_TAG_SOP_INSTANCE_UID, tag, ResourceType_Instance); | |
1654 | |
1655 Json::Value result = Json::arrayValue; | |
1656 for (LookupResults::const_iterator | |
1657 it = resources.begin(); it != resources.end(); ++it) | |
1658 { | |
1659 ResourceType type = it->first; | |
1660 const std::string& id = it->second; | |
1661 | |
1662 Json::Value item = Json::objectValue; | |
1663 item["Type"] = EnumerationToString(type); | |
1664 item["ID"] = id; | |
1665 item["Path"] = GetBasePath(type, id); | |
1666 | |
1667 result.append(item); | |
1668 } | |
1669 | |
1670 call.GetOutput().AnswerJson(result); | |
1671 } | |
1672 | |
1673 | |
1674 namespace | |
1675 { | |
1676 class FindVisitor : public ServerContext::ILookupVisitor | |
1677 { | |
1678 private: | |
1679 bool isComplete_; | |
1680 std::list<std::string> resources_; | |
1681 | |
1682 public: | |
1683 FindVisitor() : | |
1684 isComplete_(false) | |
1685 { | |
1686 } | |
1687 | |
1688 virtual bool IsDicomAsJsonNeeded() const | |
1689 { | |
1690 return false; // (*) | |
1691 } | |
1692 | |
1693 virtual void MarkAsComplete() | |
1694 { | |
1695 isComplete_ = true; // Unused information as of Orthanc 1.5.0 | |
1696 } | |
1697 | |
1698 virtual void Visit(const std::string& publicId, | |
1699 const std::string& instanceId /* unused */, | |
1700 const DicomMap& mainDicomTags /* unused */, | |
1701 const Json::Value* dicomAsJson /* unused (*) */) | |
1702 { | |
1703 resources_.push_back(publicId); | |
1704 } | |
1705 | |
1706 void Answer(RestApiOutput& output, | |
1707 ServerIndex& index, | |
1708 ResourceType level, | |
1709 bool expand) const | |
1710 { | |
1711 AnswerListOfResources(output, index, resources_, level, expand); | |
1712 } | |
1713 }; | |
1714 } | |
1715 | |
1716 | |
1717 static void Find(RestApiPostCall& call) | |
1718 { | |
1719 static const char* const KEY_CASE_SENSITIVE = "CaseSensitive"; | |
1720 static const char* const KEY_EXPAND = "Expand"; | |
1721 static const char* const KEY_LEVEL = "Level"; | |
1722 static const char* const KEY_LIMIT = "Limit"; | |
1723 static const char* const KEY_QUERY = "Query"; | |
1724 static const char* const KEY_SINCE = "Since"; | |
1725 | |
1726 ServerContext& context = OrthancRestApi::GetContext(call); | |
1727 | |
1728 Json::Value request; | |
1729 if (!call.ParseJsonRequest(request) || | |
1730 request.type() != Json::objectValue) | |
1731 { | |
1732 throw OrthancException(ErrorCode_BadRequest, | |
1733 "The body must contain a JSON object"); | |
1734 } | |
1735 else if (!request.isMember(KEY_LEVEL) || | |
1736 request[KEY_LEVEL].type() != Json::stringValue) | |
1737 { | |
1738 throw OrthancException(ErrorCode_BadRequest, | |
1739 "Field \"" + std::string(KEY_LEVEL) + "\" is missing, or should be a string"); | |
1740 } | |
1741 else if (!request.isMember(KEY_QUERY) && | |
1742 request[KEY_QUERY].type() != Json::objectValue) | |
1743 { | |
1744 throw OrthancException(ErrorCode_BadRequest, | |
1745 "Field \"" + std::string(KEY_QUERY) + "\" is missing, or should be a JSON object"); | |
1746 } | |
1747 else if (request.isMember(KEY_CASE_SENSITIVE) && | |
1748 request[KEY_CASE_SENSITIVE].type() != Json::booleanValue) | |
1749 { | |
1750 throw OrthancException(ErrorCode_BadRequest, | |
1751 "Field \"" + std::string(KEY_CASE_SENSITIVE) + "\" should be a Boolean"); | |
1752 } | |
1753 else if (request.isMember(KEY_LIMIT) && | |
1754 request[KEY_LIMIT].type() != Json::intValue) | |
1755 { | |
1756 throw OrthancException(ErrorCode_BadRequest, | |
1757 "Field \"" + std::string(KEY_LIMIT) + "\" should be an integer"); | |
1758 } | |
1759 else if (request.isMember(KEY_SINCE) && | |
1760 request[KEY_SINCE].type() != Json::intValue) | |
1761 { | |
1762 throw OrthancException(ErrorCode_BadRequest, | |
1763 "Field \"" + std::string(KEY_SINCE) + "\" should be an integer"); | |
1764 } | |
1765 else | |
1766 { | |
1767 bool expand = false; | |
1768 if (request.isMember(KEY_EXPAND)) | |
1769 { | |
1770 expand = request[KEY_EXPAND].asBool(); | |
1771 } | |
1772 | |
1773 bool caseSensitive = false; | |
1774 if (request.isMember(KEY_CASE_SENSITIVE)) | |
1775 { | |
1776 caseSensitive = request[KEY_CASE_SENSITIVE].asBool(); | |
1777 } | |
1778 | |
1779 size_t limit = 0; | |
1780 if (request.isMember(KEY_LIMIT)) | |
1781 { | |
1782 int tmp = request[KEY_LIMIT].asInt(); | |
1783 if (tmp < 0) | |
1784 { | |
1785 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
1786 "Field \"" + std::string(KEY_LIMIT) + "\" should be a positive integer"); | |
1787 } | |
1788 | |
1789 limit = static_cast<size_t>(tmp); | |
1790 } | |
1791 | |
1792 size_t since = 0; | |
1793 if (request.isMember(KEY_SINCE)) | |
1794 { | |
1795 int tmp = request[KEY_SINCE].asInt(); | |
1796 if (tmp < 0) | |
1797 { | |
1798 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
1799 "Field \"" + std::string(KEY_SINCE) + "\" should be a positive integer"); | |
1800 } | |
1801 | |
1802 since = static_cast<size_t>(tmp); | |
1803 } | |
1804 | |
1805 ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString()); | |
1806 | |
1807 DatabaseLookup query; | |
1808 | |
1809 Json::Value::Members members = request[KEY_QUERY].getMemberNames(); | |
1810 for (size_t i = 0; i < members.size(); i++) | |
1811 { | |
1812 if (request[KEY_QUERY][members[i]].type() != Json::stringValue) | |
1813 { | |
1814 throw OrthancException(ErrorCode_BadRequest, | |
1815 "Tag \"" + members[i] + "\" should be associated with a string"); | |
1816 } | |
1817 | |
1818 const std::string value = request[KEY_QUERY][members[i]].asString(); | |
1819 | |
1820 if (!value.empty()) | |
1821 { | |
1822 // An empty string corresponds to an universal constraint, | |
1823 // so we ignore it. This mimics the behavior of class | |
1824 // "OrthancFindRequestHandler" | |
1825 query.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]), | |
1826 value, caseSensitive, true); | |
1827 } | |
1828 } | |
1829 | |
1830 FindVisitor visitor; | |
1831 context.Apply(visitor, query, level, since, limit); | |
1832 visitor.Answer(call.GetOutput(), context.GetIndex(), level, expand); | |
1833 } | |
1834 } | |
1835 | |
1836 | |
1837 template <enum ResourceType start, | |
1838 enum ResourceType end> | |
1839 static void GetChildResources(RestApiGetCall& call) | |
1840 { | |
1841 ServerIndex& index = OrthancRestApi::GetIndex(call); | |
1842 | |
1843 std::list<std::string> a, b, c; | |
1844 a.push_back(call.GetUriComponent("id", "")); | |
1845 | |
1846 ResourceType type = start; | |
1847 while (type != end) | |
1848 { | |
1849 b.clear(); | |
1850 | |
1851 for (std::list<std::string>::const_iterator | |
1852 it = a.begin(); it != a.end(); ++it) | |
1853 { | |
1854 index.GetChildren(c, *it); | |
1855 b.splice(b.begin(), c); | |
1856 } | |
1857 | |
1858 type = GetChildResourceType(type); | |
1859 | |
1860 a.clear(); | |
1861 a.splice(a.begin(), b); | |
1862 } | |
1863 | |
1864 Json::Value result = Json::arrayValue; | |
1865 | |
1866 for (std::list<std::string>::const_iterator | |
1867 it = a.begin(); it != a.end(); ++it) | |
1868 { | |
1869 Json::Value item; | |
1870 | |
1871 if (OrthancRestApi::GetIndex(call).LookupResource(item, *it, end)) | |
1872 { | |
1873 result.append(item); | |
1874 } | |
1875 } | |
1876 | |
1877 call.GetOutput().AnswerJson(result); | |
1878 } | |
1879 | |
1880 | |
1881 static void GetChildInstancesTags(RestApiGetCall& call) | |
1882 { | |
1883 ServerContext& context = OrthancRestApi::GetContext(call); | |
1884 std::string publicId = call.GetUriComponent("id", ""); | |
1885 DicomToJsonFormat format = GetDicomFormat(call); | |
1886 | |
1887 std::set<DicomTag> ignoreTagLength; | |
1888 ParseSetOfTags(ignoreTagLength, call, "ignore-length"); | |
1889 | |
1890 // Retrieve all the instances of this patient/study/series | |
1891 typedef std::list<std::string> Instances; | |
1892 Instances instances; | |
1893 | |
1894 context.GetIndex().GetChildInstances(instances, publicId); // (*) | |
1895 | |
1896 Json::Value result = Json::objectValue; | |
1897 | |
1898 for (Instances::const_iterator it = instances.begin(); | |
1899 it != instances.end(); ++it) | |
1900 { | |
1901 Json::Value full; | |
1902 context.ReadDicomAsJson(full, *it, ignoreTagLength); | |
1903 | |
1904 if (format != DicomToJsonFormat_Full) | |
1905 { | |
1906 Json::Value simplified; | |
1907 ServerToolbox::SimplifyTags(simplified, full, format); | |
1908 result[*it] = simplified; | |
1909 } | |
1910 else | |
1911 { | |
1912 result[*it] = full; | |
1913 } | |
1914 } | |
1915 | |
1916 call.GetOutput().AnswerJson(result); | |
1917 } | |
1918 | |
1919 | |
1920 | |
1921 template <enum ResourceType start, | |
1922 enum ResourceType end> | |
1923 static void GetParentResource(RestApiGetCall& call) | |
1924 { | |
1925 assert(start > end); | |
1926 | |
1927 ServerIndex& index = OrthancRestApi::GetIndex(call); | |
1928 | |
1929 std::string current = call.GetUriComponent("id", ""); | |
1930 ResourceType currentType = start; | |
1931 while (currentType > end) | |
1932 { | |
1933 std::string parent; | |
1934 if (!index.LookupParent(parent, current)) | |
1935 { | |
1936 // Error that could happen if the resource gets deleted by | |
1937 // another concurrent call | |
1938 return; | |
1939 } | |
1940 | |
1941 current = parent; | |
1942 currentType = GetParentResourceType(currentType); | |
1943 } | |
1944 | |
1945 assert(currentType == end); | |
1946 | |
1947 Json::Value result; | |
1948 if (index.LookupResource(result, current, end)) | |
1949 { | |
1950 call.GetOutput().AnswerJson(result); | |
1951 } | |
1952 } | |
1953 | |
1954 | |
1955 static void ExtractPdf(RestApiGetCall& call) | |
1956 { | |
1957 const std::string id = call.GetUriComponent("id", ""); | |
1958 | |
1959 std::string pdf; | |
1960 ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); | |
1961 | |
1962 if (locker.GetDicom().ExtractPdf(pdf)) | |
1963 { | |
1964 call.GetOutput().AnswerBuffer(pdf, MimeType_Pdf); | |
1965 return; | |
1966 } | |
1967 } | |
1968 | |
1969 | |
1970 static void OrderSlices(RestApiGetCall& call) | |
1971 { | |
1972 const std::string id = call.GetUriComponent("id", ""); | |
1973 | |
1974 ServerIndex& index = OrthancRestApi::GetIndex(call); | |
1975 SliceOrdering ordering(index, id); | |
1976 | |
1977 Json::Value result; | |
1978 ordering.Format(result); | |
1979 call.GetOutput().AnswerJson(result); | |
1980 } | |
1981 | |
1982 | |
1983 static void GetInstanceHeader(RestApiGetCall& call) | |
1984 { | |
1985 ServerContext& context = OrthancRestApi::GetContext(call); | |
1986 | |
1987 std::string publicId = call.GetUriComponent("id", ""); | |
1988 | |
1989 std::string dicomContent; | |
1990 context.ReadDicom(dicomContent, publicId); | |
1991 | |
1992 // TODO Consider using "DicomMap::ParseDicomMetaInformation()" to | |
1993 // speed up things here | |
1994 | |
1995 ParsedDicomFile dicom(dicomContent); | |
1996 | |
1997 Json::Value header; | |
1998 dicom.HeaderToJson(header, DicomToJsonFormat_Full); | |
1999 | |
2000 AnswerDicomAsJson(call, header); | |
2001 } | |
2002 | |
2003 | |
2004 static void InvalidateTags(RestApiPostCall& call) | |
2005 { | |
2006 ServerIndex& index = OrthancRestApi::GetIndex(call); | |
2007 | |
2008 // Loop over the instances, grouping them by parent studies so as | |
2009 // to avoid large memory consumption | |
2010 std::list<std::string> studies; | |
2011 index.GetAllUuids(studies, ResourceType_Study); | |
2012 | |
2013 for (std::list<std::string>::const_iterator | |
2014 study = studies.begin(); study != studies.end(); ++study) | |
2015 { | |
2016 std::list<std::string> instances; | |
2017 index.GetChildInstances(instances, *study); | |
2018 | |
2019 for (std::list<std::string>::const_iterator | |
2020 instance = instances.begin(); instance != instances.end(); ++instance) | |
2021 { | |
2022 index.DeleteAttachment(*instance, FileContentType_DicomAsJson); | |
2023 } | |
2024 } | |
2025 | |
2026 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
2027 } | |
2028 | |
2029 | |
2030 template <enum ResourceType type> | |
2031 static void ReconstructResource(RestApiPostCall& call) | |
2032 { | |
2033 ServerContext& context = OrthancRestApi::GetContext(call); | |
2034 ServerToolbox::ReconstructResource(context, call.GetUriComponent("id", "")); | |
2035 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
2036 } | |
2037 | |
2038 | |
2039 static void ReconstructAllResources(RestApiPostCall& call) | |
2040 { | |
2041 ServerContext& context = OrthancRestApi::GetContext(call); | |
2042 | |
2043 std::list<std::string> studies; | |
2044 context.GetIndex().GetAllUuids(studies, ResourceType_Study); | |
2045 | |
2046 for (std::list<std::string>::const_iterator | |
2047 study = studies.begin(); study != studies.end(); ++study) | |
2048 { | |
2049 ServerToolbox::ReconstructResource(context, *study); | |
2050 } | |
2051 | |
2052 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
2053 } | |
2054 | |
2055 | |
2056 void OrthancRestApi::RegisterResources() | |
2057 { | |
2058 Register("/instances", ListResources<ResourceType_Instance>); | |
2059 Register("/patients", ListResources<ResourceType_Patient>); | |
2060 Register("/series", ListResources<ResourceType_Series>); | |
2061 Register("/studies", ListResources<ResourceType_Study>); | |
2062 | |
2063 Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); | |
2064 Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); | |
2065 Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); | |
2066 Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); | |
2067 Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); | |
2068 Register("/series/{id}", GetSingleResource<ResourceType_Series>); | |
2069 Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); | |
2070 Register("/studies/{id}", GetSingleResource<ResourceType_Study>); | |
2071 | |
2072 Register("/instances/{id}/statistics", GetResourceStatistics); | |
2073 Register("/patients/{id}/statistics", GetResourceStatistics); | |
2074 Register("/studies/{id}/statistics", GetResourceStatistics); | |
2075 Register("/series/{id}/statistics", GetResourceStatistics); | |
2076 | |
2077 Register("/patients/{id}/shared-tags", GetSharedTags); | |
2078 Register("/series/{id}/shared-tags", GetSharedTags); | |
2079 Register("/studies/{id}/shared-tags", GetSharedTags); | |
2080 | |
2081 Register("/instances/{id}/module", GetModule<ResourceType_Instance, DicomModule_Instance>); | |
2082 Register("/patients/{id}/module", GetModule<ResourceType_Patient, DicomModule_Patient>); | |
2083 Register("/series/{id}/module", GetModule<ResourceType_Series, DicomModule_Series>); | |
2084 Register("/studies/{id}/module", GetModule<ResourceType_Study, DicomModule_Study>); | |
2085 Register("/studies/{id}/module-patient", GetModule<ResourceType_Study, DicomModule_Patient>); | |
2086 | |
2087 Register("/instances/{id}/file", GetInstanceFile); | |
2088 Register("/instances/{id}/export", ExportInstanceFile); | |
2089 Register("/instances/{id}/tags", GetInstanceTagsBis); | |
2090 Register("/instances/{id}/simplified-tags", GetInstanceTags<DicomToJsonFormat_Human>); | |
2091 Register("/instances/{id}/frames", ListFrames); | |
2092 | |
2093 Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); | |
2094 Register("/instances/{id}/frames/{frame}/rendered", GetRenderedFrame); | |
2095 Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); | |
2096 Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | |
2097 Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); | |
2098 Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); | |
2099 Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>); | |
2100 Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); | |
2101 Register("/instances/{id}/pdf", ExtractPdf); | |
2102 Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); | |
2103 Register("/instances/{id}/rendered", GetRenderedFrame); | |
2104 Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); | |
2105 Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); | |
2106 Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); | |
2107 Register("/instances/{id}/matlab", GetMatlabImage); | |
2108 Register("/instances/{id}/header", GetInstanceHeader); | |
2109 | |
2110 Register("/patients/{id}/protected", IsProtectedPatient); | |
2111 Register("/patients/{id}/protected", SetPatientProtection); | |
2112 | |
2113 Register("/{resourceType}/{id}/metadata", ListMetadata); | |
2114 Register("/{resourceType}/{id}/metadata/{name}", DeleteMetadata); | |
2115 Register("/{resourceType}/{id}/metadata/{name}", GetMetadata); | |
2116 Register("/{resourceType}/{id}/metadata/{name}", SetMetadata); | |
2117 | |
2118 Register("/{resourceType}/{id}/attachments", ListAttachments); | |
2119 Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment); | |
2120 Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations); | |
2121 Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); | |
2122 Register("/{resourceType}/{id}/attachments/{name}/compress", ChangeAttachmentCompression<CompressionType_ZlibWithSize>); | |
2123 Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>); | |
2124 Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5); | |
2125 Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize); | |
2126 Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>); | |
2127 Register("/{resourceType}/{id}/attachments/{name}/is-compressed", IsAttachmentCompressed); | |
2128 Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5); | |
2129 Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize); | |
2130 Register("/{resourceType}/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression<CompressionType_None>); | |
2131 Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); | |
2132 | |
2133 Register("/tools/invalidate-tags", InvalidateTags); | |
2134 Register("/tools/lookup", Lookup); | |
2135 Register("/tools/find", Find); | |
2136 | |
2137 Register("/patients/{id}/studies", GetChildResources<ResourceType_Patient, ResourceType_Study>); | |
2138 Register("/patients/{id}/series", GetChildResources<ResourceType_Patient, ResourceType_Series>); | |
2139 Register("/patients/{id}/instances", GetChildResources<ResourceType_Patient, ResourceType_Instance>); | |
2140 Register("/studies/{id}/series", GetChildResources<ResourceType_Study, ResourceType_Series>); | |
2141 Register("/studies/{id}/instances", GetChildResources<ResourceType_Study, ResourceType_Instance>); | |
2142 Register("/series/{id}/instances", GetChildResources<ResourceType_Series, ResourceType_Instance>); | |
2143 | |
2144 Register("/studies/{id}/patient", GetParentResource<ResourceType_Study, ResourceType_Patient>); | |
2145 Register("/series/{id}/patient", GetParentResource<ResourceType_Series, ResourceType_Patient>); | |
2146 Register("/series/{id}/study", GetParentResource<ResourceType_Series, ResourceType_Study>); | |
2147 Register("/instances/{id}/patient", GetParentResource<ResourceType_Instance, ResourceType_Patient>); | |
2148 Register("/instances/{id}/study", GetParentResource<ResourceType_Instance, ResourceType_Study>); | |
2149 Register("/instances/{id}/series", GetParentResource<ResourceType_Instance, ResourceType_Series>); | |
2150 | |
2151 Register("/patients/{id}/instances-tags", GetChildInstancesTags); | |
2152 Register("/studies/{id}/instances-tags", GetChildInstancesTags); | |
2153 Register("/series/{id}/instances-tags", GetChildInstancesTags); | |
2154 | |
2155 Register("/instances/{id}/content/*", GetRawContent); | |
2156 | |
2157 Register("/series/{id}/ordered-slices", OrderSlices); | |
2158 | |
2159 Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); | |
2160 Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>); | |
2161 Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>); | |
2162 Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>); | |
2163 Register("/tools/reconstruct", ReconstructAllResources); | |
2164 } | |
2165 } |