comparison Samples/Sdl/Loader.cpp @ 746:d716bfb3e07c

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 22 May 2019 12:48:57 +0200
parents c44c1d2d3598
children ab236bb5dbc7
comparison
equal deleted inserted replaced
745:c44c1d2d3598 746:d716bfb3e07c
17 * You should have received a copy of the GNU Affero General Public License 17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/ 19 **/
20 20
21 21
22 #include "../../Framework/Toolbox/DicomInstanceParameters.h"
23 #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h"
24 #include "../../Framework/Oracle/GetOrthancImageCommand.h"
25 #include "../../Framework/Oracle/OrthancRestApiCommand.h"
22 #include "../../Framework/Oracle/SleepOracleCommand.h" 26 #include "../../Framework/Oracle/SleepOracleCommand.h"
23 #include "../../Framework/Oracle/OracleCommandExceptionMessage.h" 27 #include "../../Framework/Oracle/OracleCommandExceptionMessage.h"
24 #include "../../Framework/Messages/IMessageEmitter.h" 28 #include "../../Framework/Messages/IMessageEmitter.h"
25 #include "../../Framework/Oracle/OracleCommandWithPayload.h" 29 #include "../../Framework/Oracle/OracleCommandWithPayload.h"
26 #include "../../Framework/Oracle/IOracle.h" 30 #include "../../Framework/Oracle/IOracle.h"
27 31
28 // From Stone 32 // From Stone
29 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" 33 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h"
30 #include "../../Framework/Loaders/BasicFetchingStrategy.h" 34 #include "../../Framework/Loaders/BasicFetchingStrategy.h"
31 #include "../../Framework/Messages/ICallable.h"
32 #include "../../Framework/Messages/IMessage.h"
33 #include "../../Framework/Messages/IObservable.h"
34 #include "../../Framework/Messages/MessageBroker.h"
35 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
36 #include "../../Framework/Scene2D/FloatTextureSceneLayer.h"
37 #include "../../Framework/Scene2D/Scene2D.h" 35 #include "../../Framework/Scene2D/Scene2D.h"
38 #include "../../Framework/StoneInitialization.h" 36 #include "../../Framework/StoneInitialization.h"
39 #include "../../Framework/Toolbox/GeometryToolbox.h" 37 #include "../../Framework/Toolbox/GeometryToolbox.h"
40 #include "../../Framework/Toolbox/SlicesSorter.h" 38 #include "../../Framework/Toolbox/SlicesSorter.h"
41 #include "../../Framework/Volumes/ImageBuffer3D.h" 39 #include "../../Framework/Volumes/ImageBuffer3D.h"
46 #include <Core/Compression/ZlibCompressor.h> 44 #include <Core/Compression/ZlibCompressor.h>
47 #include <Core/DicomFormat/DicomArray.h> 45 #include <Core/DicomFormat/DicomArray.h>
48 #include <Core/DicomFormat/DicomImageInformation.h> 46 #include <Core/DicomFormat/DicomImageInformation.h>
49 #include <Core/HttpClient.h> 47 #include <Core/HttpClient.h>
50 #include <Core/IDynamicObject.h> 48 #include <Core/IDynamicObject.h>
51 #include <Core/Images/Image.h>
52 #include <Core/Images/ImageProcessing.h> 49 #include <Core/Images/ImageProcessing.h>
53 #include <Core/Images/JpegReader.h>
54 #include <Core/Images/PamReader.h>
55 #include <Core/Images/PngReader.h>
56 #include <Core/Images/PngWriter.h>
57 #include <Core/Logging.h> 50 #include <Core/Logging.h>
58 #include <Core/MultiThreading/SharedMessageQueue.h> 51 #include <Core/MultiThreading/SharedMessageQueue.h>
59 #include <Core/OrthancException.h> 52 #include <Core/OrthancException.h>
60 #include <Core/SystemToolbox.h> 53 #include <Core/SystemToolbox.h>
61 #include <Core/Toolbox.h> 54 #include <Core/Toolbox.h>
69 62
70 63
71 64
72 namespace OrthancStone 65 namespace OrthancStone
73 { 66 {
74 typedef std::map<std::string, std::string> HttpHeaders; 67 class DicomVolumeImage : public boost::noncopyable
75 68 {
76 class OrthancRestApiCommand : public OracleCommandWithPayload
77 {
78 public:
79 class SuccessMessage : public OriginMessage<OrthancRestApiCommand>
80 {
81 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
82
83 private:
84 HttpHeaders headers_;
85 std::string answer_;
86
87 public:
88 SuccessMessage(const OrthancRestApiCommand& command,
89 const HttpHeaders& answerHeaders,
90 std::string& answer /* will be swapped to avoid a memcpy() */) :
91 OriginMessage(command),
92 headers_(answerHeaders),
93 answer_(answer)
94 {
95 }
96
97 const std::string& GetAnswer() const
98 {
99 return answer_;
100 }
101
102 void ParseJsonBody(Json::Value& target) const
103 {
104 Json::Reader reader;
105 if (!reader.parse(answer_, target))
106 {
107 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
108 }
109 }
110
111 const HttpHeaders& GetAnswerHeaders() const
112 {
113 return headers_;
114 }
115 };
116
117
118 private: 69 private:
119 Orthanc::HttpMethod method_; 70 std::auto_ptr<ImageBuffer3D> image_;
120 std::string uri_; 71 std::auto_ptr<VolumeImageGeometry> geometry_;
121 std::string body_; 72 std::vector<DicomInstanceParameters*> slices_;
122 HttpHeaders headers_; 73 uint64_t revision_;
123 unsigned int timeout_; 74 std::vector<uint64_t> slicesRevision_;
124 75 std::vector<unsigned int> slicesQuality_;
125 public:
126 OrthancRestApiCommand() :
127 method_(Orthanc::HttpMethod_Get),
128 uri_("/"),
129 timeout_(10)
130 {
131 }
132
133 virtual Type GetType() const
134 {
135 return Type_OrthancRestApi;
136 }
137
138 void SetMethod(Orthanc::HttpMethod method)
139 {
140 method_ = method;
141 }
142
143 void SetUri(const std::string& uri)
144 {
145 uri_ = uri;
146 }
147
148 void SetBody(const std::string& body)
149 {
150 body_ = body;
151 }
152
153 void SetBody(const Json::Value& json)
154 {
155 Json::FastWriter writer;
156 body_ = writer.write(json);
157 }
158
159 void SetHttpHeader(const std::string& key,
160 const std::string& value)
161 {
162 headers_[key] = value;
163 }
164
165 Orthanc::HttpMethod GetMethod() const
166 {
167 return method_;
168 }
169
170 const std::string& GetUri() const
171 {
172 return uri_;
173 }
174
175 const std::string& GetBody() const
176 {
177 if (method_ == Orthanc::HttpMethod_Post ||
178 method_ == Orthanc::HttpMethod_Put)
179 {
180 return body_;
181 }
182 else
183 {
184 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
185 }
186 }
187
188 const HttpHeaders& GetHttpHeaders() const
189 {
190 return headers_;
191 }
192
193 void SetTimeout(unsigned int seconds)
194 {
195 timeout_ = seconds;
196 }
197
198 unsigned int GetTimeout() const
199 {
200 return timeout_;
201 }
202 };
203
204
205
206
207 class GetOrthancImageCommand : public OracleCommandWithPayload
208 {
209 public:
210 class SuccessMessage : public OriginMessage<GetOrthancImageCommand>
211 {
212 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
213
214 private:
215 std::auto_ptr<Orthanc::ImageAccessor> image_;
216 Orthanc::MimeType mime_;
217
218 public:
219 SuccessMessage(const GetOrthancImageCommand& command,
220 Orthanc::ImageAccessor* image, // Takes ownership
221 Orthanc::MimeType mime) :
222 OriginMessage(command),
223 image_(image),
224 mime_(mime)
225 {
226 if (image == NULL)
227 {
228 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
229 }
230 }
231
232 const Orthanc::ImageAccessor& GetImage() const
233 {
234 return *image_;
235 }
236
237 Orthanc::MimeType GetMimeType() const
238 {
239 return mime_;
240 }
241 };
242
243
244 private:
245 std::string uri_;
246 HttpHeaders headers_;
247 unsigned int timeout_;
248 bool hasExpectedFormat_;
249 Orthanc::PixelFormat expectedFormat_;
250
251 public:
252 GetOrthancImageCommand() :
253 uri_("/"),
254 timeout_(10),
255 hasExpectedFormat_(false)
256 {
257 }
258
259 virtual Type GetType() const
260 {
261 return Type_GetOrthancImage;
262 }
263
264 void SetExpectedPixelFormat(Orthanc::PixelFormat format)
265 {
266 hasExpectedFormat_ = true;
267 expectedFormat_ = format;
268 }
269
270 void SetUri(const std::string& uri)
271 {
272 uri_ = uri;
273 }
274
275 void SetInstanceUri(const std::string& instance,
276 Orthanc::PixelFormat pixelFormat)
277 {
278 uri_ = "/instances/" + instance;
279
280 switch (pixelFormat)
281 {
282 case Orthanc::PixelFormat_RGB24:
283 uri_ += "/preview";
284 break;
285
286 case Orthanc::PixelFormat_Grayscale16:
287 uri_ += "/image-uint16";
288 break;
289
290 case Orthanc::PixelFormat_SignedGrayscale16:
291 uri_ += "/image-int16";
292 break;
293
294 default:
295 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
296 }
297 }
298
299 void SetHttpHeader(const std::string& key,
300 const std::string& value)
301 {
302 headers_[key] = value;
303 }
304
305 const std::string& GetUri() const
306 {
307 return uri_;
308 }
309
310 const HttpHeaders& GetHttpHeaders() const
311 {
312 return headers_;
313 }
314
315 void SetTimeout(unsigned int seconds)
316 {
317 timeout_ = seconds;
318 }
319
320 unsigned int GetTimeout() const
321 {
322 return timeout_;
323 }
324
325 void ProcessHttpAnswer(IMessageEmitter& emitter,
326 const IObserver& receiver,
327 const std::string& answer,
328 const HttpHeaders& answerHeaders) const
329 {
330 Orthanc::MimeType contentType = Orthanc::MimeType_Binary;
331
332 for (HttpHeaders::const_iterator it = answerHeaders.begin();
333 it != answerHeaders.end(); ++it)
334 {
335 std::string s;
336 Orthanc::Toolbox::ToLowerCase(s, it->first);
337
338 if (s == "content-type")
339 {
340 contentType = Orthanc::StringToMimeType(it->second);
341 break;
342 }
343 }
344
345 std::auto_ptr<Orthanc::ImageAccessor> image;
346
347 switch (contentType)
348 {
349 case Orthanc::MimeType_Png:
350 {
351 image.reset(new Orthanc::PngReader);
352 dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer);
353 break;
354 }
355
356 case Orthanc::MimeType_Pam:
357 {
358 image.reset(new Orthanc::PamReader);
359 dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer);
360 break;
361 }
362
363 case Orthanc::MimeType_Jpeg:
364 {
365 image.reset(new Orthanc::JpegReader);
366 dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer);
367 break;
368 }
369
370 default:
371 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
372 "Unsupported HTTP Content-Type for an image: " +
373 std::string(Orthanc::EnumerationToString(contentType)));
374 }
375
376 if (hasExpectedFormat_)
377 {
378 if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 &&
379 image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
380 {
381 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
382 }
383
384 if (expectedFormat_ != image->GetFormat())
385 {
386 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
387 }
388 }
389
390 SuccessMessage message(*this, image.release(), contentType);
391 emitter.EmitMessage(receiver, message);
392 }
393 };
394
395
396
397 class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
398 {
399 public:
400 class SuccessMessage : public OriginMessage<GetOrthancWebViewerJpegCommand>
401 {
402 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
403
404 private:
405 std::auto_ptr<Orthanc::ImageAccessor> image_;
406
407 public:
408 SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
409 Orthanc::ImageAccessor* image) : // Takes ownership
410 OriginMessage(command),
411 image_(image)
412 {
413 if (image == NULL)
414 {
415 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
416 }
417 }
418
419 const Orthanc::ImageAccessor& GetImage() const
420 {
421 return *image_;
422 }
423 };
424
425 private:
426 std::string instanceId_;
427 unsigned int frame_;
428 unsigned int quality_;
429 HttpHeaders headers_;
430 unsigned int timeout_;
431 Orthanc::PixelFormat expectedFormat_;
432
433 public:
434 GetOrthancWebViewerJpegCommand() :
435 frame_(0),
436 quality_(95),
437 timeout_(10),
438 expectedFormat_(Orthanc::PixelFormat_Grayscale8)
439 {
440 }
441
442 virtual Type GetType() const
443 {
444 return Type_GetOrthancWebViewerJpeg;
445 }
446
447 void SetExpectedPixelFormat(Orthanc::PixelFormat format)
448 {
449 expectedFormat_ = format;
450 }
451
452 void SetInstance(const std::string& instanceId)
453 {
454 instanceId_ = instanceId;
455 }
456
457 void SetFrame(unsigned int frame)
458 {
459 frame_ = frame;
460 }
461
462 void SetQuality(unsigned int quality)
463 {
464 if (quality <= 0 ||
465 quality > 100)
466 {
467 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
468 }
469 else
470 {
471 quality_ = quality;
472 }
473 }
474
475 void SetHttpHeader(const std::string& key,
476 const std::string& value)
477 {
478 headers_[key] = value;
479 }
480
481 Orthanc::PixelFormat GetExpectedPixelFormat() const
482 {
483 return expectedFormat_;
484 }
485
486 const std::string& GetInstanceId() const
487 {
488 return instanceId_;
489 }
490
491 unsigned int GetFrame() const
492 {
493 return frame_;
494 }
495
496 unsigned int GetQuality() const
497 {
498 return quality_;
499 }
500
501 const HttpHeaders& GetHttpHeaders() const
502 {
503 return headers_;
504 }
505
506 void SetTimeout(unsigned int seconds)
507 {
508 timeout_ = seconds;
509 }
510
511 unsigned int GetTimeout() const
512 {
513 return timeout_;
514 }
515
516 std::string GetUri() const
517 {
518 return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) +
519 "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_));
520 }
521
522 void ProcessHttpAnswer(IMessageEmitter& emitter,
523 const IObserver& receiver,
524 const std::string& answer) const
525 {
526 // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
527
528 Json::Value encoded;
529
530 {
531 Json::Reader reader;
532 if (!reader.parse(answer, encoded))
533 {
534 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
535 }
536 }
537
538 if (encoded.type() != Json::objectValue ||
539 !encoded.isMember("Orthanc") ||
540 encoded["Orthanc"].type() != Json::objectValue)
541 {
542 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
543 }
544
545 const Json::Value& info = encoded["Orthanc"];
546 if (!info.isMember("PixelData") ||
547 !info.isMember("Stretched") ||
548 !info.isMember("Compression") ||
549 info["Compression"].type() != Json::stringValue ||
550 info["PixelData"].type() != Json::stringValue ||
551 info["Stretched"].type() != Json::booleanValue ||
552 info["Compression"].asString() != "Jpeg")
553 {
554 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
555 }
556
557 bool isSigned = false;
558 bool isStretched = info["Stretched"].asBool();
559
560 if (info.isMember("IsSigned"))
561 {
562 if (info["IsSigned"].type() != Json::booleanValue)
563 {
564 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
565 }
566 else
567 {
568 isSigned = info["IsSigned"].asBool();
569 }
570 }
571
572 std::auto_ptr<Orthanc::ImageAccessor> reader;
573
574 {
575 std::string jpeg;
576 Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
577
578 reader.reset(new Orthanc::JpegReader);
579 dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
580 }
581
582 if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image
583 {
584 if (expectedFormat_ != Orthanc::PixelFormat_RGB24)
585 {
586 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
587 }
588
589 if (isSigned || isStretched)
590 {
591 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
592 }
593 else
594 {
595 SuccessMessage message(*this, reader.release());
596 emitter.EmitMessage(receiver, message);
597 return;
598 }
599 }
600
601 if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
602 {
603 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
604 }
605
606 if (!isStretched)
607 {
608 if (expectedFormat_ != reader->GetFormat())
609 {
610 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
611 }
612 else
613 {
614 SuccessMessage message(*this, reader.release());
615 emitter.EmitMessage(receiver, message);
616 return;
617 }
618 }
619
620 int32_t stretchLow = 0;
621 int32_t stretchHigh = 0;
622
623 if (!info.isMember("StretchLow") ||
624 !info.isMember("StretchHigh") ||
625 info["StretchLow"].type() != Json::intValue ||
626 info["StretchHigh"].type() != Json::intValue)
627 {
628 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
629 }
630
631 stretchLow = info["StretchLow"].asInt();
632 stretchHigh = info["StretchHigh"].asInt();
633
634 if (stretchLow < -32768 ||
635 stretchHigh > 65535 ||
636 (stretchLow < 0 && stretchHigh > 32767))
637 {
638 // This range cannot be represented with a uint16_t or an int16_t
639 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
640 }
641
642 // Decode a grayscale JPEG 8bpp image coming from the Web viewer
643 std::auto_ptr<Orthanc::ImageAccessor> image
644 (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false));
645
646 Orthanc::ImageProcessing::Convert(*image, *reader);
647 reader.reset();
648
649 float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
650
651 if (!LinearAlgebra::IsCloseToZero(scaling))
652 {
653 float offset = static_cast<float>(stretchLow) / scaling;
654 Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
655 }
656
657 SuccessMessage message(*this, image.release());
658 emitter.EmitMessage(receiver, message);
659 }
660 };
661
662
663
664
665
666 class DicomInstanceParameters :
667 public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */
668 {
669 private:
670 struct Data // Struct to ease the copy constructor
671 {
672 std::string orthancInstanceId_;
673 std::string studyInstanceUid_;
674 std::string seriesInstanceUid_;
675 std::string sopInstanceUid_;
676 Orthanc::DicomImageInformation imageInformation_;
677 SopClassUid sopClassUid_;
678 double thickness_;
679 double pixelSpacingX_;
680 double pixelSpacingY_;
681 CoordinateSystem3D geometry_;
682 Vector frameOffsets_;
683 bool isColor_;
684 bool hasRescale_;
685 double rescaleIntercept_;
686 double rescaleSlope_;
687 bool hasDefaultWindowing_;
688 float defaultWindowingCenter_;
689 float defaultWindowingWidth_;
690 Orthanc::PixelFormat expectedPixelFormat_;
691
692 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
693 {
694 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
695
696 {
697 std::string increment;
698
699 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
700 {
701 Orthanc::Toolbox::ToUpperCase(increment);
702 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag
703 {
704 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
705 return;
706 }
707 }
708 }
709
710 if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
711 frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
712 {
713 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
714 frameOffsets_.clear();
715 }
716 else
717 {
718 if (frameOffsets_.size() >= 2)
719 {
720 thickness_ = frameOffsets_[1] - frameOffsets_[0];
721
722 if (thickness_ < 0)
723 {
724 thickness_ = -thickness_;
725 }
726 }
727 }
728 }
729
730 Data(const Orthanc::DicomMap& dicom) :
731 imageInformation_(dicom)
732 {
733 if (imageInformation_.GetNumberOfFrames() <= 0)
734 {
735 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
736 }
737
738 if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
739 !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) ||
740 !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
741 {
742 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
743 }
744
745 std::string s;
746 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
747 {
748 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
749 }
750 else
751 {
752 sopClassUid_ = StringToSopClassUid(s);
753 }
754
755 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
756 {
757 thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
758 }
759
760 GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
761
762 std::string position, orientation;
763 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
764 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
765 {
766 geometry_ = CoordinateSystem3D(position, orientation);
767 }
768
769 if (sopClassUid_ == SopClassUid_RTDose)
770 {
771 ComputeDoseOffsets(dicom);
772 }
773
774 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
775 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
776
777 double doseGridScaling;
778
779 if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
780 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
781 {
782 hasRescale_ = true;
783 }
784 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
785 {
786 hasRescale_ = true;
787 rescaleIntercept_ = 0;
788 rescaleSlope_ = doseGridScaling;
789 }
790 else
791 {
792 hasRescale_ = false;
793 }
794
795 Vector c, w;
796 if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
797 LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
798 c.size() > 0 &&
799 w.size() > 0)
800 {
801 hasDefaultWindowing_ = true;
802 defaultWindowingCenter_ = static_cast<float>(c[0]);
803 defaultWindowingWidth_ = static_cast<float>(w[0]);
804 }
805 else
806 {
807 hasDefaultWindowing_ = false;
808 }
809
810 if (sopClassUid_ == SopClassUid_RTDose)
811 {
812 switch (imageInformation_.GetBitsStored())
813 {
814 case 16:
815 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
816 break;
817
818 case 32:
819 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
820 break;
821
822 default:
823 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
824 }
825 }
826 else if (isColor_)
827 {
828 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
829 }
830 else if (imageInformation_.IsSigned())
831 {
832 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
833 }
834 else
835 {
836 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
837 }
838 }
839
840 CoordinateSystem3D GetFrameGeometry(unsigned int frame) const
841 {
842 if (frame == 0)
843 {
844 return geometry_;
845 }
846 else if (frame >= imageInformation_.GetNumberOfFrames())
847 {
848 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
849 }
850 else if (sopClassUid_ == SopClassUid_RTDose)
851 {
852 if (frame >= frameOffsets_.size())
853 {
854 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
855 }
856
857 return CoordinateSystem3D(
858 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
859 geometry_.GetAxisX(),
860 geometry_.GetAxisY());
861 }
862 else
863 {
864 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
865 }
866 }
867
868 // TODO - Is this necessary?
869 bool FrameContainsPlane(unsigned int frame,
870 const CoordinateSystem3D& plane) const
871 {
872 if (frame >= imageInformation_.GetNumberOfFrames())
873 {
874 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
875 }
876
877 CoordinateSystem3D tmp = geometry_;
878
879 if (frame != 0)
880 {
881 tmp = GetFrameGeometry(frame);
882 }
883
884 double distance;
885
886 return (CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
887 distance <= thickness_ / 2.0);
888 }
889
890
891 void ApplyRescale(Orthanc::ImageAccessor& image,
892 bool useDouble) const
893 {
894 if (image.GetFormat() != Orthanc::PixelFormat_Float32)
895 {
896 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
897 }
898
899 if (hasRescale_)
900 {
901 const unsigned int width = image.GetWidth();
902 const unsigned int height = image.GetHeight();
903
904 for (unsigned int y = 0; y < height; y++)
905 {
906 float* p = reinterpret_cast<float*>(image.GetRow(y));
907
908 if (useDouble)
909 {
910 // Slower, accurate implementation using double
911 for (unsigned int x = 0; x < width; x++, p++)
912 {
913 double value = static_cast<double>(*p);
914 *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
915 }
916 }
917 else
918 {
919 // Fast, approximate implementation using float
920 for (unsigned int x = 0; x < width; x++, p++)
921 {
922 *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
923 }
924 }
925 }
926 }
927 }
928 };
929
930
931 Data data_;
932
933
934 public:
935 DicomInstanceParameters(const DicomInstanceParameters& other) :
936 data_(other.data_)
937 {
938 }
939
940 DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
941 data_(dicom)
942 {
943 }
944
945 void SetOrthancInstanceIdentifier(const std::string& id)
946 {
947 data_.orthancInstanceId_ = id;
948 }
949
950 const std::string& GetOrthancInstanceIdentifier() const
951 {
952 return data_.orthancInstanceId_;
953 }
954
955 const Orthanc::DicomImageInformation& GetImageInformation() const
956 {
957 return data_.imageInformation_;
958 }
959
960 const std::string& GetStudyInstanceUid() const
961 {
962 return data_.studyInstanceUid_;
963 }
964
965 const std::string& GetSeriesInstanceUid() const
966 {
967 return data_.seriesInstanceUid_;
968 }
969
970 const std::string& GetSopInstanceUid() const
971 {
972 return data_.sopInstanceUid_;
973 }
974
975 SopClassUid GetSopClassUid() const
976 {
977 return data_.sopClassUid_;
978 }
979
980 double GetThickness() const
981 {
982 return data_.thickness_;
983 }
984
985 double GetPixelSpacingX() const
986 {
987 return data_.pixelSpacingX_;
988 }
989
990 double GetPixelSpacingY() const
991 {
992 return data_.pixelSpacingY_;
993 }
994
995 const CoordinateSystem3D& GetGeometry() const
996 {
997 return data_.geometry_;
998 }
999
1000 CoordinateSystem3D GetFrameGeometry(unsigned int frame) const
1001 {
1002 return data_.GetFrameGeometry(frame);
1003 }
1004
1005 // TODO - Is this necessary?
1006 bool FrameContainsPlane(unsigned int frame,
1007 const CoordinateSystem3D& plane) const
1008 {
1009 return data_.FrameContainsPlane(frame, plane);
1010 }
1011
1012 bool IsColor() const
1013 {
1014 return data_.isColor_;
1015 }
1016
1017 bool HasRescale() const
1018 {
1019 return data_.hasRescale_;
1020 }
1021
1022 double GetRescaleIntercept() const
1023 {
1024 if (data_.hasRescale_)
1025 {
1026 return data_.rescaleIntercept_;
1027 }
1028 else
1029 {
1030 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1031 }
1032 }
1033
1034 double GetRescaleSlope() const
1035 {
1036 if (data_.hasRescale_)
1037 {
1038 return data_.rescaleSlope_;
1039 }
1040 else
1041 {
1042 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1043 }
1044 }
1045
1046 bool HasDefaultWindowing() const
1047 {
1048 return data_.hasDefaultWindowing_;
1049 }
1050
1051 float GetDefaultWindowingCenter() const
1052 {
1053 if (data_.hasDefaultWindowing_)
1054 {
1055 return data_.defaultWindowingCenter_;
1056 }
1057 else
1058 {
1059 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1060 }
1061 }
1062
1063 float GetDefaultWindowingWidth() const
1064 {
1065 if (data_.hasDefaultWindowing_)
1066 {
1067 return data_.defaultWindowingWidth_;
1068 }
1069 else
1070 {
1071 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1072 }
1073 }
1074
1075 Orthanc::PixelFormat GetExpectedPixelFormat() const
1076 {
1077 return data_.expectedPixelFormat_;
1078 }
1079
1080
1081 TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const
1082 {
1083 assert(sizeof(float) == 4);
1084
1085 Orthanc::PixelFormat sourceFormat = source.GetFormat();
1086
1087 if (sourceFormat != GetExpectedPixelFormat())
1088 {
1089 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
1090 }
1091
1092 if (sourceFormat == Orthanc::PixelFormat_RGB24)
1093 {
1094 // This is the case of a color image. No conversion has to be done.
1095 return new ColorTextureSceneLayer(source);
1096 }
1097 else
1098 {
1099 if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
1100 sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
1101 sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
1102 {
1103 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1104 }
1105
1106 std::auto_ptr<FloatTextureSceneLayer> texture;
1107
1108 {
1109 // This is the case of a grayscale frame. Convert it to Float32.
1110 std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32,
1111 source.GetWidth(),
1112 source.GetHeight(),
1113 false));
1114 Orthanc::ImageProcessing::Convert(*converted, source);
1115
1116 // Correct rescale slope/intercept if need be
1117 data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
1118
1119 texture.reset(new FloatTextureSceneLayer(*converted));
1120 }
1121
1122 if (data_.hasDefaultWindowing_)
1123 {
1124 texture->SetCustomWindowing(data_.defaultWindowingCenter_,
1125 data_.defaultWindowingWidth_);
1126 }
1127
1128 return texture.release();
1129 }
1130 }
1131 };
1132
1133
1134 class DicomVolumeImage : public boost::noncopyable
1135 {
1136 private:
1137 std::auto_ptr<ImageBuffer3D> image_;
1138 std::auto_ptr<VolumeImageGeometry> geometry_;
1139 std::vector<DicomInstanceParameters*> slices_;
1140 uint64_t revision_;
1141 std::vector<uint64_t> slicesRevision_;
1142 std::vector<unsigned int> slicesQuality_;
1143 76
1144 void CheckSlice(size_t index, 77 void CheckSlice(size_t index,
1145 const DicomInstanceParameters& reference) const 78 const DicomInstanceParameters& reference) const
1146 { 79 {
1147 const DicomInstanceParameters& slice = *slices_[index]; 80 const DicomInstanceParameters& slice = *slices_[index];
1378 } 311 }
1379 }; 312 };
1380 313
1381 314
1382 315
1383 class IDicomVolumeSource : public boost::noncopyable 316 class IDicomVolumeImageSource : public boost::noncopyable
1384 { 317 {
1385 public: 318 public:
1386 virtual ~IDicomVolumeSource() 319 virtual ~IDicomVolumeImageSource()
1387 { 320 {
1388 } 321 }
1389 322
1390 virtual const DicomVolumeImage& GetVolume() const = 0; 323 virtual const DicomVolumeImage& GetVolume() const = 0;
1391 324
1394 327
1395 328
1396 329
1397 class VolumeSeriesOrthancLoader : 330 class VolumeSeriesOrthancLoader :
1398 public IObserver, 331 public IObserver,
1399 public IDicomVolumeSource 332 public IDicomVolumeImageSource
1400 { 333 {
1401 private: 334 private:
1402 static const unsigned int LOW_QUALITY = 0; 335 static const unsigned int LOW_QUALITY = 0;
1403 static const unsigned int MIDDLE_QUALITY = 1; 336 static const unsigned int MIDDLE_QUALITY = 1;
1404 static const unsigned int BEST_QUALITY = 2; 337 static const unsigned int BEST_QUALITY = 2;
1685 618
1686 619
1687 class DicomVolumeMPRSlicer : public IVolumeSlicer 620 class DicomVolumeMPRSlicer : public IVolumeSlicer
1688 { 621 {
1689 private: 622 private:
1690 bool linearInterpolation_; 623 bool linearInterpolation_;
1691 Scene2D& scene_; 624 Scene2D& scene_;
1692 int layerDepth_; 625 int layerDepth_;
1693 IDicomVolumeSource& source_; 626 IDicomVolumeImageSource& source_;
1694 bool first_; 627 bool first_;
1695 VolumeProjection lastProjection_; 628 VolumeProjection lastProjection_;
1696 unsigned int lastSliceIndex_; 629 unsigned int lastSliceIndex_;
1697 uint64_t lastSliceRevision_; 630 uint64_t lastSliceRevision_;
1698 631
1699 public: 632 public:
1700 DicomVolumeMPRSlicer(Scene2D& scene, 633 DicomVolumeMPRSlicer(Scene2D& scene,
1701 int layerDepth, 634 int layerDepth,
1702 IDicomVolumeSource& source) : 635 IDicomVolumeImageSource& source) :
1703 linearInterpolation_(false), 636 linearInterpolation_(false),
1704 scene_(scene), 637 scene_(scene),
1705 layerDepth_(layerDepth), 638 layerDepth_(layerDepth),
1706 source_(source), 639 source_(source),
1707 first_(true) 640 first_(true)
1812 745
1813 746
1814 class ThreadedOracle : public IOracle 747 class ThreadedOracle : public IOracle
1815 { 748 {
1816 private: 749 private:
750 typedef std::map<std::string, std::string> HttpHeaders;
751
1817 class Item : public Orthanc::IDynamicObject 752 class Item : public Orthanc::IDynamicObject
1818 { 753 {
1819 private: 754 private:
1820 const IObserver& receiver_; 755 const IObserver& receiver_;
1821 std::auto_ptr<IOracleCommand> command_; 756 std::auto_ptr<IOracleCommand> command_;