comparison Samples/Sdl/Loader.cpp @ 754:92c400a09f1b

Merge from default
author Benjamin Golinvaux <bgo@osimis.io>
date Wed, 22 May 2019 16:13:46 +0200
parents ab236bb5dbc7
children f7c236894c1a
comparison
equal deleted inserted replaced
753:a386bbc955dc 754:92c400a09f1b
16 * 16 *
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
22 #include "../../Framework/Toolbox/DicomInstanceParameters.h"
23 #include "../../Framework/Oracle/ThreadedOracle.h"
24 #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h"
25 #include "../../Framework/Oracle/GetOrthancImageCommand.h"
26 #include "../../Framework/Oracle/OrthancRestApiCommand.h"
27 #include "../../Framework/Oracle/SleepOracleCommand.h"
28 #include "../../Framework/Oracle/OracleCommandExceptionMessage.h"
29 #include "../../Framework/Messages/IMessageEmitter.h"
30 #include "../../Framework/Oracle/OracleCommandWithPayload.h"
31 #include "../../Framework/Oracle/IOracle.h"
32
21 // From Stone 33 // From Stone
22 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" 34 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h"
23 #include "../../Framework/Loaders/BasicFetchingStrategy.h" 35 #include "../../Framework/Loaders/BasicFetchingStrategy.h"
24 #include "../../Framework/Messages/ICallable.h"
25 #include "../../Framework/Messages/IMessage.h"
26 #include "../../Framework/Messages/IObservable.h"
27 #include "../../Framework/Messages/MessageBroker.h"
28 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
29 #include "../../Framework/Scene2D/FloatTextureSceneLayer.h"
30 #include "../../Framework/Scene2D/Scene2D.h" 36 #include "../../Framework/Scene2D/Scene2D.h"
31 #include "../../Framework/StoneInitialization.h" 37 #include "../../Framework/StoneInitialization.h"
32 #include "../../Framework/Toolbox/GeometryToolbox.h" 38 #include "../../Framework/Toolbox/GeometryToolbox.h"
33 #include "../../Framework/Toolbox/SlicesSorter.h" 39 #include "../../Framework/Toolbox/SlicesSorter.h"
34 #include "../../Framework/Volumes/ImageBuffer3D.h" 40 #include "../../Framework/Volumes/ImageBuffer3D.h"
41 #include "../../Framework/Volumes/VolumeImageGeometry.h"
35 42
36 // From Orthanc framework 43 // From Orthanc framework
37 #include <Core/Compression/GzipCompressor.h> 44 #include <Core/Compression/GzipCompressor.h>
38 #include <Core/Compression/ZlibCompressor.h> 45 #include <Core/Compression/ZlibCompressor.h>
39 #include <Core/DicomFormat/DicomArray.h> 46 #include <Core/DicomFormat/DicomArray.h>
40 #include <Core/DicomFormat/DicomImageInformation.h> 47 #include <Core/DicomFormat/DicomImageInformation.h>
41 #include <Core/HttpClient.h> 48 #include <Core/HttpClient.h>
42 #include <Core/IDynamicObject.h> 49 #include <Core/IDynamicObject.h>
43 #include <Core/Images/Image.h>
44 #include <Core/Images/ImageProcessing.h> 50 #include <Core/Images/ImageProcessing.h>
45 #include <Core/Images/JpegReader.h>
46 #include <Core/Images/PamReader.h>
47 #include <Core/Images/PngReader.h>
48 #include <Core/Images/PngWriter.h>
49 #include <Core/Logging.h> 51 #include <Core/Logging.h>
50 #include <Core/MultiThreading/SharedMessageQueue.h> 52 #include <Core/MultiThreading/SharedMessageQueue.h>
51 #include <Core/OrthancException.h> 53 #include <Core/OrthancException.h>
52 #include <Core/SystemToolbox.h> 54 #include <Core/SystemToolbox.h>
53 #include <Core/Toolbox.h> 55 #include <Core/Toolbox.h>
59 #include <list> 61 #include <list>
60 #include <stdio.h> 62 #include <stdio.h>
61 63
62 64
63 65
64 namespace Refactoring 66 namespace OrthancStone
65 { 67 {
66 class IOracleCommand : public boost::noncopyable 68 class DicomVolumeImage : public boost::noncopyable
67 {
68 public:
69 enum Type
70 {
71 Type_OrthancRestApi,
72 Type_GetOrthancImage,
73 Type_GetOrthancWebViewerJpeg
74 };
75
76 virtual ~IOracleCommand()
77 {
78 }
79
80 virtual Type GetType() const = 0;
81 };
82
83
84 class IMessageEmitter : public boost::noncopyable
85 {
86 public:
87 virtual ~IMessageEmitter()
88 {
89 }
90
91 virtual void EmitMessage(const OrthancStone::IObserver& observer,
92 const OrthancStone::IMessage& message) = 0;
93 };
94
95
96 class IOracle : public boost::noncopyable
97 {
98 public:
99 virtual ~IOracle()
100 {
101 }
102
103 virtual void Schedule(const OrthancStone::IObserver& receiver,
104 IOracleCommand* command) = 0; // Takes ownership
105 };
106
107
108
109 class IVolumeSlicer : public boost::noncopyable
110 {
111 public:
112 virtual ~IVolumeSlicer()
113 {
114 }
115
116 virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) = 0;
117 };
118
119
120
121 class OracleCommandWithPayload : public IOracleCommand
122 { 69 {
123 private: 70 private:
124 std::auto_ptr<Orthanc::IDynamicObject> payload_; 71 std::auto_ptr<ImageBuffer3D> image_;
125 72 std::auto_ptr<VolumeImageGeometry> geometry_;
126 public: 73 std::vector<DicomInstanceParameters*> slices_;
127 void SetPayload(Orthanc::IDynamicObject* payload) 74 uint64_t revision_;
128 { 75 std::vector<uint64_t> slicesRevision_;
129 if (payload == NULL) 76 std::vector<unsigned int> slicesQuality_;
130 {
131 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
132 }
133 else
134 {
135 payload_.reset(payload);
136 }
137 }
138
139 bool HasPayload() const
140 {
141 return (payload_.get() != NULL);
142 }
143
144 const Orthanc::IDynamicObject& GetPayload() const
145 {
146 if (HasPayload())
147 {
148 return *payload_;
149 }
150 else
151 {
152 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
153 }
154 }
155 };
156
157
158
159 class OracleCommandExceptionMessage : public OrthancStone::IMessage
160 {
161 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
162
163 private:
164 const IOracleCommand& command_;
165 Orthanc::OrthancException exception_;
166
167 public:
168 OracleCommandExceptionMessage(const IOracleCommand& command,
169 const Orthanc::OrthancException& exception) :
170 command_(command),
171 exception_(exception)
172 {
173 }
174
175 OracleCommandExceptionMessage(const IOracleCommand& command,
176 const Orthanc::ErrorCode& error) :
177 command_(command),
178 exception_(error)
179 {
180 }
181
182 const IOracleCommand& GetCommand() const
183 {
184 return command_;
185 }
186
187 const Orthanc::OrthancException& GetException() const
188 {
189 return exception_;
190 }
191 };
192
193
194 typedef std::map<std::string, std::string> HttpHeaders;
195
196 class OrthancRestApiCommand : public OracleCommandWithPayload
197 {
198 public:
199 class SuccessMessage : public OrthancStone::OriginMessage<OrthancRestApiCommand>
200 {
201 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
202
203 private:
204 HttpHeaders headers_;
205 std::string answer_;
206
207 public:
208 SuccessMessage(const OrthancRestApiCommand& command,
209 const HttpHeaders& answerHeaders,
210 std::string& answer /* will be swapped to avoid a memcpy() */) :
211 OriginMessage(command),
212 headers_(answerHeaders),
213 answer_(answer)
214 {
215 }
216
217 const std::string& GetAnswer() const
218 {
219 return answer_;
220 }
221
222 void ParseJsonBody(Json::Value& target) const
223 {
224 Json::Reader reader;
225 if (!reader.parse(answer_, target))
226 {
227 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
228 }
229 }
230
231 const HttpHeaders& GetAnswerHeaders() const
232 {
233 return headers_;
234 }
235 };
236
237
238 private:
239 Orthanc::HttpMethod method_;
240 std::string uri_;
241 std::string body_;
242 HttpHeaders headers_;
243 unsigned int timeout_;
244
245 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_;
246 std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_;
247
248 public:
249 OrthancRestApiCommand() :
250 method_(Orthanc::HttpMethod_Get),
251 uri_("/"),
252 timeout_(10)
253 {
254 }
255
256 virtual Type GetType() const
257 {
258 return Type_OrthancRestApi;
259 }
260
261 void SetMethod(Orthanc::HttpMethod method)
262 {
263 method_ = method;
264 }
265
266 void SetUri(const std::string& uri)
267 {
268 uri_ = uri;
269 }
270
271 void SetBody(const std::string& body)
272 {
273 body_ = body;
274 }
275
276 void SetBody(const Json::Value& json)
277 {
278 Json::FastWriter writer;
279 body_ = writer.write(json);
280 }
281
282 void SetHttpHeaders(const HttpHeaders& headers)
283 {
284 headers_ = headers;
285 }
286
287 void SetHttpHeader(const std::string& key,
288 const std::string& value)
289 {
290 headers_[key] = value;
291 }
292
293 Orthanc::HttpMethod GetMethod() const
294 {
295 return method_;
296 }
297
298 const std::string& GetUri() const
299 {
300 return uri_;
301 }
302
303 const std::string& GetBody() const
304 {
305 if (method_ == Orthanc::HttpMethod_Post ||
306 method_ == Orthanc::HttpMethod_Put)
307 {
308 return body_;
309 }
310 else
311 {
312 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
313 }
314 }
315
316 const HttpHeaders& GetHttpHeaders() const
317 {
318 return headers_;
319 }
320
321 void SetTimeout(unsigned int seconds)
322 {
323 timeout_ = seconds;
324 }
325
326 unsigned int GetTimeout() const
327 {
328 return timeout_;
329 }
330 };
331
332
333
334
335 class GetOrthancImageCommand : public OracleCommandWithPayload
336 {
337 public:
338 class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancImageCommand>
339 {
340 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
341
342 private:
343 std::auto_ptr<Orthanc::ImageAccessor> image_;
344 Orthanc::MimeType mime_;
345
346 public:
347 SuccessMessage(const GetOrthancImageCommand& command,
348 Orthanc::ImageAccessor* image, // Takes ownership
349 Orthanc::MimeType mime) :
350 OriginMessage(command),
351 image_(image),
352 mime_(mime)
353 {
354 if (image == NULL)
355 {
356 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
357 }
358 }
359
360 const Orthanc::ImageAccessor& GetImage() const
361 {
362 return *image_;
363 }
364
365 Orthanc::MimeType GetMimeType() const
366 {
367 return mime_;
368 }
369 };
370
371
372 private:
373 std::string uri_;
374 HttpHeaders headers_;
375 unsigned int timeout_;
376 bool hasExpectedFormat_;
377 Orthanc::PixelFormat expectedFormat_;
378
379 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_;
380 std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_;
381
382 public:
383 GetOrthancImageCommand() :
384 uri_("/"),
385 timeout_(10),
386 hasExpectedFormat_(false)
387 {
388 }
389
390 virtual Type GetType() const
391 {
392 return Type_GetOrthancImage;
393 }
394
395 void SetExpectedPixelFormat(Orthanc::PixelFormat format)
396 {
397 hasExpectedFormat_ = true;
398 expectedFormat_ = format;
399 }
400
401 void SetUri(const std::string& uri)
402 {
403 uri_ = uri;
404 }
405
406 void SetInstanceUri(const std::string& instance,
407 Orthanc::PixelFormat pixelFormat)
408 {
409 uri_ = "/instances/" + instance;
410
411 switch (pixelFormat)
412 {
413 case Orthanc::PixelFormat_RGB24:
414 uri_ += "/preview";
415 break;
416
417 case Orthanc::PixelFormat_Grayscale16:
418 uri_ += "/image-uint16";
419 break;
420
421 case Orthanc::PixelFormat_SignedGrayscale16:
422 uri_ += "/image-int16";
423 break;
424
425 default:
426 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
427 }
428 }
429
430 void SetHttpHeader(const std::string& key,
431 const std::string& value)
432 {
433 headers_[key] = value;
434 }
435
436 const std::string& GetUri() const
437 {
438 return uri_;
439 }
440
441 const HttpHeaders& GetHttpHeaders() const
442 {
443 return headers_;
444 }
445
446 void SetTimeout(unsigned int seconds)
447 {
448 timeout_ = seconds;
449 }
450
451 unsigned int GetTimeout() const
452 {
453 return timeout_;
454 }
455
456 void ProcessHttpAnswer(IMessageEmitter& emitter,
457 const OrthancStone::IObserver& receiver,
458 const std::string& answer,
459 const HttpHeaders& answerHeaders) const
460 {
461 Orthanc::MimeType contentType = Orthanc::MimeType_Binary;
462
463 for (HttpHeaders::const_iterator it = answerHeaders.begin();
464 it != answerHeaders.end(); ++it)
465 {
466 std::string s;
467 Orthanc::Toolbox::ToLowerCase(s, it->first);
468
469 if (s == "content-type")
470 {
471 contentType = Orthanc::StringToMimeType(it->second);
472 break;
473 }
474 }
475
476 std::auto_ptr<Orthanc::ImageAccessor> image;
477
478 switch (contentType)
479 {
480 case Orthanc::MimeType_Png:
481 {
482 image.reset(new Orthanc::PngReader);
483 dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer);
484 break;
485 }
486
487 case Orthanc::MimeType_Pam:
488 {
489 image.reset(new Orthanc::PamReader);
490 dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer);
491 break;
492 }
493
494 case Orthanc::MimeType_Jpeg:
495 {
496 image.reset(new Orthanc::JpegReader);
497 dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer);
498 break;
499 }
500
501 default:
502 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
503 "Unsupported HTTP Content-Type for an image: " +
504 std::string(Orthanc::EnumerationToString(contentType)));
505 }
506
507 if (hasExpectedFormat_)
508 {
509 if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 &&
510 image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
511 {
512 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
513 }
514
515 if (expectedFormat_ != image->GetFormat())
516 {
517 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
518 }
519 }
520
521 SuccessMessage message(*this, image.release(), contentType);
522 emitter.EmitMessage(receiver, message);
523 }
524 };
525
526
527
528 class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload
529 {
530 public:
531 class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancWebViewerJpegCommand>
532 {
533 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__);
534
535 private:
536 std::auto_ptr<Orthanc::ImageAccessor> image_;
537
538 public:
539 SuccessMessage(const GetOrthancWebViewerJpegCommand& command,
540 Orthanc::ImageAccessor* image) : // Takes ownership
541 OriginMessage(command),
542 image_(image)
543 {
544 if (image == NULL)
545 {
546 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
547 }
548 }
549
550 const Orthanc::ImageAccessor& GetImage() const
551 {
552 return *image_;
553 }
554 };
555
556 private:
557 std::string instanceId_;
558 unsigned int frame_;
559 unsigned int quality_;
560 HttpHeaders headers_;
561 unsigned int timeout_;
562 Orthanc::PixelFormat expectedFormat_;
563
564 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_;
565 std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_;
566
567 public:
568 GetOrthancWebViewerJpegCommand() :
569 frame_(0),
570 quality_(95),
571 timeout_(10),
572 expectedFormat_(Orthanc::PixelFormat_Grayscale8)
573 {
574 }
575
576 virtual Type GetType() const
577 {
578 return Type_GetOrthancWebViewerJpeg;
579 }
580
581 void SetExpectedPixelFormat(Orthanc::PixelFormat format)
582 {
583 expectedFormat_ = format;
584 }
585
586 void SetInstance(const std::string& instanceId)
587 {
588 instanceId_ = instanceId;
589 }
590
591 void SetFrame(unsigned int frame)
592 {
593 frame_ = frame;
594 }
595
596 void SetQuality(unsigned int quality)
597 {
598 if (quality <= 0 ||
599 quality > 100)
600 {
601 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
602 }
603 else
604 {
605 quality_ = quality;
606 }
607 }
608
609 void SetHttpHeader(const std::string& key,
610 const std::string& value)
611 {
612 headers_[key] = value;
613 }
614
615 Orthanc::PixelFormat GetExpectedPixelFormat() const
616 {
617 return expectedFormat_;
618 }
619
620 const std::string& GetInstanceId() const
621 {
622 return instanceId_;
623 }
624
625 unsigned int GetFrame() const
626 {
627 return frame_;
628 }
629
630 unsigned int GetQuality() const
631 {
632 return quality_;
633 }
634
635 const HttpHeaders& GetHttpHeaders() const
636 {
637 return headers_;
638 }
639
640 void SetTimeout(unsigned int seconds)
641 {
642 timeout_ = seconds;
643 }
644
645 unsigned int GetTimeout() const
646 {
647 return timeout_;
648 }
649
650 std::string GetUri() const
651 {
652 return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) +
653 "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_));
654 }
655
656 void ProcessHttpAnswer(IMessageEmitter& emitter,
657 const OrthancStone::IObserver& receiver,
658 const std::string& answer) const
659 {
660 // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()"
661
662 Json::Value encoded;
663
664 {
665 Json::Reader reader;
666 if (!reader.parse(answer, encoded))
667 {
668 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
669 }
670 }
671
672 if (encoded.type() != Json::objectValue ||
673 !encoded.isMember("Orthanc") ||
674 encoded["Orthanc"].type() != Json::objectValue)
675 {
676 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
677 }
678
679 const Json::Value& info = encoded["Orthanc"];
680 if (!info.isMember("PixelData") ||
681 !info.isMember("Stretched") ||
682 !info.isMember("Compression") ||
683 info["Compression"].type() != Json::stringValue ||
684 info["PixelData"].type() != Json::stringValue ||
685 info["Stretched"].type() != Json::booleanValue ||
686 info["Compression"].asString() != "Jpeg")
687 {
688 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
689 }
690
691 bool isSigned = false;
692 bool isStretched = info["Stretched"].asBool();
693
694 if (info.isMember("IsSigned"))
695 {
696 if (info["IsSigned"].type() != Json::booleanValue)
697 {
698 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
699 }
700 else
701 {
702 isSigned = info["IsSigned"].asBool();
703 }
704 }
705
706 std::auto_ptr<Orthanc::ImageAccessor> reader;
707
708 {
709 std::string jpeg;
710 Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
711
712 reader.reset(new Orthanc::JpegReader);
713 dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
714 }
715
716 if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image
717 {
718 if (expectedFormat_ != Orthanc::PixelFormat_RGB24)
719 {
720 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
721 }
722
723 if (isSigned || isStretched)
724 {
725 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
726 }
727 else
728 {
729 SuccessMessage message(*this, reader.release());
730 emitter.EmitMessage(receiver, message);
731 return;
732 }
733 }
734
735 if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
736 {
737 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
738 }
739
740 if (!isStretched)
741 {
742 if (expectedFormat_ != reader->GetFormat())
743 {
744 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
745 }
746 else
747 {
748 SuccessMessage message(*this, reader.release());
749 emitter.EmitMessage(receiver, message);
750 return;
751 }
752 }
753
754 int32_t stretchLow = 0;
755 int32_t stretchHigh = 0;
756
757 if (!info.isMember("StretchLow") ||
758 !info.isMember("StretchHigh") ||
759 info["StretchLow"].type() != Json::intValue ||
760 info["StretchHigh"].type() != Json::intValue)
761 {
762 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
763 }
764
765 stretchLow = info["StretchLow"].asInt();
766 stretchHigh = info["StretchHigh"].asInt();
767
768 if (stretchLow < -32768 ||
769 stretchHigh > 65535 ||
770 (stretchLow < 0 && stretchHigh > 32767))
771 {
772 // This range cannot be represented with a uint16_t or an int16_t
773 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
774 }
775
776 // Decode a grayscale JPEG 8bpp image coming from the Web viewer
777 std::auto_ptr<Orthanc::ImageAccessor> image
778 (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false));
779
780 Orthanc::ImageProcessing::Convert(*image, *reader);
781 reader.reset();
782
783 float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
784
785 if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling))
786 {
787 float offset = static_cast<float>(stretchLow) / scaling;
788 Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
789 }
790
791 SuccessMessage message(*this, image.release());
792 emitter.EmitMessage(receiver, message);
793 }
794 };
795
796
797
798
799
800 class DicomInstanceParameters :
801 public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */
802 {
803 private:
804 struct Data // Struct to ease the copy constructor
805 {
806 std::string orthancInstanceId_;
807 std::string studyInstanceUid_;
808 std::string seriesInstanceUid_;
809 std::string sopInstanceUid_;
810 Orthanc::DicomImageInformation imageInformation_;
811 OrthancStone::SopClassUid sopClassUid_;
812 double thickness_;
813 double pixelSpacingX_;
814 double pixelSpacingY_;
815 OrthancStone::CoordinateSystem3D geometry_;
816 OrthancStone::Vector frameOffsets_;
817 bool isColor_;
818 bool hasRescale_;
819 double rescaleIntercept_;
820 double rescaleSlope_;
821 bool hasDefaultWindowing_;
822 float defaultWindowingCenter_;
823 float defaultWindowingWidth_;
824 Orthanc::PixelFormat expectedPixelFormat_;
825
826 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
827 {
828 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
829
830 {
831 std::string increment;
832
833 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
834 {
835 Orthanc::Toolbox::ToUpperCase(increment);
836 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag
837 {
838 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
839 return;
840 }
841 }
842 }
843
844 if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
845 frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
846 {
847 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
848 frameOffsets_.clear();
849 }
850 else
851 {
852 if (frameOffsets_.size() >= 2)
853 {
854 thickness_ = frameOffsets_[1] - frameOffsets_[0];
855
856 if (thickness_ < 0)
857 {
858 thickness_ = -thickness_;
859 }
860 }
861 }
862 }
863
864 Data(const Orthanc::DicomMap& dicom) :
865 imageInformation_(dicom)
866 {
867 if (imageInformation_.GetNumberOfFrames() <= 0)
868 {
869 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
870 }
871
872 if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
873 !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) ||
874 !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
875 {
876 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
877 }
878
879 std::string s;
880 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
881 {
882 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
883 }
884 else
885 {
886 sopClassUid_ = OrthancStone::StringToSopClassUid(s);
887 }
888
889 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
890 {
891 thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
892 }
893
894 OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
895
896 std::string position, orientation;
897 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
898 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
899 {
900 geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
901 }
902
903 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
904 {
905 ComputeDoseOffsets(dicom);
906 }
907
908 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
909 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
910
911 double doseGridScaling;
912
913 if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
914 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
915 {
916 hasRescale_ = true;
917 }
918 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
919 {
920 hasRescale_ = true;
921 rescaleIntercept_ = 0;
922 rescaleSlope_ = doseGridScaling;
923 }
924 else
925 {
926 hasRescale_ = false;
927 }
928
929 OrthancStone::Vector c, w;
930 if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
931 OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
932 c.size() > 0 &&
933 w.size() > 0)
934 {
935 hasDefaultWindowing_ = true;
936 defaultWindowingCenter_ = static_cast<float>(c[0]);
937 defaultWindowingWidth_ = static_cast<float>(w[0]);
938 }
939 else
940 {
941 hasDefaultWindowing_ = false;
942 }
943
944 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
945 {
946 switch (imageInformation_.GetBitsStored())
947 {
948 case 16:
949 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
950 break;
951
952 case 32:
953 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
954 break;
955
956 default:
957 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
958 }
959 }
960 else if (isColor_)
961 {
962 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
963 }
964 else if (imageInformation_.IsSigned())
965 {
966 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
967 }
968 else
969 {
970 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
971 }
972 }
973
974 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const
975 {
976 if (frame == 0)
977 {
978 return geometry_;
979 }
980 else if (frame >= imageInformation_.GetNumberOfFrames())
981 {
982 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
983 }
984 else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
985 {
986 if (frame >= frameOffsets_.size())
987 {
988 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
989 }
990
991 return OrthancStone::CoordinateSystem3D(
992 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
993 geometry_.GetAxisX(),
994 geometry_.GetAxisY());
995 }
996 else
997 {
998 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
999 }
1000 }
1001
1002 // TODO - Is this necessary?
1003 bool FrameContainsPlane(unsigned int frame,
1004 const OrthancStone::CoordinateSystem3D& plane) const
1005 {
1006 if (frame >= imageInformation_.GetNumberOfFrames())
1007 {
1008 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1009 }
1010
1011 OrthancStone::CoordinateSystem3D tmp = geometry_;
1012
1013 if (frame != 0)
1014 {
1015 tmp = GetFrameGeometry(frame);
1016 }
1017
1018 double distance;
1019
1020 return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
1021 distance <= thickness_ / 2.0);
1022 }
1023
1024
1025 void ApplyRescale(Orthanc::ImageAccessor& image,
1026 bool useDouble) const
1027 {
1028 if (image.GetFormat() != Orthanc::PixelFormat_Float32)
1029 {
1030 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
1031 }
1032
1033 if (hasRescale_)
1034 {
1035 const unsigned int width = image.GetWidth();
1036 const unsigned int height = image.GetHeight();
1037
1038 for (unsigned int y = 0; y < height; y++)
1039 {
1040 float* p = reinterpret_cast<float*>(image.GetRow(y));
1041
1042 if (useDouble)
1043 {
1044 // Slower, accurate implementation using double
1045 for (unsigned int x = 0; x < width; x++, p++)
1046 {
1047 double value = static_cast<double>(*p);
1048 *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
1049 }
1050 }
1051 else
1052 {
1053 // Fast, approximate implementation using float
1054 for (unsigned int x = 0; x < width; x++, p++)
1055 {
1056 *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
1057 }
1058 }
1059 }
1060 }
1061 }
1062 };
1063
1064
1065 Data data_;
1066
1067
1068 public:
1069 DicomInstanceParameters(const DicomInstanceParameters& other) :
1070 data_(other.data_)
1071 {
1072 }
1073
1074 DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
1075 data_(dicom)
1076 {
1077 }
1078
1079 void SetOrthancInstanceIdentifier(const std::string& id)
1080 {
1081 data_.orthancInstanceId_ = id;
1082 }
1083
1084 const std::string& GetOrthancInstanceIdentifier() const
1085 {
1086 return data_.orthancInstanceId_;
1087 }
1088
1089 const Orthanc::DicomImageInformation& GetImageInformation() const
1090 {
1091 return data_.imageInformation_;
1092 }
1093
1094 const std::string& GetStudyInstanceUid() const
1095 {
1096 return data_.studyInstanceUid_;
1097 }
1098
1099 const std::string& GetSeriesInstanceUid() const
1100 {
1101 return data_.seriesInstanceUid_;
1102 }
1103
1104 const std::string& GetSopInstanceUid() const
1105 {
1106 return data_.sopInstanceUid_;
1107 }
1108
1109 OrthancStone::SopClassUid GetSopClassUid() const
1110 {
1111 return data_.sopClassUid_;
1112 }
1113
1114 double GetThickness() const
1115 {
1116 return data_.thickness_;
1117 }
1118
1119 double GetPixelSpacingX() const
1120 {
1121 return data_.pixelSpacingX_;
1122 }
1123
1124 double GetPixelSpacingY() const
1125 {
1126 return data_.pixelSpacingY_;
1127 }
1128
1129 const OrthancStone::CoordinateSystem3D& GetGeometry() const
1130 {
1131 return data_.geometry_;
1132 }
1133
1134 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const
1135 {
1136 return data_.GetFrameGeometry(frame);
1137 }
1138
1139 // TODO - Is this necessary?
1140 bool FrameContainsPlane(unsigned int frame,
1141 const OrthancStone::CoordinateSystem3D& plane) const
1142 {
1143 return data_.FrameContainsPlane(frame, plane);
1144 }
1145
1146 bool IsColor() const
1147 {
1148 return data_.isColor_;
1149 }
1150
1151 bool HasRescale() const
1152 {
1153 return data_.hasRescale_;
1154 }
1155
1156 double GetRescaleIntercept() const
1157 {
1158 if (data_.hasRescale_)
1159 {
1160 return data_.rescaleIntercept_;
1161 }
1162 else
1163 {
1164 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1165 }
1166 }
1167
1168 double GetRescaleSlope() const
1169 {
1170 if (data_.hasRescale_)
1171 {
1172 return data_.rescaleSlope_;
1173 }
1174 else
1175 {
1176 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1177 }
1178 }
1179
1180 bool HasDefaultWindowing() const
1181 {
1182 return data_.hasDefaultWindowing_;
1183 }
1184
1185 float GetDefaultWindowingCenter() const
1186 {
1187 if (data_.hasDefaultWindowing_)
1188 {
1189 return data_.defaultWindowingCenter_;
1190 }
1191 else
1192 {
1193 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1194 }
1195 }
1196
1197 float GetDefaultWindowingWidth() const
1198 {
1199 if (data_.hasDefaultWindowing_)
1200 {
1201 return data_.defaultWindowingWidth_;
1202 }
1203 else
1204 {
1205 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1206 }
1207 }
1208
1209 Orthanc::PixelFormat GetExpectedPixelFormat() const
1210 {
1211 return data_.expectedPixelFormat_;
1212 }
1213
1214
1215 OrthancStone::TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const
1216 {
1217 assert(sizeof(float) == 4);
1218
1219 Orthanc::PixelFormat sourceFormat = source.GetFormat();
1220
1221 if (sourceFormat != GetExpectedPixelFormat())
1222 {
1223 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
1224 }
1225
1226 if (sourceFormat == Orthanc::PixelFormat_RGB24)
1227 {
1228 // This is the case of a color image. No conversion has to be done.
1229 return new OrthancStone::ColorTextureSceneLayer(source);
1230 }
1231 else
1232 {
1233 if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
1234 sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
1235 sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
1236 {
1237 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1238 }
1239
1240 std::auto_ptr<OrthancStone::FloatTextureSceneLayer> texture;
1241
1242 {
1243 // This is the case of a grayscale frame. Convert it to Float32.
1244 std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32,
1245 source.GetWidth(),
1246 source.GetHeight(),
1247 false));
1248 Orthanc::ImageProcessing::Convert(*converted, source);
1249
1250 // Correct rescale slope/intercept if need be
1251 data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
1252
1253 texture.reset(new OrthancStone::FloatTextureSceneLayer(*converted));
1254 }
1255
1256 if (data_.hasDefaultWindowing_)
1257 {
1258 texture->SetCustomWindowing(data_.defaultWindowingCenter_,
1259 data_.defaultWindowingWidth_);
1260 }
1261
1262 return texture.release();
1263 }
1264 }
1265 };
1266
1267
1268 class DicomVolumeImage : public boost::noncopyable
1269 {
1270 private:
1271 std::auto_ptr<OrthancStone::ImageBuffer3D> image_;
1272 std::vector<DicomInstanceParameters*> slices_;
1273 uint64_t revision_;
1274 std::vector<uint64_t> slicesRevision_;
1275 std::vector<unsigned int> slicesQuality_;
1276 77
1277 void CheckSlice(size_t index, 78 void CheckSlice(size_t index,
1278 const DicomInstanceParameters& reference) const 79 const DicomInstanceParameters& reference) const
1279 { 80 {
1280 const DicomInstanceParameters& slice = *slices_[index]; 81 const DicomInstanceParameters& slice = *slices_[index];
1281 82
1282 if (!OrthancStone::GeometryToolbox::IsParallel( 83 if (!GeometryToolbox::IsParallel(
1283 reference.GetGeometry().GetNormal(), 84 reference.GetGeometry().GetNormal(),
1284 slice.GetGeometry().GetNormal())) 85 slice.GetGeometry().GetNormal()))
1285 { 86 {
1286 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, 87 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
1287 "A slice in the volume image is not parallel to the others"); 88 "A slice in the volume image is not parallel to the others");
1298 { 99 {
1299 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize, 100 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
1300 "The width/height of slices are not constant in the volume image"); 101 "The width/height of slices are not constant in the volume image");
1301 } 102 }
1302 103
1303 if (!OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) || 104 if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
1304 !OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY())) 105 !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
1305 { 106 {
1306 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, 107 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
1307 "The pixel spacing of the slices change across the volume image"); 108 "The pixel spacing of the slices change across the volume image");
1308 } 109 }
1309 } 110 }
1334 135
1335 136
1336 void Clear() 137 void Clear()
1337 { 138 {
1338 image_.reset(); 139 image_.reset();
140 geometry_.reset();
1339 141
1340 for (size_t i = 0; i < slices_.size(); i++) 142 for (size_t i = 0; i < slices_.size(); i++)
1341 { 143 {
1342 assert(slices_[i] != NULL); 144 assert(slices_[i] != NULL);
1343 delete slices_[i]; 145 delete slices_[i];
1374 { 176 {
1375 Clear(); 177 Clear();
1376 } 178 }
1377 179
1378 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" 180 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
1379 void SetGeometry(OrthancStone::SlicesSorter& slices) 181 void SetGeometry(SlicesSorter& slices)
1380 { 182 {
1381 Clear(); 183 Clear();
1382 184
1383 if (!slices.Sort()) 185 if (!slices.Sort())
1384 { 186 {
1385 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, 187 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
1386 "Cannot sort the 3D slices of a DICOM series"); 188 "Cannot sort the 3D slices of a DICOM series");
1387 } 189 }
1388 190
191 geometry_.reset(new VolumeImageGeometry);
192
1389 if (slices.GetSlicesCount() == 0) 193 if (slices.GetSlicesCount() == 0)
1390 { 194 {
1391 // Empty volume 195 // Empty volume
1392 image_.reset(new OrthancStone::ImageBuffer3D(Orthanc::PixelFormat_Grayscale8, 0, 0, 0, 196 image_.reset(new ImageBuffer3D(Orthanc::PixelFormat_Grayscale8, 0, 0, 0,
1393 false /* don't compute range */)); 197 false /* don't compute range */));
1394 } 198 }
1395 else 199 else
1396 { 200 {
1397 slices_.reserve(slices.GetSlicesCount()); 201 slices_.reserve(slices.GetSlicesCount());
1398 slicesRevision_.resize(slices.GetSlicesCount(), 0); 202 slicesRevision_.resize(slices.GetSlicesCount(), 0);
1410 const double spacingZ = slices.ComputeSpacingBetweenSlices(); 214 const double spacingZ = slices.ComputeSpacingBetweenSlices();
1411 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; 215 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
1412 216
1413 const DicomInstanceParameters& parameters = *slices_[0]; 217 const DicomInstanceParameters& parameters = *slices_[0];
1414 218
1415 image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(), 219 image_.reset(new ImageBuffer3D(parameters.GetExpectedPixelFormat(),
1416 parameters.GetImageInformation().GetWidth(), 220 parameters.GetImageInformation().GetWidth(),
1417 parameters.GetImageInformation().GetHeight(), 221 parameters.GetImageInformation().GetHeight(),
1418 slices.GetSlicesCount(), false /* don't compute range */)); 222 slices.GetSlicesCount(), false /* don't compute range */));
1419 223
1420 image_->GetGeometry().SetAxialGeometry(slices.GetSliceGeometry(0)); 224 geometry_->SetSize(image_->GetWidth(), image_->GetHeight(), image_->GetDepth());
1421 image_->GetGeometry().SetVoxelDimensions(parameters.GetPixelSpacingX(), 225 geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
1422 parameters.GetPixelSpacingY(), spacingZ); 226 geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
227 parameters.GetPixelSpacingY(), spacingZ);
1423 } 228 }
1424 229
1425 image_->Clear(); 230 image_->Clear();
1426 231
1427 revision_++; 232 revision_++;
1432 return revision_; 237 return revision_;
1433 } 238 }
1434 239
1435 bool HasGeometry() const 240 bool HasGeometry() const
1436 { 241 {
1437 return (image_.get() != NULL); 242 return (image_.get() != NULL &&
1438 } 243 geometry_.get() != NULL);
1439 244 }
1440 const OrthancStone::ImageBuffer3D& GetImage() const 245
246 const ImageBuffer3D& GetImage() const
1441 { 247 {
1442 if (!HasGeometry()) 248 if (!HasGeometry())
1443 { 249 {
1444 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 250 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1445 } 251 }
1446 else 252 else
1447 { 253 {
1448 return *image_; 254 return *image_;
255 }
256 }
257
258 const VolumeImageGeometry& GetGeometry() const
259 {
260 if (!HasGeometry())
261 {
262 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
263 }
264 else
265 {
266 return *geometry_;
1449 } 267 }
1450 } 268 }
1451 269
1452 size_t GetSlicesCount() const 270 size_t GetSlicesCount() const
1453 { 271 {
1481 299
1482 // If a better image quality is already available, don't update the content 300 // If a better image quality is already available, don't update the content
1483 if (quality >= slicesQuality_[index]) 301 if (quality >= slicesQuality_[index])
1484 { 302 {
1485 { 303 {
1486 OrthancStone::ImageBuffer3D::SliceWriter writer 304 ImageBuffer3D::SliceWriter writer
1487 (*image_, OrthancStone::VolumeProjection_Axial, index); 305 (*image_, VolumeProjection_Axial, index);
1488 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image); 306 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
1489 } 307 }
1490 308
1491 revision_ ++; 309 revision_ ++;
1492 slicesRevision_[index] += 1; 310 slicesRevision_[index] += 1;
1494 } 312 }
1495 }; 313 };
1496 314
1497 315
1498 316
1499 class IDicomVolumeSource : public boost::noncopyable 317 class IDicomVolumeImageSource : public boost::noncopyable
1500 { 318 {
1501 public: 319 public:
1502 virtual ~IDicomVolumeSource() 320 virtual ~IDicomVolumeImageSource()
1503 { 321 {
1504 } 322 }
1505 323
1506 virtual const DicomVolumeImage& GetVolume() const = 0; 324 virtual const DicomVolumeImage& GetVolume() const = 0;
1507 325
1509 }; 327 };
1510 328
1511 329
1512 330
1513 class VolumeSeriesOrthancLoader : 331 class VolumeSeriesOrthancLoader :
1514 public OrthancStone::IObserver, 332 public IObserver,
1515 public IDicomVolumeSource 333 public IDicomVolumeImageSource
1516 { 334 {
1517 private: 335 private:
1518 static const unsigned int LOW_QUALITY = 0; 336 static const unsigned int LOW_QUALITY = 0;
1519 static const unsigned int MIDDLE_QUALITY = 1; 337 static const unsigned int MIDDLE_QUALITY = 1;
1520 static const unsigned int BEST_QUALITY = 2; 338 static const unsigned int BEST_QUALITY = 2;
1542 if (instance.empty()) 360 if (instance.empty())
1543 { 361 {
1544 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 362 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1545 } 363 }
1546 364
1547 std::auto_ptr<Refactoring::OracleCommandWithPayload> command; 365 std::auto_ptr<OracleCommandWithPayload> command;
1548 366
1549 if (quality == BEST_QUALITY) 367 if (quality == BEST_QUALITY)
1550 { 368 {
1551 std::auto_ptr<Refactoring::GetOrthancImageCommand> tmp( 369 std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand);
1552 new Refactoring::GetOrthancImageCommand);
1553 tmp->SetHttpHeader("Accept-Encoding", "gzip"); 370 tmp->SetHttpHeader("Accept-Encoding", "gzip");
1554 tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); 371 tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
1555 tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat()); 372 tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());
1556 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); 373 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
1557 command.reset(tmp.release()); 374 command.reset(tmp.release());
1558 } 375 }
1559 else 376 else
1560 { 377 {
1561 std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> tmp( 378 std::auto_ptr<GetOrthancWebViewerJpegCommand> tmp(new GetOrthancWebViewerJpegCommand);
1562 new Refactoring::GetOrthancWebViewerJpegCommand);
1563 tmp->SetHttpHeader("Accept-Encoding", "gzip"); 379 tmp->SetHttpHeader("Accept-Encoding", "gzip");
1564 tmp->SetInstance(instance); 380 tmp->SetInstance(instance);
1565 tmp->SetQuality((quality == 0 ? 50 : 90)); 381 tmp->SetQuality((quality == 0 ? 50 : 90));
1566 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); 382 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
1567 command.reset(tmp.release()); 383 command.reset(tmp.release());
1584 } 400 }
1585 401
1586 { 402 {
1587 Json::Value::Members instances = body.getMemberNames(); 403 Json::Value::Members instances = body.getMemberNames();
1588 404
1589 OrthancStone::SlicesSorter slices; 405 SlicesSorter slices;
1590 406
1591 for (size_t i = 0; i < instances.size(); i++) 407 for (size_t i = 0; i < instances.size(); i++)
1592 { 408 {
1593 Orthanc::DicomMap dicom; 409 Orthanc::DicomMap dicom;
1594 dicom.FromDicomAsJson(body[instances[i]]); 410 dicom.FromDicomAsJson(body[instances[i]]);
1595 411
1596 std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom)); 412 std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
1597 instance->SetOrthancInstanceIdentifier(instances[i]); 413 instance->SetOrthancInstanceIdentifier(instances[i]);
1598 414
1599 OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry(); 415 CoordinateSystem3D geometry = instance->GetGeometry();
1600 slices.AddSlice(geometry, instance.release()); 416 slices.AddSlice(geometry, instance.release());
1601 } 417 }
1602 418
1603 volume_.SetGeometry(slices); 419 volume_.SetGeometry(slices);
1604 } 420 }
1605 421
1606 if (volume_.GetSlicesCount() != 0) 422 if (volume_.GetSlicesCount() != 0)
1607 { 423 {
1608 strategy_.reset(new OrthancStone::BasicFetchingStrategy( 424 strategy_.reset(new BasicFetchingStrategy(
1609 new OrthancStone::BasicFetchingItemsSorter(volume_.GetSlicesCount()), BEST_QUALITY)); 425 new BasicFetchingItemsSorter(volume_.GetSlicesCount()), BEST_QUALITY));
1610 426
1611 for (unsigned int i = 0; i < 4; i++) // Schedule up to 4 simultaneous downloads (TODO - parameter) 427 for (unsigned int i = 0; i < 4; i++) // Schedule up to 4 simultaneous downloads (TODO - parameter)
1612 { 428 {
1613 ScheduleNextSliceDownload(); 429 ScheduleNextSliceDownload();
1614 } 430 }
1615 } 431 }
1616 } 432 }
1617 433
1618 434
1619 void LoadBestQualitySliceContent(const Refactoring::GetOrthancImageCommand::SuccessMessage& message) 435 void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message)
1620 { 436 {
1621 volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), 437 volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()),
1622 message.GetImage(), BEST_QUALITY); 438 message.GetImage(), BEST_QUALITY);
1623 439
1624 ScheduleNextSliceDownload(); 440 ScheduleNextSliceDownload();
1625 } 441 }
1626 442
1627 443
1628 void LoadJpegSliceContent(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message) 444 void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
1629 { 445 {
1630 unsigned int quality; 446 unsigned int quality;
1631 447
1632 switch (message.GetOrigin().GetQuality()) 448 switch (message.GetOrigin().GetQuality())
1633 { 449 {
1651 467
1652 IOracle& oracle_; 468 IOracle& oracle_;
1653 bool active_; 469 bool active_;
1654 DicomVolumeImage volume_; 470 DicomVolumeImage volume_;
1655 471
1656 std::auto_ptr<OrthancStone::IFetchingStrategy> strategy_; 472 std::auto_ptr<IFetchingStrategy> strategy_;
1657 473
1658 public: 474 public:
1659 VolumeSeriesOrthancLoader(IOracle& oracle, 475 VolumeSeriesOrthancLoader(IOracle& oracle,
1660 OrthancStone::IObservable& oracleObservable) : 476 IObservable& oracleObservable) :
1661 IObserver(oracleObservable.GetBroker()), 477 IObserver(oracleObservable.GetBroker()),
1662 oracle_(oracle), 478 oracle_(oracle),
1663 active_(false) 479 active_(false)
1664 { 480 {
1665 oracleObservable.RegisterObserverCallback( 481 oracleObservable.RegisterObserverCallback(
1666 new OrthancStone::Callable<VolumeSeriesOrthancLoader, OrthancRestApiCommand::SuccessMessage> 482 new Callable<VolumeSeriesOrthancLoader, OrthancRestApiCommand::SuccessMessage>
1667 (*this, &VolumeSeriesOrthancLoader::LoadGeometry)); 483 (*this, &VolumeSeriesOrthancLoader::LoadGeometry));
1668 484
1669 oracleObservable.RegisterObserverCallback( 485 oracleObservable.RegisterObserverCallback(
1670 new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancImageCommand::SuccessMessage> 486 new Callable<VolumeSeriesOrthancLoader, GetOrthancImageCommand::SuccessMessage>
1671 (*this, &VolumeSeriesOrthancLoader::LoadBestQualitySliceContent)); 487 (*this, &VolumeSeriesOrthancLoader::LoadBestQualitySliceContent));
1672 488
1673 oracleObservable.RegisterObserverCallback( 489 oracleObservable.RegisterObserverCallback(
1674 new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancWebViewerJpegCommand::SuccessMessage> 490 new Callable<VolumeSeriesOrthancLoader, GetOrthancWebViewerJpegCommand::SuccessMessage>
1675 (*this, &VolumeSeriesOrthancLoader::LoadJpegSliceContent)); 491 (*this, &VolumeSeriesOrthancLoader::LoadJpegSliceContent));
1676 } 492 }
1677 493
1678 void LoadSeries(const std::string& seriesId) 494 void LoadSeries(const std::string& seriesId)
1679 { 495 {
1682 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 498 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1683 } 499 }
1684 500
1685 active_ = true; 501 active_ = true;
1686 502
1687 std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); 503 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
1688 command->SetUri("/series/" + seriesId + "/instances-tags"); 504 command->SetUri("/series/" + seriesId + "/instances-tags");
1689 505
1690 oracle_.Schedule(*this, command.release()); 506 oracle_.Schedule(*this, command.release());
1691 } 507 }
1692 508
1726 // Tag "3004-000c" is "Grid Frame Offset Vector", which is 542 // Tag "3004-000c" is "Grid Frame Offset Vector", which is
1727 // mandatory to read RT DOSE, but is too long to be returned by default 543 // mandatory to read RT DOSE, but is too long to be returned by default
1728 544
1729 // TODO => Should be part of a second call if needed 545 // TODO => Should be part of a second call if needed
1730 546
1731 std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); 547 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
1732 command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c"); 548 command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
1733 command->SetPayload(new LoadInstanceGeometryHandler(*this)); 549 command->SetPayload(new LoadInstanceGeometryHandler(*this));
1734 550
1735 oracle_.Schedule(*this, command.release()); 551 oracle_.Schedule(*this, command.release());
1736 } 552 }
1738 554
1739 555
1740 /* class VolumeSlicerBase : public IVolumeSlicer 556 /* class VolumeSlicerBase : public IVolumeSlicer
1741 { 557 {
1742 private: 558 private:
1743 OrthancStone::Scene2D& scene_; 559 Scene2D& scene_;
1744 int layerDepth_; 560 int layerDepth_;
1745 bool first_; 561 bool first_;
1746 OrthancStone::CoordinateSystem3D lastPlane_; 562 CoordinateSystem3D lastPlane_;
1747 563
1748 protected: 564 protected:
1749 bool HasViewportPlaneChanged(const OrthancStone::CoordinateSystem3D& plane) const 565 bool HasViewportPlaneChanged(const CoordinateSystem3D& plane) const
1750 { 566 {
1751 if (first_ || 567 if (first_ ||
1752 !OrthancStone::LinearAlgebra::IsCloseToZero( 568 !LinearAlgebra::IsCloseToZero(
1753 boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal()))) 569 boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal())))
1754 { 570 {
1755 // This is the first rendering, or the plane has not the same orientation 571 // This is the first rendering, or the plane has not the same orientation
1756 return false; 572 return false;
1757 } 573 }
1758 else 574 else
1759 { 575 {
1760 double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin()); 576 double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin());
1761 double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin()); 577 double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin());
1762 return OrthancStone::LinearAlgebra::IsCloseToZero(offset2 - offset1); 578 return LinearAlgebra::IsCloseToZero(offset2 - offset1);
1763 } 579 }
1764 } 580 }
1765 581
1766 void SetLastViewportPlane(const OrthancStone::CoordinateSystem3D& plane) 582 void SetLastViewportPlane(const CoordinateSystem3D& plane)
1767 { 583 {
1768 first_ = false; 584 first_ = false;
1769 lastPlane_ = plane; 585 lastPlane_ = plane;
1770 } 586 }
1771 587
1772 void SetLayer(OrthancStone::ISceneLayer* layer) 588 void SetLayer(ISceneLayer* layer)
1773 { 589 {
1774 scene_.SetLayer(layerDepth_, layer); 590 scene_.SetLayer(layerDepth_, layer);
1775 } 591 }
1776 592
1777 void DeleteLayer() 593 void DeleteLayer()
1778 { 594 {
1779 scene_.DeleteLayer(layerDepth_); 595 scene_.DeleteLayer(layerDepth_);
1780 } 596 }
1781 597
1782 public: 598 public:
1783 VolumeSlicerBase(OrthancStone::Scene2D& scene, 599 VolumeSlicerBase(Scene2D& scene,
1784 int layerDepth) : 600 int layerDepth) :
1785 scene_(scene), 601 scene_(scene),
1786 layerDepth_(layerDepth), 602 layerDepth_(layerDepth),
1787 first_(true) 603 first_(true)
1788 { 604 {
1789 } 605 }
1790 };*/ 606 };*/
1791 607
1792 608
1793 609
610 class IVolumeSlicer : public boost::noncopyable
611 {
612 public:
613 virtual ~IVolumeSlicer()
614 {
615 }
616
617 virtual void SetViewportPlane(const CoordinateSystem3D& plane) = 0;
618 };
619
620
1794 class DicomVolumeMPRSlicer : public IVolumeSlicer 621 class DicomVolumeMPRSlicer : public IVolumeSlicer
1795 { 622 {
1796 private: 623 private:
1797 bool linearInterpolation_; 624 bool linearInterpolation_;
1798 OrthancStone::Scene2D& scene_; 625 Scene2D& scene_;
1799 int layerDepth_; 626 int layerDepth_;
1800 IDicomVolumeSource& source_; 627 IDicomVolumeImageSource& source_;
1801 bool first_; 628 bool first_;
1802 OrthancStone::VolumeProjection lastProjection_; 629 VolumeProjection lastProjection_;
1803 unsigned int lastSliceIndex_; 630 unsigned int lastSliceIndex_;
1804 uint64_t lastSliceRevision_; 631 uint64_t lastSliceRevision_;
1805 632
1806 public: 633 public:
1807 DicomVolumeMPRSlicer(OrthancStone::Scene2D& scene, 634 DicomVolumeMPRSlicer(Scene2D& scene,
1808 int layerDepth, 635 int layerDepth,
1809 IDicomVolumeSource& source) : 636 IDicomVolumeImageSource& source) :
1810 linearInterpolation_(false), 637 linearInterpolation_(false),
1811 scene_(scene), 638 scene_(scene),
1812 layerDepth_(layerDepth), 639 layerDepth_(layerDepth),
1813 source_(source), 640 source_(source),
1814 first_(true) 641 first_(true)
1823 bool IsLinearInterpolation() const 650 bool IsLinearInterpolation() const
1824 { 651 {
1825 return linearInterpolation_; 652 return linearInterpolation_;
1826 } 653 }
1827 654
1828 virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) 655 virtual void SetViewportPlane(const CoordinateSystem3D& plane)
1829 { 656 {
1830 if (!source_.GetVolume().HasGeometry() || 657 if (!source_.GetVolume().HasGeometry() ||
1831 source_.GetVolume().GetSlicesCount() == 0) 658 source_.GetVolume().GetSlicesCount() == 0)
1832 { 659 {
1833 scene_.DeleteLayer(layerDepth_); 660 scene_.DeleteLayer(layerDepth_);
1834 return; 661 return;
1835 } 662 }
1836 663
1837 const OrthancStone::VolumeImageGeometry& geometry = source_.GetVolume().GetImage().GetGeometry(); 664 const VolumeImageGeometry& geometry = source_.GetVolume().GetGeometry();
1838 665
1839 OrthancStone::VolumeProjection projection; 666 VolumeProjection projection;
1840 unsigned int sliceIndex; 667 unsigned int sliceIndex;
1841 if (!geometry.DetectSlice(projection, sliceIndex, plane)) 668 if (!geometry.DetectSlice(projection, sliceIndex, plane))
1842 { 669 {
1843 // The cutting plane is neither axial, nor coronal, nor 670 // The cutting plane is neither axial, nor coronal, nor
1844 // sagittal. Could use "VolumeReslicer" here. 671 // sagittal. Could use "VolumeReslicer" here.
1845 scene_.DeleteLayer(layerDepth_); 672 scene_.DeleteLayer(layerDepth_);
1846 return; 673 return;
1847 } 674 }
1848 675
1849 uint64_t sliceRevision; 676 uint64_t sliceRevision;
1850 if (projection == OrthancStone::VolumeProjection_Axial) 677 if (projection == VolumeProjection_Axial)
1851 { 678 {
1852 sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex); 679 sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex);
1853 680
1854 if (first_ || 681 if (first_ ||
1855 lastSliceIndex_ != sliceIndex) 682 lastSliceIndex_ != sliceIndex)
1876 first_ = false; 703 first_ = false;
1877 lastProjection_ = projection; 704 lastProjection_ = projection;
1878 lastSliceIndex_ = sliceIndex; 705 lastSliceIndex_ = sliceIndex;
1879 lastSliceRevision_ = sliceRevision; 706 lastSliceRevision_ = sliceRevision;
1880 707
1881 std::auto_ptr<OrthancStone::TextureBaseSceneLayer> texture; 708 std::auto_ptr<TextureBaseSceneLayer> texture;
1882 709
1883 { 710 {
1884 const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters 711 const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters
1885 (projection == OrthancStone::VolumeProjection_Axial ? sliceIndex : 0); 712 (projection == VolumeProjection_Axial ? sliceIndex : 0);
1886 713
1887 OrthancStone::ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex); 714 ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex);
1888 texture.reset(parameters.CreateTexture(reader.GetAccessor())); 715 texture.reset(parameters.CreateTexture(reader.GetAccessor()));
1889 } 716 }
1890 717
1891 const OrthancStone::CoordinateSystem3D& system = geometry.GetProjectionGeometry(projection); 718 const CoordinateSystem3D& system = geometry.GetProjectionGeometry(projection);
1892 719
1893 double x0, y0, x1, y1; 720 double x0, y0, x1, y1;
1894 system.ProjectPoint(x0, y0, system.GetOrigin()); 721 system.ProjectPoint(x0, y0, system.GetOrigin());
1895 system.ProjectPoint(x0, y0, system.GetOrigin() + system.GetAxisX()); 722 system.ProjectPoint(x0, y0, system.GetOrigin() + system.GetAxisX());
1896 texture->SetOrigin(x0, y0); 723 texture->SetOrigin(x0, y0);
1897 724
1898 double dx = x1 - x0; 725 double dx = x1 - x0;
1899 double dy = y1 - y0; 726 double dy = y1 - y0;
1900 if (!OrthancStone::LinearAlgebra::IsCloseToZero(dx) || 727 if (!LinearAlgebra::IsCloseToZero(dx) ||
1901 !OrthancStone::LinearAlgebra::IsCloseToZero(dy)) 728 !LinearAlgebra::IsCloseToZero(dy))
1902 { 729 {
1903 texture->SetAngle(atan2(dy, dx)); 730 texture->SetAngle(atan2(dy, dx));
1904 } 731 }
1905 732
1906 OrthancStone::Vector tmp; 733 Vector tmp;
1907 geometry.GetVoxelDimensions(projection); 734 geometry.GetVoxelDimensions(projection);
1908 texture->SetPixelSpacing(tmp[0], tmp[1]); 735 texture->SetPixelSpacing(tmp[0], tmp[1]);
1909 736
1910 texture->SetLinearInterpolation(linearInterpolation_); 737 texture->SetLinearInterpolation(linearInterpolation_);
1911 738
1912 scene_.SetLayer(layerDepth_, texture.release()); 739 scene_.SetLayer(layerDepth_, texture.release());
1913 } 740 }
1914 } 741 }
1915 }; 742 };
1916
1917
1918
1919
1920
1921 class NativeOracle : public IOracle
1922 {
1923 private:
1924 class Item : public Orthanc::IDynamicObject
1925 {
1926 private:
1927 const OrthancStone::IObserver& receiver_;
1928 std::auto_ptr<IOracleCommand> command_;
1929
1930 public:
1931 Item(const OrthancStone::IObserver& receiver,
1932 IOracleCommand* command) :
1933 receiver_(receiver),
1934 command_(command)
1935 {
1936 if (command == NULL)
1937 {
1938 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
1939 }
1940 }
1941
1942 const OrthancStone::IObserver& GetReceiver() const
1943 {
1944 return receiver_;
1945 }
1946
1947 const IOracleCommand& GetCommand() const
1948 {
1949 assert(command_.get() != NULL);
1950 return *command_;
1951 }
1952 };
1953
1954
1955 enum State
1956 {
1957 State_Setup,
1958 State_Running,
1959 State_Stopped
1960 };
1961
1962
1963 IMessageEmitter& emitter_;
1964 Orthanc::WebServiceParameters orthanc_;
1965 Orthanc::SharedMessageQueue queue_;
1966 State state_;
1967 boost::mutex mutex_;
1968 std::vector<boost::thread*> workers_;
1969
1970
1971 void CopyHttpHeaders(Orthanc::HttpClient& client,
1972 const HttpHeaders& headers)
1973 {
1974 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
1975 {
1976 client.AddHeader(it->first, it->second);
1977 }
1978 }
1979
1980
1981 void DecodeAnswer(std::string& answer,
1982 const HttpHeaders& headers)
1983 {
1984 Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None;
1985
1986 for (HttpHeaders::const_iterator it = headers.begin();
1987 it != headers.end(); ++it)
1988 {
1989 std::string s;
1990 Orthanc::Toolbox::ToLowerCase(s, it->first);
1991
1992 if (s == "content-encoding")
1993 {
1994 if (it->second == "gzip")
1995 {
1996 contentEncoding = Orthanc::HttpCompression_Gzip;
1997 }
1998 else
1999 {
2000 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol,
2001 "Unsupported HTTP Content-Encoding: " + it->second);
2002 }
2003
2004 break;
2005 }
2006 }
2007
2008 if (contentEncoding == Orthanc::HttpCompression_Gzip)
2009 {
2010 std::string compressed;
2011 answer.swap(compressed);
2012
2013 Orthanc::GzipCompressor compressor;
2014 compressor.Uncompress(answer, compressed.c_str(), compressed.size());
2015 }
2016 }
2017
2018
2019 void Execute(const OrthancStone::IObserver& receiver,
2020 const OrthancRestApiCommand& command)
2021 {
2022 Orthanc::HttpClient client(orthanc_, command.GetUri());
2023 client.SetMethod(command.GetMethod());
2024 client.SetTimeout(command.GetTimeout());
2025
2026 CopyHttpHeaders(client, command.GetHttpHeaders());
2027
2028 if (command.GetMethod() == Orthanc::HttpMethod_Post ||
2029 command.GetMethod() == Orthanc::HttpMethod_Put)
2030 {
2031 client.SetBody(command.GetBody());
2032 }
2033
2034 std::string answer;
2035 HttpHeaders answerHeaders;
2036 client.ApplyAndThrowException(answer, answerHeaders);
2037
2038 DecodeAnswer(answer, answerHeaders);
2039
2040 OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer);
2041 emitter_.EmitMessage(receiver, message);
2042 }
2043
2044
2045 void Execute(const OrthancStone::IObserver& receiver,
2046 const GetOrthancImageCommand& command)
2047 {
2048 Orthanc::HttpClient client(orthanc_, command.GetUri());
2049 client.SetTimeout(command.GetTimeout());
2050
2051 CopyHttpHeaders(client, command.GetHttpHeaders());
2052
2053 std::string answer;
2054 HttpHeaders answerHeaders;
2055 client.ApplyAndThrowException(answer, answerHeaders);
2056
2057 DecodeAnswer(answer, answerHeaders);
2058
2059 command.ProcessHttpAnswer(emitter_, receiver, answer, answerHeaders);
2060 }
2061
2062
2063 void Execute(const OrthancStone::IObserver& receiver,
2064 const GetOrthancWebViewerJpegCommand& command)
2065 {
2066 Orthanc::HttpClient client(orthanc_, command.GetUri());
2067 client.SetTimeout(command.GetTimeout());
2068
2069 CopyHttpHeaders(client, command.GetHttpHeaders());
2070
2071 std::string answer;
2072 HttpHeaders answerHeaders;
2073 client.ApplyAndThrowException(answer, answerHeaders);
2074
2075 DecodeAnswer(answer, answerHeaders);
2076
2077 command.ProcessHttpAnswer(emitter_, receiver, answer);
2078 }
2079
2080
2081 void Step()
2082 {
2083 std::auto_ptr<Orthanc::IDynamicObject> object(queue_.Dequeue(100));
2084
2085 if (object.get() != NULL)
2086 {
2087 const Item& item = dynamic_cast<Item&>(*object);
2088
2089 try
2090 {
2091 switch (item.GetCommand().GetType())
2092 {
2093 case IOracleCommand::Type_OrthancRestApi:
2094 Execute(item.GetReceiver(),
2095 dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand()));
2096 break;
2097
2098 case IOracleCommand::Type_GetOrthancImage:
2099 Execute(item.GetReceiver(),
2100 dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand()));
2101 break;
2102
2103 case IOracleCommand::Type_GetOrthancWebViewerJpeg:
2104 Execute(item.GetReceiver(),
2105 dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand()));
2106 break;
2107
2108 default:
2109 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
2110 }
2111 }
2112 catch (Orthanc::OrthancException& e)
2113 {
2114 LOG(ERROR) << "Exception within the oracle: " << e.What();
2115 emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e));
2116 }
2117 catch (...)
2118 {
2119 LOG(ERROR) << "Native exception within the oracle";
2120 emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage
2121 (item.GetCommand(), Orthanc::ErrorCode_InternalError));
2122 }
2123 }
2124 }
2125
2126
2127 static void Worker(NativeOracle* that)
2128 {
2129 assert(that != NULL);
2130
2131 for (;;)
2132 {
2133 {
2134 boost::mutex::scoped_lock lock(that->mutex_);
2135 if (that->state_ != State_Running)
2136 {
2137 return;
2138 }
2139 }
2140
2141 that->Step();
2142 }
2143 }
2144
2145
2146 void StopInternal()
2147 {
2148 {
2149 boost::mutex::scoped_lock lock(mutex_);
2150
2151 if (state_ == State_Setup ||
2152 state_ == State_Stopped)
2153 {
2154 return;
2155 }
2156 else
2157 {
2158 state_ = State_Stopped;
2159 }
2160 }
2161
2162 for (size_t i = 0; i < workers_.size(); i++)
2163 {
2164 if (workers_[i] != NULL)
2165 {
2166 if (workers_[i]->joinable())
2167 {
2168 workers_[i]->join();
2169 }
2170
2171 delete workers_[i];
2172 }
2173 }
2174 }
2175
2176
2177 public:
2178 NativeOracle(IMessageEmitter& emitter) :
2179 emitter_(emitter),
2180 state_(State_Setup),
2181 workers_(4)
2182 {
2183 }
2184
2185 virtual ~NativeOracle()
2186 {
2187 StopInternal();
2188 }
2189
2190 void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
2191 {
2192 boost::mutex::scoped_lock lock(mutex_);
2193
2194 if (state_ != State_Setup)
2195 {
2196 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
2197 }
2198 else
2199 {
2200 orthanc_ = orthanc;
2201 }
2202 }
2203
2204 void SetWorkersCount(unsigned int count)
2205 {
2206 boost::mutex::scoped_lock lock(mutex_);
2207
2208 if (count <= 0)
2209 {
2210 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
2211 }
2212 else if (state_ != State_Setup)
2213 {
2214 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
2215 }
2216 else
2217 {
2218 workers_.resize(count);
2219 }
2220 }
2221
2222 void Start()
2223 {
2224 boost::mutex::scoped_lock lock(mutex_);
2225
2226 if (state_ != State_Setup)
2227 {
2228 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
2229 }
2230 else
2231 {
2232 state_ = State_Running;
2233
2234 for (unsigned int i = 0; i < workers_.size(); i++)
2235 {
2236 workers_[i] = new boost::thread(Worker, this);
2237 }
2238 }
2239 }
2240
2241 void Stop()
2242 {
2243 StopInternal();
2244 }
2245
2246 virtual void Schedule(const OrthancStone::IObserver& receiver,
2247 IOracleCommand* command)
2248 {
2249 queue_.Enqueue(new Item(receiver, command));
2250 }
2251 };
2252 743
2253 744
2254 class NativeApplicationContext : public IMessageEmitter 745 class NativeApplicationContext : public IMessageEmitter
2255 { 746 {
2256 private: 747 private:
2257 boost::shared_mutex mutex_; 748 boost::shared_mutex mutex_;
2258 OrthancStone::MessageBroker broker_; 749 MessageBroker broker_;
2259 OrthancStone::IObservable oracleObservable_; 750 IObservable oracleObservable_;
2260 751
2261 public: 752 public:
2262 NativeApplicationContext() : 753 NativeApplicationContext() :
2263 oracleObservable_(broker_) 754 oracleObservable_(broker_)
2264 { 755 {
2265 } 756 }
2266 757
2267 758
2268 virtual void EmitMessage(const OrthancStone::IObserver& observer, 759 virtual void EmitMessage(const IObserver& observer,
2269 const OrthancStone::IMessage& message) 760 const IMessage& message)
2270 { 761 {
2271 try 762 try
2272 { 763 {
2273 boost::unique_lock<boost::shared_mutex> lock(mutex_); 764 boost::unique_lock<boost::shared_mutex> lock(mutex_);
2274 oracleObservable_.EmitMessage(observer, message); 765 oracleObservable_.EmitMessage(observer, message);
2286 NativeApplicationContext& that_; 777 NativeApplicationContext& that_;
2287 boost::shared_lock<boost::shared_mutex> lock_; 778 boost::shared_lock<boost::shared_mutex> lock_;
2288 779
2289 public: 780 public:
2290 ReaderLock(NativeApplicationContext& that) : 781 ReaderLock(NativeApplicationContext& that) :
2291 that_(that), 782 that_(that),
2292 lock_(that.mutex_) 783 lock_(that.mutex_)
2293 { 784 {
2294 } 785 }
2295 }; 786 };
2296 787
2297 788
2301 NativeApplicationContext& that_; 792 NativeApplicationContext& that_;
2302 boost::unique_lock<boost::shared_mutex> lock_; 793 boost::unique_lock<boost::shared_mutex> lock_;
2303 794
2304 public: 795 public:
2305 WriterLock(NativeApplicationContext& that) : 796 WriterLock(NativeApplicationContext& that) :
2306 that_(that), 797 that_(that),
2307 lock_(that.mutex_) 798 lock_(that.mutex_)
2308 { 799 {
2309 } 800 }
2310 801
2311 OrthancStone::MessageBroker& GetBroker() 802 MessageBroker& GetBroker()
2312 { 803 {
2313 return that_.broker_; 804 return that_.broker_;
2314 } 805 }
2315 806
2316 OrthancStone::IObservable& GetOracleObservable() 807 IObservable& GetOracleObservable()
2317 { 808 {
2318 return that_.oracleObservable_; 809 return that_.oracleObservable_;
2319 } 810 }
2320 }; 811 };
2321 }; 812 };
2324 815
2325 816
2326 class Toto : public OrthancStone::IObserver 817 class Toto : public OrthancStone::IObserver
2327 { 818 {
2328 private: 819 private:
2329 void Handle(const Refactoring::OrthancRestApiCommand::SuccessMessage& message) 820 void Handle(const OrthancStone::SleepOracleCommand::TimeoutMessage& message)
821 {
822 printf("TIMEOUT! %d\n", dynamic_cast<const Orthanc::SingleValueObject<unsigned int>& >(message.GetOrigin().GetPayload()).GetValue());
823 }
824
825 void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message)
2330 { 826 {
2331 Json::Value v; 827 Json::Value v;
2332 message.ParseJsonBody(v); 828 message.ParseJsonBody(v);
2333 829
2334 printf("ICI [%s]\n", v.toStyledString().c_str()); 830 printf("ICI [%s]\n", v.toStyledString().c_str());
2335 } 831 }
2336 832
2337 void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message) 833 void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message)
2338 { 834 {
2339 printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); 835 printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
2340 } 836 }
2341 837
2342 void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message) 838 void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message)
2343 { 839 {
2344 printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); 840 printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight());
2345 } 841 }
2346 842
2347 void Handle(const Refactoring::OracleCommandExceptionMessage& message) 843 void Handle(const OrthancStone::OracleCommandExceptionMessage& message)
2348 { 844 {
2349 printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType()); 845 printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType());
2350 846
2351 switch (message.GetCommand().GetType()) 847 switch (message.GetCommand().GetType())
2352 { 848 {
2353 case Refactoring::IOracleCommand::Type_GetOrthancWebViewerJpeg: 849 case OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg:
2354 printf("URI: [%s]\n", dynamic_cast<const Refactoring::GetOrthancWebViewerJpegCommand&> 850 printf("URI: [%s]\n", dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&>
2355 (message.GetCommand()).GetUri().c_str()); 851 (message.GetCommand()).GetUri().c_str());
2356 break; 852 break;
2357 853
2358 default: 854 default:
2359 break; 855 break;
2364 Toto(OrthancStone::IObservable& oracle) : 860 Toto(OrthancStone::IObservable& oracle) :
2365 IObserver(oracle.GetBroker()) 861 IObserver(oracle.GetBroker())
2366 { 862 {
2367 oracle.RegisterObserverCallback 863 oracle.RegisterObserverCallback
2368 (new OrthancStone::Callable 864 (new OrthancStone::Callable
2369 <Toto, Refactoring::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle)); 865 <Toto, OrthancStone::SleepOracleCommand::TimeoutMessage>(*this, &Toto::Handle));
2370 866
2371 oracle.RegisterObserverCallback 867 oracle.RegisterObserverCallback
2372 (new OrthancStone::Callable 868 (new OrthancStone::Callable
2373 <Toto, Refactoring::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle)); 869 <Toto, OrthancStone::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle));
2374 870
2375 oracle.RegisterObserverCallback 871 oracle.RegisterObserverCallback
2376 (new OrthancStone::Callable 872 (new OrthancStone::Callable
2377 <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle)); 873 <Toto, OrthancStone::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle));
2378 874
2379 oracle.RegisterObserverCallback 875 oracle.RegisterObserverCallback
2380 (new OrthancStone::Callable 876 (new OrthancStone::Callable
2381 <Toto, Refactoring::OracleCommandExceptionMessage>(*this, &Toto::Handle)); 877 <Toto, OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle));
878
879 oracle.RegisterObserverCallback
880 (new OrthancStone::Callable
881 <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle));
2382 } 882 }
2383 }; 883 };
2384 884
2385 885
2386 void Run(Refactoring::NativeApplicationContext& context, 886 void Run(OrthancStone::NativeApplicationContext& context,
2387 Refactoring::IOracle& oracle) 887 OrthancStone::IOracle& oracle)
2388 { 888 {
2389 std::auto_ptr<Toto> toto; 889 std::auto_ptr<Toto> toto;
2390 std::auto_ptr<Refactoring::VolumeSeriesOrthancLoader> loader1, loader2; 890 std::auto_ptr<OrthancStone::VolumeSeriesOrthancLoader> loader1, loader2;
2391 891
2392 { 892 {
2393 Refactoring::NativeApplicationContext::WriterLock lock(context); 893 OrthancStone::NativeApplicationContext::WriterLock lock(context);
2394 toto.reset(new Toto(lock.GetOracleObservable())); 894 toto.reset(new Toto(lock.GetOracleObservable()));
2395 loader1.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable())); 895 loader1.reset(new OrthancStone::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable()));
2396 loader2.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable())); 896 loader2.reset(new OrthancStone::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable()));
2397 } 897 }
2398 898
2399 if (1) 899 if (0)
2400 { 900 {
2401 Json::Value v = Json::objectValue; 901 Json::Value v = Json::objectValue;
2402 v["Level"] = "Series"; 902 v["Level"] = "Series";
2403 v["Query"] = Json::objectValue; 903 v["Query"] = Json::objectValue;
2404 904
2405 std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); 905 std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand);
2406 command->SetMethod(Orthanc::HttpMethod_Post); 906 command->SetMethod(Orthanc::HttpMethod_Post);
2407 command->SetUri("/tools/find"); 907 command->SetUri("/tools/find");
2408 command->SetBody(v); 908 command->SetBody(v);
2409 909
2410 oracle.Schedule(*toto, command.release()); 910 oracle.Schedule(*toto, command.release());
2411 } 911 }
2412 912
2413 if (1) 913 if (0)
2414 { 914 {
2415 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); 915 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand);
2416 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg))); 916 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)));
2417 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); 917 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
2418 oracle.Schedule(*toto, command.release()); 918 oracle.Schedule(*toto, command.release());
2419 } 919 }
2420 920
2421 if (1) 921 if (0)
2422 { 922 {
2423 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); 923 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand);
2424 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); 924 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
2425 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); 925 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview");
2426 oracle.Schedule(*toto, command.release()); 926 oracle.Schedule(*toto, command.release());
2427 } 927 }
2428 928
2429 if (1) 929 if (0)
2430 { 930 {
2431 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); 931 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand);
2432 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); 932 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png)));
2433 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); 933 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
2434 oracle.Schedule(*toto, command.release()); 934 oracle.Schedule(*toto, command.release());
2435 } 935 }
2436 936
2437 if (1) 937 if (0)
2438 { 938 {
2439 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); 939 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand);
2440 command->SetHttpHeader("Accept-Encoding", "gzip"); 940 command->SetHttpHeader("Accept-Encoding", "gzip");
2441 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); 941 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
2442 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); 942 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
2443 oracle.Schedule(*toto, command.release()); 943 oracle.Schedule(*toto, command.release());
2444 } 944 }
2445 945
2446 if (1) 946 if (0)
2447 { 947 {
2448 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); 948 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand);
2449 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); 949 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
2450 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); 950 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16");
2451 oracle.Schedule(*toto, command.release()); 951 oracle.Schedule(*toto, command.release());
2452 } 952 }
2453 953
2454 if (1) 954 if (0)
2455 { 955 {
2456 std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> command(new Refactoring::GetOrthancWebViewerJpegCommand); 956 std::auto_ptr<OrthancStone::GetOrthancWebViewerJpegCommand> command(new OrthancStone::GetOrthancWebViewerJpegCommand);
2457 command->SetHttpHeader("Accept-Encoding", "gzip"); 957 command->SetHttpHeader("Accept-Encoding", "gzip");
2458 command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e"); 958 command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e");
2459 command->SetQuality(90); 959 command->SetQuality(90);
2460 oracle.Schedule(*toto, command.release()); 960 oracle.Schedule(*toto, command.release());
2461 } 961 }
2462 962
2463 963
964 if (0)
965 {
966 for (unsigned int i = 0; i < 10; i++)
967 {
968 std::auto_ptr<OrthancStone::SleepOracleCommand> command(new OrthancStone::SleepOracleCommand(i * 1000));
969 command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(42 * i));
970 oracle.Schedule(*toto, command.release());
971 }
972 }
973
2464 // 2017-11-17-Anonymized 974 // 2017-11-17-Anonymized
2465 //loader1->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT 975 //loader1->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT
2466 //loader2->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE 976 //loader2->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE
2467 977
2468 // Delphine 978 // Delphine
2469 loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT 979 //loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT
980 loader1->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm
2470 981
2471 LOG(WARNING) << "...Waiting for Ctrl-C..."; 982 LOG(WARNING) << "...Waiting for Ctrl-C...";
2472 Orthanc::SystemToolbox::ServerBarrier(); 983 Orthanc::SystemToolbox::ServerBarrier();
2473 //boost::this_thread::sleep(boost::posix_time::seconds(1)); 984 //boost::this_thread::sleep(boost::posix_time::seconds(1));
2474 } 985 }
2485 OrthancStone::StoneInitialize(); 996 OrthancStone::StoneInitialize();
2486 Orthanc::Logging::EnableInfoLevel(true); 997 Orthanc::Logging::EnableInfoLevel(true);
2487 998
2488 try 999 try
2489 { 1000 {
2490 Refactoring::NativeApplicationContext context; 1001 OrthancStone::NativeApplicationContext context;
2491 1002
2492 Refactoring::NativeOracle oracle(context); 1003 OrthancStone::ThreadedOracle oracle(context);
2493 1004
2494 { 1005 {
2495 Orthanc::WebServiceParameters p; 1006 Orthanc::WebServiceParameters p;
2496 //p.SetUrl("http://localhost:8043/"); 1007 //p.SetUrl("http://localhost:8043/");
2497 p.SetCredentials("orthanc", "orthanc"); 1008 p.SetCredentials("orthanc", "orthanc");