comparison Samples/Sdl/Loader.cpp @ 814:aead999345e0

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 28 May 2019 21:16:39 +0200
parents abc08ac721d3
children df442f1ba0c6
comparison
equal deleted inserted replaced
813:bc7ee59420a1 814:aead999345e0
17 * You should have received a copy of the GNU Affero General Public License 17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/ 19 **/
20 20
21 21
22 #include "../../Framework/Toolbox/DicomInstanceParameters.h" 22 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h"
23 #include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h"
24 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h"
25 #include "../../Framework/Scene2D/LookupTableStyleConfigurator.h"
23 #include "../../Framework/Oracle/ThreadedOracle.h" 26 #include "../../Framework/Oracle/ThreadedOracle.h"
24 #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h" 27 #include "../../Framework/Oracle/GetOrthancWebViewerJpegCommand.h"
25 #include "../../Framework/Oracle/GetOrthancImageCommand.h" 28 #include "../../Framework/Oracle/GetOrthancImageCommand.h"
26 #include "../../Framework/Oracle/OrthancRestApiCommand.h" 29 #include "../../Framework/Oracle/OrthancRestApiCommand.h"
27 #include "../../Framework/Oracle/SleepOracleCommand.h" 30 #include "../../Framework/Oracle/SleepOracleCommand.h"
58 61
59 62
60 namespace OrthancStone 63 namespace OrthancStone
61 { 64 {
62 /** 65 /**
63 This interface is implemented by objects able to create an ISceneLayer
64 suitable to display the Orthanc image supplied to the CreateTextureXX
65 factory methods (taking Dicom parameters into account if relevant).
66
67 It can also refresh the style of an existing layer afterwards, to match
68 the configurator settings.
69 */
70 class ILayerStyleConfigurator
71 {
72 public:
73 virtual ~ILayerStyleConfigurator()
74 {
75 }
76
77 virtual uint64_t GetRevision() const = 0;
78
79 virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const = 0;
80
81 virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame,
82 const DicomInstanceParameters& parameters) const = 0;
83
84 virtual void ApplyStyle(ISceneLayer& layer) const = 0;
85 };
86
87 /**
88 This configurator supplies an API to set a display range and a LUT.
89 */
90 class LookupTableStyleConfigurator : public ILayerStyleConfigurator
91 {
92 private:
93 uint64_t revision_;
94 bool hasLut_;
95 std::string lut_;
96 bool hasRange_;
97 float minValue_;
98 float maxValue_;
99
100 public:
101 LookupTableStyleConfigurator() :
102 revision_(0),
103 hasLut_(false),
104 hasRange_(false)
105 {
106 }
107
108 void SetLookupTable(Orthanc::EmbeddedResources::FileResourceId resource)
109 {
110 hasLut_ = true;
111 Orthanc::EmbeddedResources::GetFileResource(lut_, resource);
112 }
113
114 void SetLookupTable(const std::string& lut)
115 {
116 hasLut_ = true;
117 lut_ = lut;
118 }
119
120 void SetRange(float minValue,
121 float maxValue)
122 {
123 if (minValue > maxValue)
124 {
125 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
126 }
127 else
128 {
129 hasRange_ = true;
130 minValue_ = minValue;
131 maxValue_ = maxValue;
132 }
133 }
134
135 virtual uint64_t GetRevision() const
136 {
137 return revision_;
138 }
139
140 virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const
141 {
142 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
143 }
144
145 virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame,
146 const DicomInstanceParameters& parameters) const
147 {
148 return parameters.CreateLookupTableTexture(frame);
149 }
150
151 virtual void ApplyStyle(ISceneLayer& layer) const
152 {
153 LookupTableTextureSceneLayer& l = dynamic_cast<LookupTableTextureSceneLayer&>(layer);
154
155 if (hasLut_)
156 {
157 l.SetLookupTable(lut_);
158 }
159
160 if (hasRange_)
161 {
162 l.SetRange(minValue_, maxValue_);
163 }
164 else
165 {
166 l.FitRange();
167 }
168 }
169 };
170
171 /**
172 Creates layers to display the supplied image in grayscale. No dynamic
173 style is available.
174 */
175 class GrayscaleStyleConfigurator : public ILayerStyleConfigurator
176 {
177 private:
178 uint64_t revision_;
179
180 public:
181 GrayscaleStyleConfigurator() :
182 revision_(0)
183 {
184 }
185
186 virtual uint64_t GetRevision() const
187 {
188 return revision_;
189 }
190
191 virtual TextureBaseSceneLayer* CreateTextureFromImage(const Orthanc::ImageAccessor& image) const
192 {
193 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
194 }
195
196 virtual TextureBaseSceneLayer* CreateTextureFromDicom(const Orthanc::ImageAccessor& frame,
197 const DicomInstanceParameters& parameters) const
198 {
199 return parameters.CreateTexture(frame);
200 }
201
202 virtual void ApplyStyle(ISceneLayer& layer) const
203 {
204 }
205 };
206
207 /**
208 This interface is implemented by objects representing 3D volume data and
209 that are able to return an object that:
210 - represent a slice of their data
211 - are able to create the corresponding slice visual representation.
212 */
213 class IVolumeSlicer : public boost::noncopyable
214 {
215 public:
216 /**
217 This interface is implemented by objects representing a slice of
218 volume data and that are able to create a 2D layer to display a this
219 slice.
220
221 The CreateSceneLayer factory method is called with an optional
222 configurator that possibly impacts the ISceneLayer subclass that is
223 created (for instance, if a LUT must be applied on the texture when
224 displaying it)
225 */
226 class IExtractedSlice : public boost::noncopyable
227 {
228 public:
229 virtual ~IExtractedSlice()
230 {
231 }
232
233 /**
234 Invalid slices are created when the data is not ready yet or if the
235 cut is outside of the available geometry.
236 */
237 virtual bool IsValid() = 0;
238
239 /**
240 This retrieves the *revision* that gets incremented every time the
241 underlying object undergoes a mutable operation (that it, changes its
242 state).
243 This **must** be a cheap call.
244 */
245 virtual uint64_t GetRevision() = 0;
246
247 /** Creates the slice visual representation */
248 virtual ISceneLayer* CreateSceneLayer(
249 const ILayerStyleConfigurator* configurator, // possibly absent
250 const CoordinateSystem3D& cuttingPlane) = 0;
251 };
252
253 /**
254 See IExtractedSlice.IsValid()
255 */
256 class InvalidSlice : public IExtractedSlice
257 {
258 public:
259 virtual bool IsValid()
260 {
261 return false;
262 }
263
264 virtual uint64_t GetRevision()
265 {
266 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
267 }
268
269 virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
270 const CoordinateSystem3D& cuttingPlane)
271 {
272 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
273 }
274 };
275
276
277 virtual ~IVolumeSlicer()
278 {
279 }
280
281 /**
282 This method is implemented by the objects representing volumetric data
283 and must returns an IExtractedSlice subclass that contains all the data
284 needed to, later on, create its visual representation through
285 CreateSceneLayer.
286 Subclasses a.o.:
287 - InvalidSlice,
288 - DicomVolumeImageMPRSlicer::Slice,
289 - DicomVolumeImageReslicer::Slice
290 - DicomStructureSetLoader::Slice
291 */
292 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0;
293 };
294
295 /**
296 This class combines a 3D image buffer, a 3D volume geometry and
297 information about the DICOM parameters of the series.
298 (MPR means MultiPlanar Reconstruction)
299 */
300 class DicomVolumeImage : public boost::noncopyable
301 {
302 public:
303 ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, DicomVolumeImage);
304 ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentUpdatedMessage, DicomVolumeImage);
305
306 private:
307 uint64_t revision_;
308 std::auto_ptr<VolumeImageGeometry> geometry_;
309 std::auto_ptr<ImageBuffer3D> image_;
310 std::auto_ptr<DicomInstanceParameters> parameters_;
311
312 void CheckHasGeometry() const
313 {
314 if (!HasGeometry())
315 {
316 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
317 }
318 }
319
320 public:
321 DicomVolumeImage() :
322 revision_(0)
323 {
324 }
325
326 void IncrementRevision()
327 {
328 revision_ ++;
329 }
330
331 void Initialize(const VolumeImageGeometry& geometry,
332 Orthanc::PixelFormat format)
333 {
334 geometry_.reset(new VolumeImageGeometry(geometry));
335 image_.reset(new ImageBuffer3D(format, geometry_->GetWidth(), geometry_->GetHeight(),
336 geometry_->GetDepth(), false /* don't compute range */));
337
338 revision_ ++;
339 }
340
341 void SetDicomParameters(const DicomInstanceParameters& parameters)
342 {
343 parameters_.reset(parameters.Clone());
344 revision_ ++;
345 }
346
347 uint64_t GetRevision() const
348 {
349 return revision_;
350 }
351
352 bool HasGeometry() const
353 {
354 return (geometry_.get() != NULL &&
355 image_.get() != NULL);
356 }
357
358 ImageBuffer3D& GetPixelData()
359 {
360 CheckHasGeometry();
361 return *image_;
362 }
363
364 const ImageBuffer3D& GetPixelData() const
365 {
366 CheckHasGeometry();
367 return *image_;
368 }
369
370 const VolumeImageGeometry& GetGeometry() const
371 {
372 CheckHasGeometry();
373 return *geometry_;
374 }
375
376 bool HasDicomParameters() const
377 {
378 return parameters_.get() != NULL;
379 }
380
381 const DicomInstanceParameters& GetDicomParameters() const
382 {
383 if (HasDicomParameters())
384 {
385 return *parameters_;
386 }
387 else
388 {
389 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
390 }
391 }
392 };
393
394 /**
395 Implements the IVolumeSlicer on Dicom volume data when the cutting plane
396 that is supplied to the slicer is either axial, sagittal or coronal.
397 Arbitrary planes are *not* supported
398 */
399 class DicomVolumeImageMPRSlicer : public IVolumeSlicer
400 {
401 public:
402 class Slice : public IExtractedSlice
403 {
404 private:
405 const DicomVolumeImage& volume_;
406 bool valid_;
407 VolumeProjection projection_;
408 unsigned int sliceIndex_;
409
410 void CheckValid() const
411 {
412 if (!valid_)
413 {
414 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
415 }
416 }
417
418 protected:
419 // Can be overloaded in subclasses
420 virtual uint64_t GetRevisionInternal(VolumeProjection projection,
421 unsigned int sliceIndex) const
422 {
423 return volume_.GetRevision();
424 }
425
426 public:
427 /**
428 Represents a slice of a volume image that is parallel to the
429 coordinate system axis.
430 The constructor initializes the type of projection (axial, sagittal or
431 coronal) and the corresponding slice index, from the cutting plane.
432 */
433 Slice(const DicomVolumeImage& volume,
434 const CoordinateSystem3D& cuttingPlane) :
435 volume_(volume)
436 {
437 valid_ = (volume_.HasDicomParameters() &&
438 volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane));
439 }
440
441 VolumeProjection GetProjection() const
442 {
443 CheckValid();
444 return projection_;
445 }
446
447 unsigned int GetSliceIndex() const
448 {
449 CheckValid();
450 return sliceIndex_;
451 }
452
453 virtual bool IsValid()
454 {
455 return valid_;
456 }
457
458 virtual uint64_t GetRevision()
459 {
460 CheckValid();
461 return GetRevisionInternal(projection_, sliceIndex_);
462 }
463
464 virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator,
465 const CoordinateSystem3D& cuttingPlane)
466 {
467 CheckValid();
468
469 if (configurator == NULL)
470 {
471 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer,
472 "A style configurator is mandatory for textures");
473 }
474
475 std::auto_ptr<TextureBaseSceneLayer> texture;
476
477 {
478 const DicomInstanceParameters& parameters = volume_.GetDicomParameters();
479 ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_);
480 texture.reset(dynamic_cast<TextureBaseSceneLayer*>
481 (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters)));
482 }
483
484 const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_);
485
486 double x0, y0, x1, y1;
487 cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin());
488 cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX());
489 texture->SetOrigin(x0, y0);
490
491 double dx = x1 - x0;
492 double dy = y1 - y0;
493 if (!LinearAlgebra::IsCloseToZero(dx) ||
494 !LinearAlgebra::IsCloseToZero(dy))
495 {
496 texture->SetAngle(atan2(dy, dx));
497 }
498
499 Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_);
500 texture->SetPixelSpacing(tmp[0], tmp[1]);
501
502 return texture.release();
503
504 #if 0
505 double w = texture->GetTexture().GetWidth() * tmp[0];
506 double h = texture->GetTexture().GetHeight() * tmp[1];
507 printf("%.1f %.1f %.1f => %.1f %.1f => %.1f %.1f\n",
508 system.GetOrigin() [0],
509 system.GetOrigin() [1],
510 system.GetOrigin() [2],
511 x0, y0, x0 + w, y0 + h);
512
513 std::auto_ptr<PolylineSceneLayer> toto(new PolylineSceneLayer);
514
515 PolylineSceneLayer::Chain c;
516 c.push_back(ScenePoint2D(x0, y0));
517 c.push_back(ScenePoint2D(x0 + w, y0));
518 c.push_back(ScenePoint2D(x0 + w, y0 + h));
519 c.push_back(ScenePoint2D(x0, y0 + h));
520
521 toto->AddChain(c, true);
522
523 return toto.release();
524 #endif
525 }
526 };
527
528 private:
529 boost::shared_ptr<DicomVolumeImage> volume_;
530
531 public:
532 DicomVolumeImageMPRSlicer(const boost::shared_ptr<DicomVolumeImage>& volume) :
533 volume_(volume)
534 {
535 }
536
537 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) ORTHANC_OVERRIDE
538 {
539 if (volume_->HasGeometry())
540 {
541 return new Slice(*volume_, cuttingPlane);
542 }
543 else
544 {
545 return new IVolumeSlicer::InvalidSlice;
546 }
547 }
548 };
549
550
551
552
553 /**
554 This class is used to manage the progressive loading of a volume that
555 is stored in a Dicom series.
556
557 // TODO - Refactor using LoaderStateMachine?
558 // TODO:
559 */
560 class OrthancSeriesVolumeProgressiveLoader :
561 public IObserver,
562 public IObservable,
563 public IVolumeSlicer
564 {
565 private:
566 static const unsigned int LOW_QUALITY = 0;
567 static const unsigned int MIDDLE_QUALITY = 1;
568 static const unsigned int BEST_QUALITY = 2;
569
570 /** Helper class internal to OrthancSeriesVolumeProgressiveLoader */
571 class SeriesGeometry : public boost::noncopyable
572 {
573 private:
574 void CheckSlice(size_t index,
575 const DicomInstanceParameters& reference) const
576 {
577 const DicomInstanceParameters& slice = *slices_[index];
578
579 if (!GeometryToolbox::IsParallel(
580 reference.GetGeometry().GetNormal(),
581 slice.GetGeometry().GetNormal()))
582 {
583 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
584 "A slice in the volume image is not parallel to the others");
585 }
586
587 if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
588 {
589 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
590 "The pixel format changes across the slices of the volume image");
591 }
592
593 if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
594 reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
595 {
596 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
597 "The width/height of slices are not constant in the volume image");
598 }
599
600 if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
601 !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
602 {
603 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
604 "The pixel spacing of the slices change across the volume image");
605 }
606 }
607
608
609 void CheckVolume() const
610 {
611 for (size_t i = 0; i < slices_.size(); i++)
612 {
613 assert(slices_[i] != NULL);
614 if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
615 {
616 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
617 "This class does not support multi-frame images");
618 }
619 }
620
621 if (slices_.size() != 0)
622 {
623 const DicomInstanceParameters& reference = *slices_[0];
624
625 for (size_t i = 1; i < slices_.size(); i++)
626 {
627 CheckSlice(i, reference);
628 }
629 }
630 }
631
632
633 void Clear()
634 {
635 for (size_t i = 0; i < slices_.size(); i++)
636 {
637 assert(slices_[i] != NULL);
638 delete slices_[i];
639 }
640
641 slices_.clear();
642 slicesRevision_.clear();
643 }
644
645
646 void CheckSliceIndex(size_t index) const
647 {
648 if (!HasGeometry())
649 {
650 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
651 }
652 else if (index >= slices_.size())
653 {
654 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
655 }
656 else
657 {
658 assert(slices_.size() == GetImageGeometry().GetDepth() &&
659 slices_.size() == slicesRevision_.size());
660 }
661 }
662
663
664 std::auto_ptr<VolumeImageGeometry> geometry_;
665 std::vector<DicomInstanceParameters*> slices_;
666 std::vector<uint64_t> slicesRevision_;
667
668 public:
669 ~SeriesGeometry()
670 {
671 Clear();
672 }
673
674 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
675 // (called with the slices created in LoadGeometry)
676 void ComputeGeometry(SlicesSorter& slices)
677 {
678 Clear();
679
680 if (!slices.Sort())
681 {
682 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
683 "Cannot sort the 3D slices of a DICOM series");
684 }
685
686 if (slices.GetSlicesCount() == 0)
687 {
688 geometry_.reset(new VolumeImageGeometry);
689 }
690 else
691 {
692 slices_.reserve(slices.GetSlicesCount());
693 slicesRevision_.resize(slices.GetSlicesCount(), 0);
694
695 for (size_t i = 0; i < slices.GetSlicesCount(); i++)
696 {
697 const DicomInstanceParameters& slice =
698 dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
699 slices_.push_back(new DicomInstanceParameters(slice));
700 }
701
702 CheckVolume();
703
704 const double spacingZ = slices.ComputeSpacingBetweenSlices();
705 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
706
707 const DicomInstanceParameters& parameters = *slices_[0];
708
709 geometry_.reset(new VolumeImageGeometry);
710 geometry_->SetSize(parameters.GetImageInformation().GetWidth(),
711 parameters.GetImageInformation().GetHeight(),
712 static_cast<unsigned int>(slices.GetSlicesCount()));
713 geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
714 geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
715 parameters.GetPixelSpacingY(), spacingZ);
716 }
717 }
718
719 bool HasGeometry() const
720 {
721 return geometry_.get() != NULL;
722 }
723
724 const VolumeImageGeometry& GetImageGeometry() const
725 {
726 if (!HasGeometry())
727 {
728 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
729 }
730 else
731 {
732 assert(slices_.size() == geometry_->GetDepth());
733 return *geometry_;
734 }
735 }
736
737 const DicomInstanceParameters& GetSliceParameters(size_t index) const
738 {
739 CheckSliceIndex(index);
740 return *slices_[index];
741 }
742
743 uint64_t GetSliceRevision(size_t index) const
744 {
745 CheckSliceIndex(index);
746 return slicesRevision_[index];
747 }
748
749 void IncrementSliceRevision(size_t index)
750 {
751 CheckSliceIndex(index);
752 slicesRevision_[index] ++;
753 }
754 };
755
756
757 class ExtractedSlice : public DicomVolumeImageMPRSlicer::Slice
758 {
759 private:
760 const OrthancSeriesVolumeProgressiveLoader& that_;
761
762 protected:
763 virtual uint64_t GetRevisionInternal(VolumeProjection projection,
764 unsigned int sliceIndex) const
765 {
766 if (projection == VolumeProjection_Axial)
767 {
768 return that_.seriesGeometry_.GetSliceRevision(sliceIndex);
769 }
770 else
771 {
772 // For coronal and sagittal projections, we take the global
773 // revision of the volume because even if a single slice changes,
774 // this means the projection will yield a different result -->
775 // we must increase the revision as soon as any slice changes
776 return that_.volume_->GetRevision();
777 }
778 }
779
780 public:
781 ExtractedSlice(const OrthancSeriesVolumeProgressiveLoader& that,
782 const CoordinateSystem3D& plane) :
783 DicomVolumeImageMPRSlicer::Slice(*that.volume_, plane),
784 that_(that)
785 {
786 if (that_.strategy_.get() != NULL &&
787 IsValid() &&
788 GetProjection() == VolumeProjection_Axial)
789 {
790 that_.strategy_->SetCurrent(GetSliceIndex());
791 }
792 }
793 };
794
795
796
797 static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command)
798 {
799 return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
800 }
801
802
803 void ScheduleNextSliceDownload()
804 {
805 assert(strategy_.get() != NULL);
806
807 unsigned int sliceIndex, quality;
808
809 if (strategy_->GetNext(sliceIndex, quality))
810 {
811 assert(quality <= BEST_QUALITY);
812
813 const DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex);
814
815 const std::string& instance = slice.GetOrthancInstanceIdentifier();
816 if (instance.empty())
817 {
818 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
819 }
820
821 std::auto_ptr<OracleCommandWithPayload> command;
822
823 if (quality == BEST_QUALITY)
824 {
825 std::auto_ptr<GetOrthancImageCommand> tmp(new GetOrthancImageCommand);
826 tmp->SetHttpHeader("Accept-Encoding", "gzip");
827 tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam)));
828 tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat());
829 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
830 command.reset(tmp.release());
831 }
832 else
833 {
834 std::auto_ptr<GetOrthancWebViewerJpegCommand> tmp(new GetOrthancWebViewerJpegCommand);
835 tmp->SetHttpHeader("Accept-Encoding", "gzip");
836 tmp->SetInstance(instance);
837 tmp->SetQuality((quality == 0 ? 50 : 90));
838 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat());
839 command.reset(tmp.release());
840 }
841
842 command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex));
843 oracle_.Schedule(*this, command.release());
844 }
845 }
846
847 /**
848 This is called in response to GET "/series/XXXXXXXXXXXXX/instances-tags"
849 */
850 void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message)
851 {
852 Json::Value body;
853 message.ParseJsonBody(body);
854
855 if (body.type() != Json::objectValue)
856 {
857 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
858 }
859
860 {
861 Json::Value::Members instances = body.getMemberNames();
862
863 SlicesSorter slices;
864
865 for (size_t i = 0; i < instances.size(); i++)
866 {
867 Orthanc::DicomMap dicom;
868 dicom.FromDicomAsJson(body[instances[i]]);
869
870 std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom));
871 instance->SetOrthancInstanceIdentifier(instances[i]);
872
873 // the 3D plane corresponding to the slice
874 CoordinateSystem3D geometry = instance->GetGeometry();
875 slices.AddSlice(geometry, instance.release());
876 }
877
878 seriesGeometry_.ComputeGeometry(slices);
879 }
880
881 size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth();
882
883 if (slicesCount == 0)
884 {
885 volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8);
886 }
887 else
888 {
889 const DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0);
890
891 volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat());
892 volume_->SetDicomParameters(parameters);
893 volume_->GetPixelData().Clear();
894
895 strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(static_cast<unsigned int>(slicesCount)), BEST_QUALITY));
896
897 assert(simultaneousDownloads_ != 0);
898 for (unsigned int i = 0; i < simultaneousDownloads_; i++)
899 {
900 ScheduleNextSliceDownload();
901 }
902 }
903
904 slicesQuality_.resize(slicesCount, 0);
905
906 BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
907 }
908
909
910 void SetSliceContent(unsigned int sliceIndex,
911 const Orthanc::ImageAccessor& image,
912 unsigned int quality)
913 {
914 assert(sliceIndex < slicesQuality_.size() &&
915 slicesQuality_.size() == volume_->GetPixelData().GetDepth());
916
917 if (quality >= slicesQuality_[sliceIndex])
918 {
919 {
920 ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), VolumeProjection_Axial, sliceIndex);
921 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
922 }
923
924 volume_->IncrementRevision();
925 seriesGeometry_.IncrementSliceRevision(sliceIndex);
926 slicesQuality_[sliceIndex] = quality;
927
928 BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
929 }
930
931 ScheduleNextSliceDownload();
932 }
933
934
935 void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message)
936 {
937 SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY);
938 }
939
940
941 void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
942 {
943 unsigned int quality;
944
945 switch (message.GetOrigin().GetQuality())
946 {
947 case 50:
948 quality = LOW_QUALITY;
949 break;
950
951 case 90:
952 quality = MIDDLE_QUALITY;
953 break;
954
955 default:
956 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
957 }
958
959 SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
960 }
961
962
963 IOracle& oracle_;
964 bool active_;
965 unsigned int simultaneousDownloads_;
966 SeriesGeometry seriesGeometry_;
967
968 boost::shared_ptr<DicomVolumeImage> volume_;
969 std::auto_ptr<IFetchingItemsSorter::IFactory> sorter_;
970 std::auto_ptr<IFetchingStrategy> strategy_;
971 std::vector<unsigned int> slicesQuality_;
972
973
974 public:
975 OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
976 IOracle& oracle,
977 IObservable& oracleObservable) :
978 IObserver(oracleObservable.GetBroker()),
979 IObservable(oracleObservable.GetBroker()),
980 oracle_(oracle),
981 active_(false),
982 simultaneousDownloads_(4),
983 volume_(volume),
984 sorter_(new BasicFetchingItemsSorter::Factory)
985 {
986 oracleObservable.RegisterObserverCallback(
987 new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage>
988 (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry));
989
990 oracleObservable.RegisterObserverCallback(
991 new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancImageCommand::SuccessMessage>
992 (*this, &OrthancSeriesVolumeProgressiveLoader::LoadBestQualitySliceContent));
993
994 oracleObservable.RegisterObserverCallback(
995 new Callable<OrthancSeriesVolumeProgressiveLoader, GetOrthancWebViewerJpegCommand::SuccessMessage>
996 (*this, &OrthancSeriesVolumeProgressiveLoader::LoadJpegSliceContent));
997 }
998
999 void SetSimultaneousDownloads(unsigned int count)
1000 {
1001 if (active_)
1002 {
1003 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1004 }
1005 else if (count == 0)
1006 {
1007 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
1008 }
1009 else
1010 {
1011 simultaneousDownloads_ = count;
1012 }
1013 }
1014
1015 void LoadSeries(const std::string& seriesId)
1016 {
1017 if (active_)
1018 {
1019 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
1020 }
1021 else
1022 {
1023 active_ = true;
1024
1025 std::auto_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
1026 command->SetUri("/series/" + seriesId + "/instances-tags");
1027
1028 oracle_.Schedule(*this, command.release());
1029 }
1030 }
1031
1032 /**
1033 When a slice is requested, the strategy algorithm (that defines the
1034 sequence of resources to be loaded from the server) is modified to
1035 take into account this request (this is done in the ExtractedSlice ctor)
1036 */
1037 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
1038 {
1039 if (volume_->HasGeometry())
1040 {
1041 return new ExtractedSlice(*this, cuttingPlane);
1042 }
1043 else
1044 {
1045 return new IVolumeSlicer::InvalidSlice;
1046 }
1047 }
1048 };
1049
1050
1051 /**
1052 This class is supplied with Oracle commands and will schedule up to 66 This class is supplied with Oracle commands and will schedule up to
1053 simultaneousDownloads_ of them at the same time, then will schedule the 67 simultaneousDownloads_ of them at the same time, then will schedule the
1054 rest once slots become available. It is used, a.o., by the 68 rest once slots become available. It is used, a.o., by the
1055 OrtancMultiframeVolumeLoader class. 69 OrtancMultiframeVolumeLoader class.
1056 */ 70 */