comparison Samples/Sdl/Loader.cpp @ 633:b0652595b62a

Merge
author Benjamin Golinvaux <bgo@osimis.io>
date Thu, 09 May 2019 14:38:27 +0200
parents ea8322566596
children fb00a8be03e2
comparison
equal deleted inserted replaced
632:500c3f70b6c2 633:b0652595b62a
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2019 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
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/>.
19 **/
20
21 // From Stone
22 #include "../../Framework/Messages/ICallable.h"
23 #include "../../Framework/Messages/IMessage.h"
24 #include "../../Framework/Messages/IObservable.h"
25 #include "../../Framework/Messages/MessageBroker.h"
26 #include "../../Framework/StoneInitialization.h"
27 #include "../../Framework/Toolbox/GeometryToolbox.h"
28 #include "../../Framework/Volumes/ImageBuffer3D.h"
29
30 // From Orthanc framework
31 #include <Core/DicomFormat/DicomArray.h>
32 #include <Core/DicomFormat/DicomImageInformation.h>
33 #include <Core/HttpClient.h>
34 #include <Core/IDynamicObject.h>
35 #include <Core/Images/Image.h>
36 #include <Core/Images/ImageProcessing.h>
37 #include <Core/Images/PngWriter.h>
38 #include <Core/Logging.h>
39 #include <Core/MultiThreading/SharedMessageQueue.h>
40 #include <Core/OrthancException.h>
41 #include <Core/Toolbox.h>
42
43 #include <json/reader.h>
44 #include <json/value.h>
45 #include <json/writer.h>
46
47 #include <list>
48 #include <stdio.h>
49
50
51
52 namespace Refactoring
53 {
54 class IOracleCommand : public boost::noncopyable
55 {
56 public:
57 enum Type
58 {
59 Type_OrthancApi
60 };
61
62 virtual ~IOracleCommand()
63 {
64 }
65
66 virtual Type GetType() const = 0;
67 };
68
69
70 class IMessageEmitter : public boost::noncopyable
71 {
72 public:
73 virtual ~IMessageEmitter()
74 {
75 }
76
77 virtual void EmitMessage(const OrthancStone::IObserver& observer,
78 const OrthancStone::IMessage& message) = 0;
79 };
80
81
82 class IOracle : public boost::noncopyable
83 {
84 public:
85 virtual ~IOracle()
86 {
87 }
88
89 virtual void Schedule(const OrthancStone::IObserver& receiver,
90 IOracleCommand* command) = 0; // Takes ownership
91 };
92
93
94
95
96 class OracleCommandWithPayload : public IOracleCommand
97 {
98 private:
99 std::auto_ptr<Orthanc::IDynamicObject> payload_;
100
101 public:
102 void SetPayload(Orthanc::IDynamicObject* payload)
103 {
104 if (payload == NULL)
105 {
106 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
107 }
108 else
109 {
110 payload_.reset(payload);
111 }
112 }
113
114 bool HasPayload() const
115 {
116 return (payload_.get() != NULL);
117 }
118
119 const Orthanc::IDynamicObject& GetPayload() const
120 {
121 if (HasPayload())
122 {
123 return *payload_;
124 }
125 else
126 {
127 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
128 }
129 }
130 };
131
132
133
134 typedef std::map<std::string, std::string> HttpHeaders;
135
136 class OrthancApiOracleCommand : public OracleCommandWithPayload
137 {
138 public:
139 class SuccessMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestSuccess, // TODO
140 OrthancApiOracleCommand>
141 {
142 private:
143 HttpHeaders headers_;
144 std::string answer_;
145
146 public:
147 SuccessMessage(const OrthancApiOracleCommand& command,
148 const HttpHeaders& answerHeaders,
149 std::string& answer /* will be swapped to avoid a memcpy() */) :
150 OriginMessage(command),
151 headers_(answerHeaders),
152 answer_(answer)
153 {
154 }
155
156 const std::string& GetAnswer() const
157 {
158 return answer_;
159 }
160
161 void ParseJsonBody(Json::Value& target) const
162 {
163 Json::Reader reader;
164 if (!reader.parse(answer_, target))
165 {
166 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
167 }
168 }
169
170 const HttpHeaders& GetAnswerHeaders() const
171 {
172 return headers_;
173 }
174 };
175
176
177 class FailureMessage : public OrthancStone::OriginMessage<OrthancStone::MessageType_HttpRequestError, // TODO
178 OrthancApiOracleCommand>
179 {
180 private:
181 Orthanc::HttpStatus status_;
182
183 public:
184 FailureMessage(const OrthancApiOracleCommand& command,
185 Orthanc::HttpStatus status) :
186 OriginMessage(command),
187 status_(status)
188 {
189 }
190
191 Orthanc::HttpStatus GetHttpStatus() const
192 {
193 return status_;
194 }
195 };
196
197
198 private:
199 Orthanc::HttpMethod method_;
200 std::string uri_;
201 std::string body_;
202 HttpHeaders headers_;
203 unsigned int timeout_;
204
205 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_;
206 std::auto_ptr< OrthancStone::MessageHandler<FailureMessage> > failureCallback_;
207
208 public:
209 OrthancApiOracleCommand() :
210 method_(Orthanc::HttpMethod_Get),
211 uri_("/"),
212 timeout_(10)
213 {
214 }
215
216 virtual Type GetType() const
217 {
218 return Type_OrthancApi;
219 }
220
221 void SetMethod(Orthanc::HttpMethod method)
222 {
223 method_ = method;
224 }
225
226 void SetUri(const std::string& uri)
227 {
228 uri_ = uri;
229 }
230
231 void SetBody(const std::string& body)
232 {
233 body_ = body;
234 }
235
236 void SetBody(const Json::Value& json)
237 {
238 Json::FastWriter writer;
239 body_ = writer.write(json);
240 }
241
242 void SetHttpHeader(const std::string& key,
243 const std::string& value)
244 {
245 headers_[key] = value;
246 }
247
248 Orthanc::HttpMethod GetMethod() const
249 {
250 return method_;
251 }
252
253 const std::string& GetUri() const
254 {
255 return uri_;
256 }
257
258 const std::string& GetBody() const
259 {
260 if (method_ == Orthanc::HttpMethod_Post ||
261 method_ == Orthanc::HttpMethod_Put)
262 {
263 return body_;
264 }
265 else
266 {
267 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
268 }
269 }
270
271 const HttpHeaders& GetHttpHeaders() const
272 {
273 return headers_;
274 }
275
276 void SetTimeout(unsigned int seconds)
277 {
278 timeout_ = seconds;
279 }
280
281 unsigned int GetTimeout() const
282 {
283 return timeout_;
284 }
285 };
286
287
288
289 class NativeOracle : public IOracle
290 {
291 private:
292 class Item : public Orthanc::IDynamicObject
293 {
294 private:
295 const OrthancStone::IObserver& receiver_;
296 std::auto_ptr<IOracleCommand> command_;
297
298 public:
299 Item(const OrthancStone::IObserver& receiver,
300 IOracleCommand* command) :
301 receiver_(receiver),
302 command_(command)
303 {
304 if (command == NULL)
305 {
306 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
307 }
308 }
309
310 const OrthancStone::IObserver& GetReceiver() const
311 {
312 return receiver_;
313 }
314
315 const IOracleCommand& GetCommand() const
316 {
317 assert(command_.get() != NULL);
318 return *command_;
319 }
320 };
321
322
323 enum State
324 {
325 State_Setup,
326 State_Running,
327 State_Stopped
328 };
329
330
331 IMessageEmitter& emitter_;
332 Orthanc::WebServiceParameters orthanc_;
333 Orthanc::SharedMessageQueue queue_;
334 State state_;
335 boost::mutex mutex_;
336 std::vector<boost::thread*> workers_;
337
338
339 void Execute(const OrthancStone::IObserver& receiver,
340 const OrthancApiOracleCommand& command)
341 {
342 Orthanc::HttpClient client(orthanc_, command.GetUri());
343 client.SetMethod(command.GetMethod());
344 client.SetTimeout(command.GetTimeout());
345
346 if (command.GetMethod() == Orthanc::HttpMethod_Post ||
347 command.GetMethod() == Orthanc::HttpMethod_Put)
348 {
349 client.SetBody(command.GetBody());
350 }
351
352 {
353 const HttpHeaders& headers = command.GetHttpHeaders();
354 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ )
355 {
356 client.AddHeader(it->first, it->second);
357 }
358 }
359
360 std::string answer;
361 HttpHeaders answerHeaders;
362
363 bool success;
364 try
365 {
366 success = client.Apply(answer, answerHeaders);
367 }
368 catch (Orthanc::OrthancException& e)
369 {
370 success = false;
371 }
372
373 if (success)
374 {
375 OrthancApiOracleCommand::SuccessMessage message(command, answerHeaders, answer);
376 emitter_.EmitMessage(receiver, message);
377 }
378 else
379 {
380 OrthancApiOracleCommand::FailureMessage message(command, client.GetLastStatus());
381 emitter_.EmitMessage(receiver, message);
382 }
383 }
384
385
386
387 void Step()
388 {
389 std::auto_ptr<Orthanc::IDynamicObject> object(queue_.Dequeue(100));
390
391 if (object.get() != NULL)
392 {
393 const Item& item = dynamic_cast<Item&>(*object);
394
395 try
396 {
397 switch (item.GetCommand().GetType())
398 {
399 case IOracleCommand::Type_OrthancApi:
400 Execute(item.GetReceiver(),
401 dynamic_cast<const OrthancApiOracleCommand&>(item.GetCommand()));
402 break;
403
404 default:
405 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
406 }
407 }
408 catch (Orthanc::OrthancException& e)
409 {
410 LOG(ERROR) << "Exception within the oracle: " << e.What();
411 }
412 catch (...)
413 {
414 LOG(ERROR) << "Native exception within the oracle";
415 }
416 }
417 }
418
419
420 static void Worker(NativeOracle* that)
421 {
422 assert(that != NULL);
423
424 for (;;)
425 {
426 {
427 boost::mutex::scoped_lock lock(that->mutex_);
428 if (that->state_ != State_Running)
429 {
430 return;
431 }
432 }
433
434 that->Step();
435 }
436 }
437
438
439 void StopInternal()
440 {
441 {
442 boost::mutex::scoped_lock lock(mutex_);
443
444 if (state_ == State_Setup ||
445 state_ == State_Stopped)
446 {
447 return;
448 }
449 else
450 {
451 state_ = State_Stopped;
452 }
453 }
454
455 for (size_t i = 0; i < workers_.size(); i++)
456 {
457 if (workers_[i] != NULL)
458 {
459 if (workers_[i]->joinable())
460 {
461 workers_[i]->join();
462 }
463
464 delete workers_[i];
465 }
466 }
467 }
468
469
470 public:
471 NativeOracle(IMessageEmitter& emitter) :
472 emitter_(emitter),
473 state_(State_Setup),
474 workers_(4)
475 {
476 }
477
478 virtual ~NativeOracle()
479 {
480 StopInternal();
481 }
482
483 void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc)
484 {
485 boost::mutex::scoped_lock lock(mutex_);
486
487 if (state_ != State_Setup)
488 {
489 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
490 }
491 else
492 {
493 orthanc_ = orthanc;
494 }
495 }
496
497 void SetWorkersCount(unsigned int count)
498 {
499 boost::mutex::scoped_lock lock(mutex_);
500
501 if (count <= 0)
502 {
503 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
504 }
505 else if (state_ != State_Setup)
506 {
507 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
508 }
509 else
510 {
511 workers_.resize(count);
512 }
513 }
514
515 void Start()
516 {
517 boost::mutex::scoped_lock lock(mutex_);
518
519 if (state_ != State_Setup)
520 {
521 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
522 }
523 else
524 {
525 state_ = State_Running;
526
527 for (unsigned int i = 0; i < workers_.size(); i++)
528 {
529 workers_[i] = new boost::thread(Worker, this);
530 }
531 }
532 }
533
534 void Stop()
535 {
536 StopInternal();
537 }
538
539 virtual void Schedule(const OrthancStone::IObserver& receiver,
540 IOracleCommand* command)
541 {
542 queue_.Enqueue(new Item(receiver, command));
543 }
544 };
545
546
547
548 class NativeApplicationContext : public IMessageEmitter
549 {
550 private:
551 boost::shared_mutex mutex_;
552 OrthancStone::MessageBroker broker_;
553 OrthancStone::IObservable oracleObservable_;
554
555 public:
556 NativeApplicationContext() :
557 oracleObservable_(broker_)
558 {
559 }
560
561
562 virtual void EmitMessage(const OrthancStone::IObserver& observer,
563 const OrthancStone::IMessage& message)
564 {
565 boost::unique_lock<boost::shared_mutex> lock(mutex_);
566 oracleObservable_.EmitMessage(observer, message);
567 }
568
569
570 class ReaderLock : public boost::noncopyable
571 {
572 private:
573 NativeApplicationContext& that_;
574 boost::shared_lock<boost::shared_mutex> lock_;
575
576 public:
577 ReaderLock(NativeApplicationContext& that) :
578 that_(that),
579 lock_(that.mutex_)
580 {
581 }
582 };
583
584
585 class WriterLock : public boost::noncopyable
586 {
587 private:
588 NativeApplicationContext& that_;
589 boost::unique_lock<boost::shared_mutex> lock_;
590
591 public:
592 WriterLock(NativeApplicationContext& that) :
593 that_(that),
594 lock_(that.mutex_)
595 {
596 }
597
598 OrthancStone::MessageBroker& GetBroker()
599 {
600 return that_.broker_;
601 }
602
603 OrthancStone::IObservable& GetOracleObservable()
604 {
605 return that_.oracleObservable_;
606 }
607 };
608 };
609
610
611
612 class DicomInstanceParameters : public boost::noncopyable
613 {
614 private:
615 Orthanc::DicomImageInformation imageInformation_;
616 OrthancStone::SopClassUid sopClassUid_;
617 double thickness_;
618 double pixelSpacingX_;
619 double pixelSpacingY_;
620 OrthancStone::CoordinateSystem3D geometry_;
621 OrthancStone::Vector frameOffsets_;
622 bool isColor_;
623 bool hasRescale_;
624 double rescaleOffset_;
625 double rescaleSlope_;
626 bool hasDefaultWindowing_;
627 float defaultWindowingCenter_;
628 float defaultWindowingWidth_;
629 Orthanc::PixelFormat expectedPixelFormat_;
630
631 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
632 {
633 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
634
635 {
636 std::string increment;
637
638 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
639 {
640 Orthanc::Toolbox::ToUpperCase(increment);
641 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag
642 {
643 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
644 return;
645 }
646 }
647 }
648
649 if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
650 frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
651 {
652 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
653 frameOffsets_.clear();
654 }
655 else
656 {
657 if (frameOffsets_.size() >= 2)
658 {
659 thickness_ = frameOffsets_[1] - frameOffsets_[0];
660
661 if (thickness_ < 0)
662 {
663 thickness_ = -thickness_;
664 }
665 }
666 }
667 }
668
669 public:
670 DicomInstanceParameters(const Orthanc::DicomMap& dicom) :
671 imageInformation_(dicom)
672 {
673 if (imageInformation_.GetNumberOfFrames() <= 0)
674 {
675 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
676 }
677
678 std::string s;
679 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
680 {
681 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
682 }
683 else
684 {
685 sopClassUid_ = OrthancStone::StringToSopClassUid(s);
686 }
687
688 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
689 {
690 thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
691 }
692
693 OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
694
695 std::string position, orientation;
696 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
697 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
698 {
699 geometry_ = OrthancStone::CoordinateSystem3D(position, orientation);
700 }
701
702 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
703 {
704 ComputeDoseOffsets(dicom);
705 }
706
707 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
708 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
709
710 double doseGridScaling;
711
712 if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
713 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
714 {
715 hasRescale_ = true;
716 }
717 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
718 {
719 hasRescale_ = true;
720 rescaleOffset_ = 0;
721 rescaleSlope_ = doseGridScaling;
722 }
723 else
724 {
725 hasRescale_ = false;
726 }
727
728 OrthancStone::Vector c, w;
729 if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
730 OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
731 c.size() > 0 &&
732 w.size() > 0)
733 {
734 hasDefaultWindowing_ = true;
735 defaultWindowingCenter_ = static_cast<float>(c[0]);
736 defaultWindowingWidth_ = static_cast<float>(w[0]);
737 }
738 else
739 {
740 hasDefaultWindowing_ = false;
741 }
742
743 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
744 {
745 switch (imageInformation_.GetBitsStored())
746 {
747 case 16:
748 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
749 break;
750
751 case 32:
752 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
753 break;
754
755 default:
756 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
757 }
758 }
759 else if (isColor_)
760 {
761 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
762 }
763 else if (imageInformation_.IsSigned())
764 {
765 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
766 }
767 else
768 {
769 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
770 }
771 }
772
773 const Orthanc::DicomImageInformation& GetImageInformation() const
774 {
775 return imageInformation_;
776 }
777
778 OrthancStone::SopClassUid GetSopClassUid() const
779 {
780 return sopClassUid_;
781 }
782
783 double GetThickness() const
784 {
785 return thickness_;
786 }
787
788 double GetPixelSpacingX() const
789 {
790 return pixelSpacingX_;
791 }
792
793 double GetPixelSpacingY() const
794 {
795 return pixelSpacingY_;
796 }
797
798 const OrthancStone::CoordinateSystem3D& GetGeometry() const
799 {
800 return geometry_;
801 }
802
803 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const
804 {
805 if (frame >= imageInformation_.GetNumberOfFrames())
806 {
807 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
808 }
809
810 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose)
811 {
812 if (frame >= frameOffsets_.size())
813 {
814 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
815 }
816
817 return OrthancStone::CoordinateSystem3D(
818 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
819 geometry_.GetAxisX(),
820 geometry_.GetAxisY());
821 }
822 }
823
824 bool FrameContainsPlane(unsigned int frame,
825 const OrthancStone::CoordinateSystem3D& plane) const
826 {
827 if (frame >= imageInformation_.GetNumberOfFrames())
828 {
829 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
830 }
831
832 OrthancStone::CoordinateSystem3D tmp = geometry_;
833
834 if (frame != 0)
835 {
836 tmp = GetFrameGeometry(frame);
837 }
838
839 bool opposite; // Ignored
840 return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(
841 opposite, tmp.GetNormal(), plane.GetNormal()) &&
842 OrthancStone::LinearAlgebra::IsNear(
843 tmp.ProjectAlongNormal(tmp.GetOrigin()),
844 tmp.ProjectAlongNormal(plane.GetOrigin()),
845 thickness_ / 2.0));
846 }
847
848 bool IsColor() const
849 {
850 return isColor_;
851 }
852
853 bool HasRescale() const
854 {
855 return hasRescale_;
856 }
857
858 double GetRescaleOffset() const
859 {
860 if (hasRescale_)
861 {
862 return rescaleOffset_;
863 }
864 else
865 {
866 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
867 }
868 }
869
870 double GetRescaleSlope() const
871 {
872 if (hasRescale_)
873 {
874 return rescaleSlope_;
875 }
876 else
877 {
878 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
879 }
880 }
881
882 bool HasDefaultWindowing() const
883 {
884 return hasDefaultWindowing_;
885 }
886
887 float GetDefaultWindowingCenter() const
888 {
889 if (hasDefaultWindowing_)
890 {
891 return defaultWindowingCenter_;
892 }
893 else
894 {
895 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
896 }
897 }
898
899 float GetDefaultWindowingWidth() const
900 {
901 if (hasDefaultWindowing_)
902 {
903 return defaultWindowingWidth_;
904 }
905 else
906 {
907 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
908 }
909 }
910
911 Orthanc::PixelFormat GetExpectedPixelFormat() const
912 {
913 return expectedPixelFormat_;
914 }
915 };
916
917
918 class AxialVolumeOrthancLoader : public OrthancStone::IObserver
919 {
920 private:
921 class MessageHandler : public Orthanc::IDynamicObject
922 {
923 public:
924 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const = 0;
925 };
926
927 void Handle(const OrthancApiOracleCommand::SuccessMessage& message)
928 {
929 dynamic_cast<const MessageHandler&>(message.GetOrigin().GetPayload()).Handle(message);
930 }
931
932
933 class LoadSeriesGeometryHandler : public MessageHandler
934 {
935 private:
936 AxialVolumeOrthancLoader& that_;
937
938 public:
939 LoadSeriesGeometryHandler(AxialVolumeOrthancLoader& that) :
940 that_(that)
941 {
942 }
943
944 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
945 {
946 Json::Value value;
947 message.ParseJsonBody(value);
948
949 if (value.type() != Json::objectValue)
950 {
951 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
952 }
953
954 Json::Value::Members instances = value.getMemberNames();
955
956 for (size_t i = 0; i < instances.size(); i++)
957 {
958 Orthanc::DicomMap dicom;
959 dicom.FromDicomAsJson(value[instances[i]]);
960
961 DicomInstanceParameters instance(dicom);
962 }
963 }
964 };
965
966
967 class LoadInstanceGeometryHandler : public MessageHandler
968 {
969 private:
970 AxialVolumeOrthancLoader& that_;
971
972 public:
973 LoadInstanceGeometryHandler(AxialVolumeOrthancLoader& that) :
974 that_(that)
975 {
976 }
977
978 virtual void Handle(const OrthancApiOracleCommand::SuccessMessage& message) const
979 {
980 Json::Value value;
981 message.ParseJsonBody(value);
982
983 if (value.type() != Json::objectValue)
984 {
985 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
986 }
987
988 Orthanc::DicomMap dicom;
989 dicom.FromDicomAsJson(value);
990
991 DicomInstanceParameters instance(dicom);
992 }
993 };
994
995
996 bool active_;
997 std::auto_ptr<OrthancStone::ImageBuffer3D> image_;
998
999
1000 public:
1001 AxialVolumeOrthancLoader(OrthancStone::IObservable& oracle) :
1002 IObserver(oracle.GetBroker()),
1003 active_(false)
1004 {
1005 oracle.RegisterObserverCallback(
1006 new OrthancStone::Callable<AxialVolumeOrthancLoader, OrthancApiOracleCommand::SuccessMessage>
1007 (*this, &AxialVolumeOrthancLoader::Handle));
1008 }
1009
1010 void LoadSeries(IOracle& oracle,
1011 const std::string& seriesId)
1012 {
1013 if (active_)
1014 {
1015 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1016 }
1017
1018 active_ = true;
1019
1020 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
1021 command->SetUri("/series/" + seriesId + "/instances-tags");
1022 command->SetPayload(new LoadSeriesGeometryHandler(*this));
1023
1024 oracle.Schedule(*this, command.release());
1025 }
1026
1027 void LoadInstance(IOracle& oracle,
1028 const std::string& instanceId)
1029 {
1030 if (active_)
1031 {
1032 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1033 }
1034
1035 active_ = true;
1036
1037 // Tag "3004-000c" is "Grid Frame Offset Vector", which is
1038 // mandatory to read RT DOSE, but is too long to be returned by default
1039
1040 // TODO => Should be part of a second call if needed
1041
1042 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
1043 command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c");
1044 command->SetPayload(new LoadInstanceGeometryHandler(*this));
1045
1046 oracle.Schedule(*this, command.release());
1047 }
1048 };
1049
1050 }
1051
1052
1053
1054 class Toto : public OrthancStone::IObserver
1055 {
1056 private:
1057 void Handle(const Refactoring::OrthancApiOracleCommand::SuccessMessage& message)
1058 {
1059 Json::Value v;
1060 message.ParseJsonBody(v);
1061
1062 printf("ICI [%s]\n", v.toStyledString().c_str());
1063 }
1064
1065 void Handle(const Refactoring::OrthancApiOracleCommand::FailureMessage& message)
1066 {
1067 printf("ERROR %d\n", message.GetHttpStatus());
1068 }
1069
1070 public:
1071 Toto(OrthancStone::IObservable& oracle) :
1072 IObserver(oracle.GetBroker())
1073 {
1074 oracle.RegisterObserverCallback
1075 (new OrthancStone::Callable
1076 <Toto, Refactoring::OrthancApiOracleCommand::SuccessMessage>(*this, &Toto::Handle));
1077 }
1078 };
1079
1080
1081 void Run(Refactoring::NativeApplicationContext& context)
1082 {
1083 std::auto_ptr<Toto> toto;
1084 std::auto_ptr<Refactoring::AxialVolumeOrthancLoader> loader1, loader2;
1085
1086 {
1087 Refactoring::NativeApplicationContext::WriterLock lock(context);
1088 toto.reset(new Toto(lock.GetOracleObservable()));
1089 loader1.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable()));
1090 loader2.reset(new Refactoring::AxialVolumeOrthancLoader(lock.GetOracleObservable()));
1091 }
1092
1093 Refactoring::NativeOracle oracle(context);
1094
1095 {
1096 Orthanc::WebServiceParameters p;
1097 //p.SetUrl("http://localhost:8043/");
1098 p.SetCredentials("orthanc", "orthanc");
1099 oracle.SetOrthancParameters(p);
1100 }
1101
1102 oracle.Start();
1103
1104 {
1105 Json::Value v = Json::objectValue;
1106 v["Level"] = "Series";
1107 v["Query"] = Json::objectValue;
1108
1109 std::auto_ptr<Refactoring::OrthancApiOracleCommand> command(new Refactoring::OrthancApiOracleCommand);
1110 command->SetMethod(Orthanc::HttpMethod_Post);
1111 command->SetUri("/tools/find");
1112 command->SetBody(v);
1113
1114 oracle.Schedule(*toto, command.release());
1115 }
1116
1117 // 2017-11-17-Anonymized
1118 loader1->LoadSeries(oracle, "cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT
1119 loader2->LoadInstance(oracle, "41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE
1120
1121 boost::this_thread::sleep(boost::posix_time::seconds(1));
1122
1123 oracle.Stop();
1124 }
1125
1126
1127
1128 /**
1129 * IMPORTANT: The full arguments to "main()" are needed for SDL on
1130 * Windows. Otherwise, one gets the linking error "undefined reference
1131 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows
1132 **/
1133 int main(int argc, char* argv[])
1134 {
1135 OrthancStone::StoneInitialize();
1136 Orthanc::Logging::EnableInfoLevel(true);
1137
1138 try
1139 {
1140 Refactoring::NativeApplicationContext context;
1141 Run(context);
1142 }
1143 catch (Orthanc::OrthancException& e)
1144 {
1145 LOG(ERROR) << "EXCEPTION: " << e.What();
1146 }
1147
1148 OrthancStone::StoneFinalize();
1149
1150 return 0;
1151 }