comparison Samples/Sdl/Loader.cpp @ 815:df442f1ba0c6

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 28 May 2019 21:59:20 +0200
parents aead999345e0
children 270c31978df1 2fd96a637a59
comparison
equal deleted inserted replaced
814:aead999345e0 815:df442f1ba0c6
17 * You should have received a copy of the GNU Affero General Public License 17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/ 19 **/
20 20
21 21
22 #include "../../Framework/Loaders/DicomStructureSetLoader.h"
23 #include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h"
22 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" 24 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
23 #include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" 25 #include "../../Framework/Oracle/SleepOracleCommand.h"
26 #include "../../Framework/Oracle/ThreadedOracle.h"
27 #include "../../Framework/Scene2D/CairoCompositor.h"
24 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" 28 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
25 #include "../../Framework/Scene2D/LookupTableStyleConfigurator.h" 29 #include "../../Framework/Scene2D/LookupTableStyleConfigurator.h"
26 #include "../../Framework/Oracle/ThreadedOracle.h"
27 #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h"
28 #include "../../Framework/Oracle/GetOrthancImageCommand.h"
29 #include "../../Framework/Oracle/OrthancRestApiCommand.h"
30 #include "../../Framework/Oracle/SleepOracleCommand.h"
31 #include "../../Framework/Oracle/OracleCommandExceptionMessage.h"
32
33 // From Stone
34 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h"
35 #include "../../Framework/Loaders/BasicFetchingStrategy.h"
36 #include "../../Framework/Scene2D/CairoCompositor.h"
37 #include "../../Framework/Scene2D/Scene2D.h"
38 #include "../../Framework/Scene2D/PolylineSceneLayer.h"
39 #include "../../Framework/Scene2D/LookupTableTextureSceneLayer.h"
40 #include "../../Framework/StoneInitialization.h" 30 #include "../../Framework/StoneInitialization.h"
41 #include "../../Framework/Toolbox/GeometryToolbox.h" 31 #include "../../Framework/Volumes/VolumeSceneLayerSource.h"
42 #include "../../Framework/Toolbox/SlicesSorter.h" 32 #include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h"
43 #include "../../Framework/Toolbox/DicomStructureSet.h" 33 #include "../../Framework/Volumes/DicomVolumeImageReslicer.h"
44 #include "../../Framework/Volumes/ImageBuffer3D.h"
45 #include "../../Framework/Volumes/VolumeImageGeometry.h"
46 #include "../../Framework/Volumes/VolumeReslicer.h"
47 34
48 // From Orthanc framework 35 // From Orthanc framework
49 #include <Core/DicomFormat/DicomArray.h>
50 #include <Core/Images/Image.h>
51 #include <Core/Images/ImageProcessing.h> 36 #include <Core/Images/ImageProcessing.h>
52 #include <Core/Images/PngWriter.h> 37 #include <Core/Images/PngWriter.h>
53 #include <Core/Endianness.h>
54 #include <Core/Logging.h> 38 #include <Core/Logging.h>
55 #include <Core/OrthancException.h> 39 #include <Core/OrthancException.h>
56 #include <Core/SystemToolbox.h> 40 #include <Core/SystemToolbox.h>
57 #include <Core/Toolbox.h>
58
59
60 #include <EmbeddedResources.h>
61 41
62 42
63 namespace OrthancStone 43 namespace OrthancStone
64 { 44 {
65 /** 45 class NativeApplicationContext : public IMessageEmitter
66 This class is supplied with Oracle commands and will schedule up to 46 {
67 simultaneousDownloads_ of them at the same time, then will schedule the
68 rest once slots become available. It is used, a.o., by the
69 OrtancMultiframeVolumeLoader class.
70 */
71 class LoaderStateMachine : public IObserver
72 {
73 protected:
74 class State : public Orthanc::IDynamicObject
75 {
76 private:
77 LoaderStateMachine& that_;
78
79 public:
80 State(LoaderStateMachine& that) :
81 that_(that)
82 {
83 }
84
85 State(const State& currentState) :
86 that_(currentState.that_)
87 {
88 }
89
90 void Schedule(OracleCommandWithPayload* command) const
91 {
92 that_.Schedule(command);
93 }
94
95 template <typename T>
96 T& GetLoader() const
97 {
98 return dynamic_cast<T&>(that_);
99 }
100
101 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
102 {
103 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
104 }
105
106 virtual void Handle(const GetOrthancImageCommand::SuccessMessage& message)
107 {
108 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
109 }
110
111 virtual void Handle(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
112 {
113 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
114 }
115 };
116
117 void Schedule(OracleCommandWithPayload* command)
118 {
119 std::auto_ptr<OracleCommandWithPayload> protection(command);
120
121 if (command == NULL)
122 {
123 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
124 }
125
126 if (!command->HasPayload())
127 {
128 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
129 "The payload must contain the next state");
130 }
131
132 pendingCommands_.push_back(protection.release());
133 Step();
134 }
135
136 void Start()
137 {
138 if (active_)
139 {
140 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
141 }
142
143 active_ = true;
144
145 for (size_t i = 0; i < simultaneousDownloads_; i++)
146 {
147 Step();
148 }
149 }
150
151 private: 47 private:
152 void Step() 48 boost::shared_mutex mutex_;
153 { 49 MessageBroker broker_;
154 if (!pendingCommands_.empty() && 50 IObservable oracleObservable_;
155 activeCommands_ < simultaneousDownloads_)
156 {
157 oracle_.Schedule(*this, pendingCommands_.front());
158 pendingCommands_.pop_front();
159
160 activeCommands_++;
161 }
162 }
163
164 void Clear()
165 {
166 for (PendingCommands::iterator it = pendingCommands_.begin();
167 it != pendingCommands_.end(); ++it)
168 {
169 delete *it;
170 }
171
172 pendingCommands_.clear();
173 }
174
175 void HandleExceptionMessage(const OracleCommandExceptionMessage& message)
176 {
177 LOG(ERROR) << "Error in the state machine, stopping all processing";
178 Clear();
179 }
180
181 template <typename T>
182 void HandleSuccessMessage(const T& message)
183 {
184 assert(activeCommands_ > 0);
185 activeCommands_--;
186
187 try
188 {
189 dynamic_cast<State&>(message.GetOrigin().GetPayload()).Handle(message);
190 Step();
191 }
192 catch (Orthanc::OrthancException& e)
193 {
194 LOG(ERROR) << "Error in the state machine, stopping all processing: " << e.What();
195 Clear();
196 }
197 }
198
199 typedef std::list<IOracleCommand*> PendingCommands;
200
201 IOracle& oracle_;
202 bool active_;
203 unsigned int simultaneousDownloads_;
204 PendingCommands pendingCommands_;
205 unsigned int activeCommands_;
206
207 public:
208 LoaderStateMachine(IOracle& oracle,
209 IObservable& oracleObservable) :
210 IObserver(oracleObservable.GetBroker()),
211 oracle_(oracle),
212 active_(false),
213 simultaneousDownloads_(4),
214 activeCommands_(0)
215 {
216 oracleObservable.RegisterObserverCallback(
217 new Callable<LoaderStateMachine, OrthancRestApiCommand::SuccessMessage>
218 (*this, &LoaderStateMachine::HandleSuccessMessage));
219
220 oracleObservable.RegisterObserverCallback(
221 new Callable<LoaderStateMachine, GetOrthancImageCommand::SuccessMessage>
222 (*this, &LoaderStateMachine::HandleSuccessMessage));
223
224 oracleObservable.RegisterObserverCallback(
225 new Callable<LoaderStateMachine, GetOrthancWebViewerJpegCommand::SuccessMessage>
226 (*this, &LoaderStateMachine::HandleSuccessMessage));
227
228 oracleObservable.RegisterObserverCallback(
229 new Callable<LoaderStateMachine, OracleCommandExceptionMessage>
230 (*this, &LoaderStateMachine::HandleExceptionMessage));
231 }
232
233 virtual ~LoaderStateMachine()
234 {
235 Clear();
236 }
237
238 bool IsActive() const
239 {
240 return active_;
241 }
242
243 void SetSimultaneousDownloads(unsigned int count)
244 {
245 if (active_)
246 {
247 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
248 }
249 else if (count == 0)
250 {
251 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
252 }
253 else
254 {
255 simultaneousDownloads_ = count;
256 }
257 }
258 };
259
260
261
262 class OrthancMultiframeVolumeLoader :
263 public LoaderStateMachine,
264 public IObservable
265 {
266 private:
267 class LoadRTDoseGeometry : public State
268 {
269 private:
270 std::auto_ptr<Orthanc::DicomMap> dicom_;
271
272 public:
273 LoadRTDoseGeometry(OrthancMultiframeVolumeLoader& that,
274 Orthanc::DicomMap* dicom) :
275 State(that),
276 dicom_(dicom)
277 {
278 if (dicom == NULL)
279 {
280 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
281 }
282
283 }
284
285 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
286 {
287 // Complete the DICOM tags with just-received "Grid Frame Offset Vector"
288 std::string s = Orthanc::Toolbox::StripSpaces(message.GetAnswer());
289 dicom_->SetValue(Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, s, false);
290
291 GetLoader<OrthancMultiframeVolumeLoader>().SetGeometry(*dicom_);
292 }
293 };
294
295
296 static std::string GetSopClassUid(const Orthanc::DicomMap& dicom)
297 {
298 std::string s;
299 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
300 {
301 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
302 "DICOM file without SOP class UID");
303 }
304 else
305 {
306 return s;
307 }
308 }
309
310
311 class LoadGeometry : public State
312 {
313 public:
314 LoadGeometry(OrthancMultiframeVolumeLoader& that) :
315 State(that)
316 {
317 }
318
319 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
320 {
321 OrthancMultiframeVolumeLoader& loader = GetLoader<OrthancMultiframeVolumeLoader>();
322
323 Json::Value body;
324 message.ParseJsonBody(body);
325
326 if (body.type() != Json::objectValue)
327 {
328 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
329 }
330
331 std::auto_ptr<Orthanc::DicomMap> dicom(new Orthanc::DicomMap);
332 dicom->FromDicomAsJson(body);
333
334 if (StringToSopClassUid(GetSopClassUid(*dicom)) == SopClassUid_RTDose)
335 {
336 // Download the "Grid Frame Offset Vector" DICOM tag, that is
337 // mandatory for RT-DOSE, but is too long to be returned by default
338
339 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
340 command->SetUri("/instances/" + loader.GetInstanceId() + "/content/" +
341 Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR.Format());
342 command->SetPayload(new LoadRTDoseGeometry(loader, dicom.release()));
343
344 Schedule(command.release());
345 }
346 else
347 {
348 loader.SetGeometry(*dicom);
349 }
350 }
351 };
352
353
354
355 class LoadTransferSyntax : public State
356 {
357 public:
358 LoadTransferSyntax(OrthancMultiframeVolumeLoader& that) :
359 State(that)
360 {
361 }
362
363 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
364 {
365 GetLoader<OrthancMultiframeVolumeLoader>().SetTransferSyntax(message.GetAnswer());
366 }
367 };
368
369
370 class LoadUncompressedPixelData : public State
371 {
372 public:
373 LoadUncompressedPixelData(OrthancMultiframeVolumeLoader& that) :
374 State(that)
375 {
376 }
377
378 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
379 {
380 GetLoader<OrthancMultiframeVolumeLoader>().SetUncompressedPixelData(message.GetAnswer());
381 }
382 };
383
384
385
386 boost::shared_ptr<DicomVolumeImage> volume_;
387 std::string instanceId_;
388 std::string transferSyntaxUid_;
389
390
391 const std::string& GetInstanceId() const
392 {
393 if (IsActive())
394 {
395 return instanceId_;
396 }
397 else
398 {
399 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
400 }
401 }
402
403
404 void ScheduleFrameDownloads()
405 {
406 if (transferSyntaxUid_.empty() ||
407 !volume_->HasGeometry())
408 {
409 return;
410 }
411 /*
412 1.2.840.10008.1.2 Implicit VR Endian: Default Transfer Syntax for DICOM
413 1.2.840.10008.1.2.1 Explicit VR Little Endian
414 1.2.840.10008.1.2.2 Explicit VR Big Endian
415
416 See https://www.dicomlibrary.com/dicom/transfer-syntax/
417 */
418 if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
419 transferSyntaxUid_ == "1.2.840.10008.1.2.1" ||
420 transferSyntaxUid_ == "1.2.840.10008.1.2.2")
421 {
422 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
423 command->SetHttpHeader("Accept-Encoding", "gzip");
424 command->SetUri("/instances/" + instanceId_ + "/content/" +
425 Orthanc::DICOM_TAG_PIXEL_DATA.Format() + "/0");
426 command->SetPayload(new LoadUncompressedPixelData(*this));
427 Schedule(command.release());
428 }
429 else
430 {
431 throw Orthanc::OrthancException(
432 Orthanc::ErrorCode_NotImplemented,
433 "No support for multiframe instances with transfer syntax: " + transferSyntaxUid_);
434 }
435 }
436
437
438 void SetTransferSyntax(const std::string& transferSyntax)
439 {
440 transferSyntaxUid_ = Orthanc::Toolbox::StripSpaces(transferSyntax);
441 ScheduleFrameDownloads();
442 }
443
444
445 void SetGeometry(const Orthanc::DicomMap& dicom)
446 {
447 DicomInstanceParameters parameters(dicom);
448 volume_->SetDicomParameters(parameters);
449
450 Orthanc::PixelFormat format;
451 if (!parameters.GetImageInformation().ExtractPixelFormat(format, true))
452 {
453 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
454 }
455
456 double spacingZ;
457 switch (parameters.GetSopClassUid())
458 {
459 case SopClassUid_RTDose:
460 spacingZ = parameters.GetThickness();
461 break;
462
463 default:
464 throw Orthanc::OrthancException(
465 Orthanc::ErrorCode_NotImplemented,
466 "No support for multiframe instances with SOP class UID: " + GetSopClassUid(dicom));
467 }
468
469 const unsigned int width = parameters.GetImageInformation().GetWidth();
470 const unsigned int height = parameters.GetImageInformation().GetHeight();
471 const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames();
472
473 {
474 VolumeImageGeometry geometry;
475 geometry.SetSize(width, height, depth);
476 geometry.SetAxialGeometry(parameters.GetGeometry());
477 geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
478 parameters.GetPixelSpacingY(), spacingZ);
479 volume_->Initialize(geometry, format);
480 }
481
482 volume_->GetPixelData().Clear();
483
484 ScheduleFrameDownloads();
485
486 BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
487 }
488
489
490 ORTHANC_FORCE_INLINE
491 static void CopyPixel(uint32_t& target,
492 const void* source)
493 {
494 // TODO - check alignement?
495 target = le32toh(*reinterpret_cast<const uint32_t*>(source));
496 }
497
498
499 template <typename T>
500 void CopyPixelData(const std::string& pixelData)
501 {
502 ImageBuffer3D& target = volume_->GetPixelData();
503
504 const unsigned int bpp = target.GetBytesPerPixel();
505 const unsigned int width = target.GetWidth();
506 const unsigned int height = target.GetHeight();
507 const unsigned int depth = target.GetDepth();
508
509 if (pixelData.size() != bpp * width * height * depth)
510 {
511 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
512 "The pixel data has not the proper size");
513 }
514
515 if (pixelData.empty())
516 {
517 return;
518 }
519
520 const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str());
521
522 for (unsigned int z = 0; z < depth; z++)
523 {
524 ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z);
525
526 assert (writer.GetAccessor().GetWidth() == width &&
527 writer.GetAccessor().GetHeight() == height);
528
529 for (unsigned int y = 0; y < height; y++)
530 {
531 assert(sizeof(T) == Orthanc::GetBytesPerPixel(target.GetFormat()));
532
533 T* target = reinterpret_cast<T*>(writer.GetAccessor().GetRow(y));
534
535 for (unsigned int x = 0; x < width; x++)
536 {
537 CopyPixel(*target, source);
538
539 target ++;
540 source += bpp;
541 }
542 }
543 }
544 }
545
546
547 void SetUncompressedPixelData(const std::string& pixelData)
548 {
549 switch (volume_->GetPixelData().GetFormat())
550 {
551 case Orthanc::PixelFormat_Grayscale32:
552 CopyPixelData<uint32_t>(pixelData);
553 break;
554
555 default:
556 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
557 }
558
559 volume_->IncrementRevision();
560
561 BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
562 }
563
564
565 public:
566 OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
567 IOracle& oracle,
568 IObservable& oracleObservable) :
569 LoaderStateMachine(oracle, oracleObservable),
570 IObservable(oracleObservable.GetBroker()),
571 volume_(volume)
572 {
573 if (volume.get() == NULL)
574 {
575 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
576 }
577 }
578
579
580 void LoadInstance(const std::string& instanceId)
581 {
582 Start();
583
584 instanceId_ = instanceId;
585
586 {
587 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
588 command->SetHttpHeader("Accept-Encoding", "gzip");
589 command->SetUri("/instances/" + instanceId + "/tags");
590 command->SetPayload(new LoadGeometry(*this));
591 Schedule(command.release());
592 }
593
594 {
595 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
596 command->SetUri("/instances/" + instanceId + "/metadata/TransferSyntax");
597 command->SetPayload(new LoadTransferSyntax(*this));
598 Schedule(command.release());
599 }
600 }
601 };
602
603
604 /**
605 This class is able to supply an extract slice for an arbitrary cutting
606 plane through a volume image
607 */
608 class DicomVolumeImageReslicer : public IVolumeSlicer
609 {
610 private:
611 class Slice : public IExtractedSlice
612 {
613 private:
614 DicomVolumeImageReslicer& that_;
615 CoordinateSystem3D cuttingPlane_;
616
617 public:
618 Slice(DicomVolumeImageReslicer& that,
619 const CoordinateSystem3D& cuttingPlane) :
620 that_(that),
621 cuttingPlane_(cuttingPlane)
622 {
623 }
624
625 virtual bool IsValid()
626 {
627 return true;
628 }
629
630 virtual uint64_t GetRevision()
631 {
632 return that_.volume_->GetRevision();
633 }
634
635 virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
636 const CoordinateSystem3D& cuttingPlane)
637 {
638 VolumeReslicer& reslicer = that_.reslicer_;
639
640 if (configurator == NULL)
641 {
642 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
643 "Must provide a layer style configurator");
644 }
645
646 reslicer.SetOutputFormat(that_.volume_->GetPixelData().GetFormat());
647 reslicer.Apply(that_.volume_->GetPixelData(),
648 that_.volume_->GetGeometry(),
649 cuttingPlane);
650
651 if (reslicer.IsSuccess())
652 {
653 std::auto_ptr<TextureBaseSceneLayer> layer
654 (configurator->CreateTextureFromDicom(reslicer.GetOutputSlice(),
655 that_.volume_->GetDicomParameters()));
656 if (layer.get() == NULL)
657 {
658 return NULL;
659 }
660
661 double s = reslicer.GetPixelSpacing();
662 layer->SetPixelSpacing(s, s);
663 layer->SetOrigin(reslicer.GetOutputExtent().GetX1() + 0.5 * s,
664 reslicer.GetOutputExtent().GetY1() + 0.5 * s);
665
666 // TODO - Angle!!
667
668 return layer.release();
669 }
670 else
671 {
672 return NULL;
673 }
674 }
675 };
676
677 boost::shared_ptr<DicomVolumeImage> volume_;
678 VolumeReslicer reslicer_;
679
680 public:
681 DicomVolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume) :
682 volume_(volume)
683 {
684 if (volume.get() == NULL)
685 {
686 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
687 }
688 }
689
690 ImageInterpolation GetInterpolation() const
691 {
692 return reslicer_.GetInterpolation();
693 }
694
695 void SetInterpolation(ImageInterpolation interpolation)
696 {
697 reslicer_.SetInterpolation(interpolation);
698 }
699
700 bool IsFastMode() const
701 {
702 return reslicer_.IsFastMode();
703 }
704
705 void SetFastMode(bool fast)
706 {
707 reslicer_.EnableFastMode(fast);
708 }
709
710 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
711 {
712 if (volume_->HasGeometry())
713 {
714 return new Slice(*this, cuttingPlane);
715 }
716 else
717 {
718 return new IVolumeSlicer::InvalidSlice;
719 }
720 }
721 };
722
723
724
725 class DicomStructureSetLoader :
726 public LoaderStateMachine,
727 public IVolumeSlicer
728 {
729 private:
730 class AddReferencedInstance : public State
731 {
732 private:
733 std::string instanceId_;
734
735 public:
736 AddReferencedInstance(DicomStructureSetLoader& that,
737 const std::string& instanceId) :
738 State(that),
739 instanceId_(instanceId)
740 {
741 }
742
743 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
744 {
745 Json::Value tags;
746 message.ParseJsonBody(tags);
747
748 Orthanc::DicomMap dicom;
749 dicom.FromDicomAsJson(tags);
750
751 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
752 loader.content_->AddReferencedSlice(dicom);
753
754 loader.countProcessedInstances_ ++;
755 assert(loader.countProcessedInstances_ <= loader.countReferencedInstances_);
756
757 if (loader.countProcessedInstances_ == loader.countReferencedInstances_)
758 {
759 // All the referenced instances have been loaded, finalize the RT-STRUCT
760 loader.content_->CheckReferencedSlices();
761 loader.revision_++;
762 }
763 }
764 };
765
766 // State that converts a "SOP Instance UID" to an Orthanc identifier
767 class LookupInstance : public State
768 {
769 private:
770 std::string sopInstanceUid_;
771
772 public:
773 LookupInstance(DicomStructureSetLoader& that,
774 const std::string& sopInstanceUid) :
775 State(that),
776 sopInstanceUid_(sopInstanceUid)
777 {
778 }
779
780 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
781 {
782 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
783
784 Json::Value lookup;
785 message.ParseJsonBody(lookup);
786
787 if (lookup.type() != Json::arrayValue ||
788 lookup.size() != 1 ||
789 !lookup[0].isMember("Type") ||
790 !lookup[0].isMember("Path") ||
791 lookup[0]["Type"].type() != Json::stringValue ||
792 lookup[0]["ID"].type() != Json::stringValue ||
793 lookup[0]["Type"].asString() != "Instance")
794 {
795 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
796 }
797
798 const std::string instanceId = lookup[0]["ID"].asString();
799
800 {
801 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
802 command->SetHttpHeader("Accept-Encoding", "gzip");
803 command->SetUri("/instances/" + instanceId + "/tags");
804 command->SetPayload(new AddReferencedInstance(loader, instanceId));
805 Schedule(command.release());
806 }
807 }
808 };
809
810 class LoadStructure : public State
811 {
812 public:
813 LoadStructure(DicomStructureSetLoader& that) :
814 State(that)
815 {
816 }
817
818 virtual void Handle(const OrthancRestApiCommand::SuccessMessage& message)
819 {
820 DicomStructureSetLoader& loader = GetLoader<DicomStructureSetLoader>();
821
822 {
823 OrthancPlugins::FullOrthancDataset dicom(message.GetAnswer());
824 loader.content_.reset(new DicomStructureSet(dicom));
825 }
826
827 std::set<std::string> instances;
828 loader.content_->GetReferencedInstances(instances);
829
830 loader.countReferencedInstances_ = instances.size();
831
832 for (std::set<std::string>::const_iterator
833 it = instances.begin(); it != instances.end(); ++it)
834 {
835 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
836 command->SetUri("/tools/lookup");
837 command->SetMethod(Orthanc::HttpMethod_Post);
838 command->SetBody(*it);
839 command->SetPayload(new LookupInstance(loader, *it));
840 Schedule(command.release());
841 }
842 }
843 };
844
845
846
847 std::auto_ptr<DicomStructureSet> content_;
848 uint64_t revision_;
849 std::string instanceId_;
850 unsigned int countProcessedInstances_;
851 unsigned int countReferencedInstances_;
852
853
854 class Slice : public IExtractedSlice
855 {
856 private:
857 const DicomStructureSet& content_;
858 uint64_t revision_;
859 bool isValid_;
860
861 public:
862 Slice(const DicomStructureSet& content,
863 uint64_t revision,
864 const CoordinateSystem3D& cuttingPlane) :
865 content_(content),
866 revision_(revision)
867 {
868 bool opposite;
869
870 const Vector normal = content.GetNormal();
871 isValid_ = (
872 GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetNormal()) ||
873 GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisX()) ||
874 GeometryToolbox::IsParallelOrOpposite(opposite, normal, cuttingPlane.GetAxisY()));
875 }
876
877 virtual bool IsValid()
878 {
879 return isValid_;
880 }
881
882 virtual uint64_t GetRevision()
883 {
884 return revision_;
885 }
886
887 virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
888 const CoordinateSystem3D& cuttingPlane)
889 {
890 assert(isValid_);
891
892 std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
893 layer->SetThickness(2);
894
895 for (size_t i = 0; i < content_.GetStructuresCount(); i++)
896 {
897 const Color& color = content_.GetStructureColor(i);
898
899 std::vector< std::vector<DicomStructureSet::PolygonPoint> > polygons;
900
901 if (content_.ProjectStructure(polygons, i, cuttingPlane))
902 {
903 for (size_t j = 0; j < polygons.size(); j++)
904 {
905 PolylineSceneLayer::Chain chain;
906 chain.resize(polygons[j].size());
907
908 for (size_t k = 0; k < polygons[j].size(); k++)
909 {
910 chain[k] = ScenePoint2D(polygons[j][k].first, polygons[j][k].second);
911 }
912
913 layer->AddChain(chain, true /* closed */, color);
914 }
915 }
916 }
917
918 return layer.release();
919 }
920 };
921
922 public:
923 DicomStructureSetLoader(IOracle& oracle,
924 IObservable& oracleObservable) :
925 LoaderStateMachine(oracle, oracleObservable),
926 revision_(0),
927 countProcessedInstances_(0),
928 countReferencedInstances_(0)
929 {
930 }
931
932
933 void LoadInstance(const std::string& instanceId)
934 {
935 Start();
936
937 instanceId_ = instanceId;
938
939 {
940 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
941 command->SetHttpHeader("Accept-Encoding", "gzip");
942 command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3006-0050");
943 command->SetPayload(new LoadStructure(*this));
944 Schedule(command.release());
945 }
946 }
947
948 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
949 {
950 if (content_.get() == NULL)
951 {
952 // Geometry is not available yet
953 return new IVolumeSlicer::InvalidSlice;
954 }
955 else
956 {
957 return new Slice(*content_, revision_, cuttingPlane);
958 }
959 }
960 };
961
962
963
964 class VolumeSceneLayerSource : public boost::noncopyable
965 {
966 private:
967 Scene2D& scene_;
968 int layerDepth_;
969 boost::shared_ptr<IVolumeSlicer> slicer_;
970 std::auto_ptr<ILayerStyleConfigurator> configurator_;
971 std::auto_ptr<CoordinateSystem3D> lastPlane_;
972 uint64_t lastRevision_;
973 uint64_t lastConfiguratorRevision_;
974
975 static bool IsSameCuttingPlane(const CoordinateSystem3D& a,
976 const CoordinateSystem3D& b)
977 {
978 // TODO - What if the normal is reversed?
979 double distance;
980 return (CoordinateSystem3D::ComputeDistance(distance, a, b) &&
981 LinearAlgebra::IsCloseToZero(distance));
982 }
983
984 void ClearLayer()
985 {
986 scene_.DeleteLayer(layerDepth_);
987 lastPlane_.reset(NULL);
988 }
989
990 public:
991 VolumeSceneLayerSource(Scene2D& scene,
992 int layerDepth,
993 const boost::shared_ptr<IVolumeSlicer>& slicer) :
994 scene_(scene),
995 layerDepth_(layerDepth),
996 slicer_(slicer)
997 {
998 if (slicer == NULL)
999 {
1000 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
1001 }
1002 }
1003
1004 const IVolumeSlicer& GetSlicer() const
1005 {
1006 return *slicer_;
1007 }
1008
1009 void RemoveConfigurator()
1010 {
1011 configurator_.reset();
1012 lastPlane_.reset();
1013 }
1014
1015 void SetConfigurator(ILayerStyleConfigurator* configurator) // Takes ownership
1016 {
1017 if (configurator == NULL)
1018 {
1019 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
1020 }
1021
1022 configurator_.reset(configurator);
1023
1024 // Invalidate the layer
1025 lastPlane_.reset(NULL);
1026 }
1027
1028 bool HasConfigurator() const
1029 {
1030 return configurator_.get() != NULL;
1031 }
1032
1033 ILayerStyleConfigurator& GetConfigurator() const
1034 {
1035 if (configurator_.get() == NULL)
1036 {
1037 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1038 }
1039
1040 return *configurator_;
1041 }
1042
1043 void Update(const CoordinateSystem3D& plane)
1044 {
1045 assert(slicer_.get() != NULL);
1046 std::auto_ptr<IVolumeSlicer::IExtractedSlice> slice(slicer_->ExtractSlice(plane));
1047
1048 if (slice.get() == NULL)
1049 {
1050 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
1051 }
1052
1053 if (!slice->IsValid())
1054 {
1055 // The slicer cannot handle this cutting plane: Clear the layer
1056 ClearLayer();
1057 }
1058 else if (lastPlane_.get() != NULL &&
1059 IsSameCuttingPlane(*lastPlane_, plane) &&
1060 lastRevision_ == slice->GetRevision())
1061 {
1062 // The content of the slice has not changed: Don't update the
1063 // layer content, but possibly update its style
1064
1065 if (configurator_.get() != NULL &&
1066 configurator_->GetRevision() != lastConfiguratorRevision_ &&
1067 scene_.HasLayer(layerDepth_))
1068 {
1069 configurator_->ApplyStyle(scene_.GetLayer(layerDepth_));
1070 }
1071 }
1072 else
1073 {
1074 // Content has changed: An update is needed
1075 lastPlane_.reset(new CoordinateSystem3D(plane));
1076 lastRevision_ = slice->GetRevision();
1077
1078 std::auto_ptr<ISceneLayer> layer(slice->CreateSceneLayer(configurator_.get(), plane));
1079 if (layer.get() == NULL)
1080 {
1081 ClearLayer();
1082 }
1083 else
1084 {
1085 if (configurator_.get() != NULL)
1086 {
1087 lastConfiguratorRevision_ = configurator_->GetRevision();
1088 configurator_->ApplyStyle(*layer);
1089 }
1090
1091 scene_.SetLayer(layerDepth_, layer.release());
1092 }
1093 }
1094 }
1095 };
1096
1097
1098
1099
1100
1101 class NativeApplicationContext : public IMessageEmitter
1102 {
1103 private:
1104 boost::shared_mutex mutex_;
1105 MessageBroker broker_;
1106 IObservable oracleObservable_;
1107 51
1108 public: 52 public:
1109 NativeApplicationContext() : 53 NativeApplicationContext() :
1110 oracleObservable_(broker_) 54 oracleObservable_(broker_)
1111 { 55 {