comparison Samples/Sdl/Loader.cpp @ 783:cd13a062c9bd

DicomVolumeImage
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 27 May 2019 15:54:53 +0200
parents b24c208fa953
children 9f3b2027a4a9 e76c4eef1054
comparison
equal deleted inserted replaced
782:b24c208fa953 783:cd13a062c9bd
190 { 190 {
191 } 191 }
192 }; 192 };
193 193
194 194
195 class IVolumeImage : public boost::noncopyable
196 {
197 public:
198 ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, IVolumeImage);
199 ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentUpdatedMessage, IVolumeImage);
200
201 virtual ~IVolumeImage()
202 {
203 }
204
205 virtual uint64_t GetRevision() const = 0;
206
207 virtual bool HasGeometry() const = 0;
208
209 virtual const ImageBuffer3D& GetPixelData() const = 0;
210
211 virtual const VolumeImageGeometry& GetGeometry() const = 0;
212
213 virtual bool HasDicomParameters() const = 0;
214
215 virtual const DicomInstanceParameters& GetDicomParameters() const = 0;
216 };
217
218
219 class IVolumeSlicer : public boost::noncopyable 195 class IVolumeSlicer : public boost::noncopyable
220 { 196 {
221 public: 197 public:
222 class IExtractedSlice : public boost::noncopyable 198 class IExtractedSlice : public boost::noncopyable
223 { 199 {
264 240
265 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0; 241 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) = 0;
266 }; 242 };
267 243
268 244
245
246 // This class combines a 3D image buffer, a 3D volume geometry and
247 // information about the DICOM parameters of the series.
248 class DicomVolumeImage : public boost::noncopyable
249 {
250 public:
251 ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, GeometryReadyMessage, DicomVolumeImage);
252 ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, ContentUpdatedMessage, DicomVolumeImage);
253
254 private:
255 uint64_t revision_;
256 std::auto_ptr<VolumeImageGeometry> geometry_;
257 std::auto_ptr<ImageBuffer3D> image_;
258 std::auto_ptr<DicomInstanceParameters> parameters_;
259
260 void CheckHasGeometry() const
261 {
262 if (!HasGeometry())
263 {
264 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
265 }
266 }
267
268 public:
269 DicomVolumeImage() :
270 revision_(0)
271 {
272 }
273
274 void IncrementRevision()
275 {
276 revision_ ++;
277 }
278
279 void Initialize(const VolumeImageGeometry& geometry,
280 Orthanc::PixelFormat format)
281 {
282 geometry_.reset(new VolumeImageGeometry(geometry));
283 image_.reset(new ImageBuffer3D(format, geometry_->GetWidth(), geometry_->GetHeight(),
284 geometry_->GetDepth(), false /* don't compute range */));
285
286 revision_ ++;
287 }
288
289 void SetDicomParameters(const DicomInstanceParameters& parameters)
290 {
291 parameters_.reset(parameters.Clone());
292 revision_ ++;
293 }
294
295 uint64_t GetRevision() const
296 {
297 return revision_;
298 }
299
300 bool HasGeometry() const
301 {
302 return (geometry_.get() != NULL &&
303 image_.get() != NULL);
304 }
305
306 ImageBuffer3D& GetPixelData()
307 {
308 CheckHasGeometry();
309 return *image_;
310 }
311
312 const ImageBuffer3D& GetPixelData() const
313 {
314 CheckHasGeometry();
315 return *image_;
316 }
317
318 const VolumeImageGeometry& GetGeometry() const
319 {
320 CheckHasGeometry();
321 return *geometry_;
322 }
323
324 bool HasDicomParameters() const
325 {
326 return parameters_.get() != NULL;
327 }
328
329 const DicomInstanceParameters& GetDicomParameters() const
330 {
331 if (HasDicomParameters())
332 {
333 return *parameters_;
334 }
335 else
336 {
337 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
338 }
339 }
340 };
341
342
343
269 class DicomVolumeImageOrthogonalSlice : public IVolumeSlicer::IExtractedSlice 344 class DicomVolumeImageOrthogonalSlice : public IVolumeSlicer::IExtractedSlice
270 { 345 {
271 private: 346 private:
272 const IVolumeImage& volume_; 347 const DicomVolumeImage& volume_;
273 bool valid_; 348 bool valid_;
274 VolumeProjection projection_; 349 VolumeProjection projection_;
275 unsigned int sliceIndex_; 350 unsigned int sliceIndex_;
276 351
277 void CheckValid() const 352 void CheckValid() const
281 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 356 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
282 } 357 }
283 } 358 }
284 359
285 protected: 360 protected:
361 // Can be overloaded in subclasses
286 virtual uint64_t GetRevisionInternal(VolumeProjection projection, 362 virtual uint64_t GetRevisionInternal(VolumeProjection projection,
287 unsigned int sliceIndex) const = 0; 363 unsigned int sliceIndex) const
364 {
365 return volume_.GetRevision();
366 }
288 367
289 public: 368 public:
290 DicomVolumeImageOrthogonalSlice(const IVolumeImage& volume, 369 DicomVolumeImageOrthogonalSlice(const DicomVolumeImage& volume,
291 const CoordinateSystem3D& cuttingPlane) : 370 const CoordinateSystem3D& cuttingPlane) :
292 volume_(volume) 371 volume_(volume)
293 { 372 {
294 valid_ = (volume_.HasDicomParameters() && 373 valid_ = (volume_.HasDicomParameters() &&
295 volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane)); 374 volume_.GetGeometry().DetectSlice(projection_, sliceIndex_, cuttingPlane));
380 return toto.release(); 459 return toto.release();
381 #endif 460 #endif
382 } 461 }
383 }; 462 };
384 463
385
386 class VolumeImageBase : public IVolumeImage
387 {
388 private:
389 uint64_t revision_;
390 std::auto_ptr<VolumeImageGeometry> geometry_;
391 std::auto_ptr<ImageBuffer3D> image_;
392 std::auto_ptr<DicomInstanceParameters> parameters_;
393
394 protected:
395 void Finalize()
396 {
397 geometry_.reset();
398 image_.reset();
399
400 revision_ ++;
401 }
402
403 void Initialize(VolumeImageGeometry* geometry, // Takes ownership
404 Orthanc::PixelFormat format)
405 {
406 if (geometry == NULL)
407 {
408 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
409 }
410
411 geometry_.reset(geometry);
412 image_.reset(new ImageBuffer3D(format, geometry_->GetWidth(), geometry_->GetHeight(),
413 geometry_->GetDepth(), false /* don't compute range */));
414
415 revision_ ++;
416 }
417
418 void SetDicomParameters(const DicomInstanceParameters& parameters)
419 {
420 parameters_.reset(parameters.Clone());
421 revision_ ++;
422 }
423
424 ImageBuffer3D& GetPixelData()
425 {
426 return *image_;
427 }
428
429 void IncrementRevision()
430 {
431 revision_ ++;
432 }
433
434 public:
435 VolumeImageBase() :
436 revision_(0)
437 {
438 }
439
440 virtual uint64_t GetRevision() const
441 {
442 return revision_;
443 }
444
445 virtual bool HasGeometry() const
446 {
447 return (geometry_.get() != NULL &&
448 image_.get() != NULL);
449 }
450
451 virtual const ImageBuffer3D& GetPixelData() const
452 {
453 if (HasGeometry())
454 {
455 return *image_;
456 }
457 else
458 {
459 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
460 }
461 }
462
463 virtual const VolumeImageGeometry& GetGeometry() const
464 {
465 if (HasGeometry())
466 {
467 return *geometry_;
468 }
469 else
470 {
471 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
472 }
473 }
474
475 virtual bool HasDicomParameters() const
476 {
477 return parameters_.get() != NULL;
478 }
479
480 virtual const DicomInstanceParameters& GetDicomParameters() const
481 {
482 if (HasDicomParameters())
483 {
484 return *parameters_;
485 }
486 else
487 {
488 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
489 }
490 }
491 };
492
493 464
494
495
496 // This class combines a 3D image buffer, a 3D volume geometry and
497 // information about the DICOM parameters of each slice.
498 class DicomSeriesVolumeImage : public VolumeImageBase
499 {
500 public:
501 class ExtractedOrthogonalSlice : public DicomVolumeImageOrthogonalSlice
502 {
503 private:
504 const DicomSeriesVolumeImage& that_;
505
506 protected:
507 virtual uint64_t GetRevisionInternal(VolumeProjection projection,
508 unsigned int sliceIndex) const
509 {
510 if (projection == VolumeProjection_Axial)
511 {
512 return that_.GetSliceRevision(sliceIndex);
513 }
514 else
515 {
516 // For coronal and sagittal projections, we take the global
517 // revision of the volume
518 return that_.GetRevision();
519 }
520 }
521
522 public:
523 ExtractedOrthogonalSlice(const DicomSeriesVolumeImage& that,
524 const CoordinateSystem3D& plane) :
525 DicomVolumeImageOrthogonalSlice(that, plane),
526 that_(that)
527 {
528 }
529 };
530
531
532 private:
533 std::vector<DicomInstanceParameters*> slices_;
534 std::vector<uint64_t> slicesRevision_;
535 std::vector<unsigned int> slicesQuality_;
536
537 void CheckSlice(size_t index,
538 const DicomInstanceParameters& reference) const
539 {
540 const DicomInstanceParameters& slice = *slices_[index];
541
542 if (!GeometryToolbox::IsParallel(
543 reference.GetGeometry().GetNormal(),
544 slice.GetGeometry().GetNormal()))
545 {
546 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
547 "A slice in the volume image is not parallel to the others");
548 }
549
550 if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
551 {
552 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
553 "The pixel format changes across the slices of the volume image");
554 }
555
556 if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
557 reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
558 {
559 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
560 "The width/height of slices are not constant in the volume image");
561 }
562
563 if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
564 !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
565 {
566 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
567 "The pixel spacing of the slices change across the volume image");
568 }
569 }
570
571
572 void CheckVolume() const
573 {
574 for (size_t i = 0; i < slices_.size(); i++)
575 {
576 assert(slices_[i] != NULL);
577 if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
578 {
579 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
580 "This class does not support multi-frame images");
581 }
582 }
583
584 if (slices_.size() != 0)
585 {
586 const DicomInstanceParameters& reference = *slices_[0];
587
588 for (size_t i = 1; i < slices_.size(); i++)
589 {
590 CheckSlice(i, reference);
591 }
592 }
593 }
594
595
596 void Clear()
597 {
598 VolumeImageBase::Finalize();
599
600 for (size_t i = 0; i < slices_.size(); i++)
601 {
602 assert(slices_[i] != NULL);
603 delete slices_[i];
604 }
605
606 slices_.clear();
607 slicesRevision_.clear();
608 slicesQuality_.clear();
609 }
610
611
612 void CheckSliceIndex(size_t index) const
613 {
614 assert(slices_.size() == GetPixelData().GetDepth() &&
615 slices_.size() == slicesRevision_.size());
616
617 if (!HasGeometry())
618 {
619 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
620 }
621 else if (index >= slices_.size())
622 {
623 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
624 }
625 }
626
627
628 public:
629 ~DicomSeriesVolumeImage()
630 {
631 Clear();
632 }
633
634 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
635 void SetGeometry(SlicesSorter& slices)
636 {
637 Clear();
638
639 if (!slices.Sort())
640 {
641 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
642 "Cannot sort the 3D slices of a DICOM series");
643 }
644
645 if (slices.GetSlicesCount() == 0)
646 {
647 // Empty volume
648 VolumeImageBase::Initialize(new VolumeImageGeometry, Orthanc::PixelFormat_Grayscale8);
649 }
650 else
651 {
652 slices_.reserve(slices.GetSlicesCount());
653 slicesRevision_.resize(slices.GetSlicesCount(), 0);
654 slicesQuality_.resize(slices.GetSlicesCount(), 0);
655
656 for (size_t i = 0; i < slices.GetSlicesCount(); i++)
657 {
658 const DicomInstanceParameters& slice =
659 dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
660 slices_.push_back(new DicomInstanceParameters(slice));
661 }
662
663 CheckVolume();
664
665 const double spacingZ = slices.ComputeSpacingBetweenSlices();
666 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
667
668 const DicomInstanceParameters& parameters = *slices_[0];
669
670 std::auto_ptr<VolumeImageGeometry> geometry(new VolumeImageGeometry);
671 geometry->SetSize(parameters.GetImageInformation().GetWidth(),
672 parameters.GetImageInformation().GetHeight(),
673 static_cast<unsigned int>(slices.GetSlicesCount()));
674 geometry->SetAxialGeometry(slices.GetSliceGeometry(0));
675 geometry->SetVoxelDimensions(parameters.GetPixelSpacingX(),
676 parameters.GetPixelSpacingY(), spacingZ);
677
678 VolumeImageBase::Initialize(geometry.release(), parameters.GetExpectedPixelFormat());
679 VolumeImageBase::SetDicomParameters(parameters);
680 }
681
682 GetPixelData().Clear();
683 }
684
685 size_t GetSlicesCount() const
686 {
687 if (!HasGeometry())
688 {
689 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
690 }
691 else
692 {
693 return slices_.size();
694 }
695 }
696
697 const DicomInstanceParameters& GetSliceParameters(size_t index) const
698 {
699 CheckSliceIndex(index);
700 return *slices_[index];
701 }
702
703 uint64_t GetSliceRevision(size_t index) const
704 {
705 CheckSliceIndex(index);
706 return slicesRevision_[index];
707 }
708
709 void SetSliceContent(size_t index,
710 const Orthanc::ImageAccessor& image,
711 unsigned int quality)
712 {
713 CheckSliceIndex(index);
714
715 // If a better image quality is already available, don't update the content
716 if (quality >= slicesQuality_[index])
717 {
718 {
719 ImageBuffer3D::SliceWriter writer
720 (GetPixelData(), VolumeProjection_Axial, static_cast<unsigned int>(index));
721 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
722 }
723
724 slicesRevision_[index] += 1;
725
726 VolumeImageBase::IncrementRevision();
727 }
728 }
729 };
730 465
731 466
732 467
733 class OrthancSeriesVolumeProgressiveLoader : 468 class OrthancSeriesVolumeProgressiveLoader :
734 public IObserver, 469 public IObserver,
739 static const unsigned int LOW_QUALITY = 0; 474 static const unsigned int LOW_QUALITY = 0;
740 static const unsigned int MIDDLE_QUALITY = 1; 475 static const unsigned int MIDDLE_QUALITY = 1;
741 static const unsigned int BEST_QUALITY = 2; 476 static const unsigned int BEST_QUALITY = 2;
742 477
743 478
479 // Helper class internal to OrthancSeriesVolumeProgressiveLoader
480 class SeriesGeometry : public boost::noncopyable
481 {
482 private:
483 void CheckSlice(size_t index,
484 const DicomInstanceParameters& reference) const
485 {
486 const DicomInstanceParameters& slice = *slices_[index];
487
488 if (!GeometryToolbox::IsParallel(
489 reference.GetGeometry().GetNormal(),
490 slice.GetGeometry().GetNormal()))
491 {
492 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
493 "A slice in the volume image is not parallel to the others");
494 }
495
496 if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat())
497 {
498 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat,
499 "The pixel format changes across the slices of the volume image");
500 }
501
502 if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() ||
503 reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight())
504 {
505 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize,
506 "The width/height of slices are not constant in the volume image");
507 }
508
509 if (!LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) ||
510 !LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY()))
511 {
512 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
513 "The pixel spacing of the slices change across the volume image");
514 }
515 }
516
517
518 void CheckVolume() const
519 {
520 for (size_t i = 0; i < slices_.size(); i++)
521 {
522 assert(slices_[i] != NULL);
523 if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1)
524 {
525 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry,
526 "This class does not support multi-frame images");
527 }
528 }
529
530 if (slices_.size() != 0)
531 {
532 const DicomInstanceParameters& reference = *slices_[0];
533
534 for (size_t i = 1; i < slices_.size(); i++)
535 {
536 CheckSlice(i, reference);
537 }
538 }
539 }
540
541
542 void Clear()
543 {
544 for (size_t i = 0; i < slices_.size(); i++)
545 {
546 assert(slices_[i] != NULL);
547 delete slices_[i];
548 }
549
550 slices_.clear();
551 slicesRevision_.clear();
552 }
553
554
555 void CheckSliceIndex(size_t index) const
556 {
557 if (!HasGeometry())
558 {
559 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
560 }
561 else if (index >= slices_.size())
562 {
563 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
564 }
565 else
566 {
567 assert(slices_.size() == GetImageGeometry().GetDepth() &&
568 slices_.size() == slicesRevision_.size());
569 }
570 }
571
572
573 std::auto_ptr<VolumeImageGeometry> geometry_;
574 std::vector<DicomInstanceParameters*> slices_;
575 std::vector<uint64_t> slicesRevision_;
576
577 public:
578 ~SeriesGeometry()
579 {
580 Clear();
581 }
582
583 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters"
584 void ComputeGeometry(SlicesSorter& slices)
585 {
586 Clear();
587
588 if (!slices.Sort())
589 {
590 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
591 "Cannot sort the 3D slices of a DICOM series");
592 }
593
594 if (slices.GetSlicesCount() == 0)
595 {
596 geometry_.reset(new VolumeImageGeometry);
597 }
598 else
599 {
600 slices_.reserve(slices.GetSlicesCount());
601 slicesRevision_.resize(slices.GetSlicesCount(), 0);
602
603 for (size_t i = 0; i < slices.GetSlicesCount(); i++)
604 {
605 const DicomInstanceParameters& slice =
606 dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i));
607 slices_.push_back(new DicomInstanceParameters(slice));
608 }
609
610 CheckVolume();
611
612 const double spacingZ = slices.ComputeSpacingBetweenSlices();
613 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm";
614
615 const DicomInstanceParameters& parameters = *slices_[0];
616
617 geometry_.reset(new VolumeImageGeometry);
618 geometry_->SetSize(parameters.GetImageInformation().GetWidth(),
619 parameters.GetImageInformation().GetHeight(),
620 static_cast<unsigned int>(slices.GetSlicesCount()));
621 geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
622 geometry_->SetVoxelDimensions(parameters.GetPixelSpacingX(),
623 parameters.GetPixelSpacingY(), spacingZ);
624 }
625 }
626
627 bool HasGeometry() const
628 {
629 return geometry_.get() != NULL;
630 }
631
632 const VolumeImageGeometry& GetImageGeometry() const
633 {
634 if (!HasGeometry())
635 {
636 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
637 }
638 else
639 {
640 assert(slices_.size() == geometry_->GetDepth());
641 return *geometry_;
642 }
643 }
644
645 const DicomInstanceParameters& GetSliceParameters(size_t index) const
646 {
647 CheckSliceIndex(index);
648 return *slices_[index];
649 }
650
651 uint64_t GetSliceRevision(size_t index) const
652 {
653 CheckSliceIndex(index);
654 return slicesRevision_[index];
655 }
656
657 void IncrementSliceRevision(size_t index)
658 {
659 CheckSliceIndex(index);
660 slicesRevision_[index] ++;
661 }
662 };
663
664
665 class Slice : public DicomVolumeImageOrthogonalSlice
666 {
667 private:
668 const OrthancSeriesVolumeProgressiveLoader& that_;
669
670 protected:
671 virtual uint64_t GetRevisionInternal(VolumeProjection projection,
672 unsigned int sliceIndex) const
673 {
674 if (projection == VolumeProjection_Axial)
675 {
676 return that_.seriesGeometry_.GetSliceRevision(sliceIndex);
677 }
678 else
679 {
680 // For coronal and sagittal projections, we take the global
681 // revision of the volume
682 return that_.volume_->GetRevision();
683 }
684 }
685
686 public:
687 Slice(const OrthancSeriesVolumeProgressiveLoader& that,
688 const CoordinateSystem3D& plane) :
689 DicomVolumeImageOrthogonalSlice(*that.volume_, plane),
690 that_(that)
691 {
692 }
693 };
694
695
696
744 static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command) 697 static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command)
745 { 698 {
746 return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue(); 699 return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue();
747 } 700 }
748 701
755 708
756 if (strategy_->GetNext(sliceIndex, quality)) 709 if (strategy_->GetNext(sliceIndex, quality))
757 { 710 {
758 assert(quality <= BEST_QUALITY); 711 assert(quality <= BEST_QUALITY);
759 712
760 const DicomInstanceParameters& slice = volume_.GetSliceParameters(sliceIndex); 713 const DicomInstanceParameters& slice = seriesGeometry_.GetSliceParameters(sliceIndex);
761 714
762 const std::string& instance = slice.GetOrthancInstanceIdentifier(); 715 const std::string& instance = slice.GetOrthancInstanceIdentifier();
763 if (instance.empty()) 716 if (instance.empty())
764 { 717 {
765 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 718 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
817 770
818 CoordinateSystem3D geometry = instance->GetGeometry(); 771 CoordinateSystem3D geometry = instance->GetGeometry();
819 slices.AddSlice(geometry, instance.release()); 772 slices.AddSlice(geometry, instance.release());
820 } 773 }
821 774
822 volume_.SetGeometry(slices); 775 seriesGeometry_.ComputeGeometry(slices);
823 } 776 }
824 777
825 if (volume_.GetSlicesCount() != 0) 778 size_t slicesCount = seriesGeometry_.GetImageGeometry().GetDepth();
826 { 779
827 strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter( 780 if (slicesCount == 0)
828 static_cast<unsigned int>(volume_.GetSlicesCount())), BEST_QUALITY)); 781 {
829 782 volume_->Initialize(seriesGeometry_.GetImageGeometry(), Orthanc::PixelFormat_Grayscale8);
783 }
784 else
785 {
786 const DicomInstanceParameters& parameters = seriesGeometry_.GetSliceParameters(0);
787
788 volume_->Initialize(seriesGeometry_.GetImageGeometry(), parameters.GetExpectedPixelFormat());
789 volume_->SetDicomParameters(parameters);
790 volume_->GetPixelData().Clear();
791
792 strategy_.reset(new BasicFetchingStrategy(sorter_->CreateSorter(slicesCount), BEST_QUALITY));
793
830 assert(simultaneousDownloads_ != 0); 794 assert(simultaneousDownloads_ != 0);
831 for (unsigned int i = 0; i < simultaneousDownloads_; i++) 795 for (unsigned int i = 0; i < simultaneousDownloads_; i++)
832 { 796 {
833 ScheduleNextSliceDownload(); 797 ScheduleNextSliceDownload();
834 } 798 }
835 } 799 }
836 800
837 BroadcastMessage(IVolumeImage::GeometryReadyMessage(volume_)); 801 slicesQuality_.resize(slicesCount, 0);
802
803 BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
804 }
805
806
807 void SetSliceContent(unsigned int sliceIndex,
808 const Orthanc::ImageAccessor& image,
809 unsigned int quality)
810 {
811 assert(sliceIndex < slicesQuality_.size() &&
812 slicesQuality_.size() == volume_->GetPixelData().GetDepth());
813
814 if (quality >= slicesQuality_[sliceIndex])
815 {
816 {
817 ImageBuffer3D::SliceWriter writer(volume_->GetPixelData(), VolumeProjection_Axial, sliceIndex);
818 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image);
819 }
820
821 volume_->IncrementRevision();
822 seriesGeometry_.IncrementSliceRevision(sliceIndex);
823 slicesQuality_[sliceIndex] = quality;
824
825 BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
826 }
827
828 ScheduleNextSliceDownload();
838 } 829 }
839 830
840 831
841 void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message) 832 void LoadBestQualitySliceContent(const GetOrthancImageCommand::SuccessMessage& message)
842 { 833 {
843 volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), 834 SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), BEST_QUALITY);
844 message.GetImage(), BEST_QUALITY);
845
846 ScheduleNextSliceDownload();
847
848 BroadcastMessage(IVolumeImage::ContentUpdatedMessage(volume_));
849 } 835 }
850 836
851 837
852 void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message) 838 void LoadJpegSliceContent(const GetOrthancWebViewerJpegCommand::SuccessMessage& message)
853 { 839 {
865 851
866 default: 852 default:
867 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 853 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
868 } 854 }
869 855
870 volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality); 856 SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality);
871 857 }
872 ScheduleNextSliceDownload(); 858
873 859
874 BroadcastMessage(IVolumeImage::ContentUpdatedMessage(volume_)); 860 IOracle& oracle_;
875 } 861 bool active_;
876 862 unsigned int simultaneousDownloads_;
877 863 SeriesGeometry seriesGeometry_;
878 IOracle& oracle_; 864
879 bool active_; 865 boost::shared_ptr<DicomVolumeImage> volume_;
880 DicomSeriesVolumeImage volume_;
881 unsigned int simultaneousDownloads_;
882
883 std::auto_ptr<IFetchingItemsSorter::IFactory> sorter_; 866 std::auto_ptr<IFetchingItemsSorter::IFactory> sorter_;
884 std::auto_ptr<IFetchingStrategy> strategy_; 867 std::auto_ptr<IFetchingStrategy> strategy_;
868 std::vector<unsigned int> slicesQuality_;
885 869
886 870
887 public: 871 public:
888 OrthancSeriesVolumeProgressiveLoader(IOracle& oracle, 872 OrthancSeriesVolumeProgressiveLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
873 IOracle& oracle,
889 IObservable& oracleObservable) : 874 IObservable& oracleObservable) :
890 IObserver(oracleObservable.GetBroker()), 875 IObserver(oracleObservable.GetBroker()),
891 IObservable(oracleObservable.GetBroker()), 876 IObservable(oracleObservable.GetBroker()),
892 oracle_(oracle), 877 oracle_(oracle),
893 active_(false), 878 active_(false),
894 simultaneousDownloads_(4), 879 simultaneousDownloads_(4),
880 volume_(volume),
895 sorter_(new BasicFetchingItemsSorter::Factory) 881 sorter_(new BasicFetchingItemsSorter::Factory)
896 { 882 {
897 oracleObservable.RegisterObserverCallback( 883 oracleObservable.RegisterObserverCallback(
898 new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage> 884 new Callable<OrthancSeriesVolumeProgressiveLoader, OrthancRestApiCommand::SuccessMessage>
899 (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry)); 885 (*this, &OrthancSeriesVolumeProgressiveLoader::LoadGeometry));
938 924
939 oracle_.Schedule(*this, command.release()); 925 oracle_.Schedule(*this, command.release());
940 } 926 }
941 } 927 }
942 928
943 929
944 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) 930 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
945 { 931 {
946 if (volume_.HasGeometry() && 932 if (volume_->HasGeometry())
947 volume_.GetSlicesCount() != 0) 933 {
948 { 934 std::auto_ptr<Slice> slice(new Slice(*this, cuttingPlane));
949 std::auto_ptr<DicomVolumeImageOrthogonalSlice> slice 935
950 (new DicomSeriesVolumeImage::ExtractedOrthogonalSlice(volume_, cuttingPlane)); 936 if (strategy_.get() != NULL &&
951 937 slice->IsValid() &&
952 assert(slice.get() != NULL &&
953 strategy_.get() != NULL);
954
955 if (slice->IsValid() &&
956 slice->GetProjection() == VolumeProjection_Axial) 938 slice->GetProjection() == VolumeProjection_Axial)
957 { 939 {
958 strategy_->SetCurrent(slice->GetSliceIndex()); 940 strategy_->SetCurrent(slice->GetSliceIndex());
959 } 941 }
960 942
970 952
971 953
972 class OrthancMultiframeVolumeLoader : 954 class OrthancMultiframeVolumeLoader :
973 public IObserver, 955 public IObserver,
974 public IObservable, 956 public IObservable,
975 public IVolumeSlicer, 957 public IVolumeSlicer
976 public VolumeImageBase
977 { 958 {
978 private: 959 private:
979 class State : public Orthanc::IDynamicObject 960 class State : public Orthanc::IDynamicObject
980 { 961 {
981 private: 962 private:
1121 } 1102 }
1122 }; 1103 };
1123 1104
1124 1105
1125 1106
1107 boost::shared_ptr<DicomVolumeImage> volume_;
1126 IOracle& oracle_; 1108 IOracle& oracle_;
1127 bool active_; 1109 bool active_;
1128 std::string instanceId_; 1110 std::string instanceId_;
1129 std::string transferSyntaxUid_; 1111 std::string transferSyntaxUid_;
1130 1112
1143 1125
1144 1126
1145 void ScheduleFrameDownloads() 1127 void ScheduleFrameDownloads()
1146 { 1128 {
1147 if (transferSyntaxUid_.empty() || 1129 if (transferSyntaxUid_.empty() ||
1148 !HasGeometry()) 1130 !volume_->HasGeometry())
1149 { 1131 {
1150 return; 1132 return;
1151 } 1133 }
1152 1134
1153 if (transferSyntaxUid_ == "1.2.840.10008.1.2" || 1135 if (transferSyntaxUid_ == "1.2.840.10008.1.2" ||
1178 1160
1179 1161
1180 void SetGeometry(const Orthanc::DicomMap& dicom) 1162 void SetGeometry(const Orthanc::DicomMap& dicom)
1181 { 1163 {
1182 DicomInstanceParameters parameters(dicom); 1164 DicomInstanceParameters parameters(dicom);
1183 VolumeImageBase::SetDicomParameters(parameters); 1165 volume_->SetDicomParameters(parameters);
1184 1166
1185 Orthanc::PixelFormat format; 1167 Orthanc::PixelFormat format;
1186 if (!parameters.GetImageInformation().ExtractPixelFormat(format, true)) 1168 if (!parameters.GetImageInformation().ExtractPixelFormat(format, true))
1187 { 1169 {
1188 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); 1170 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1204 const unsigned int width = parameters.GetImageInformation().GetWidth(); 1186 const unsigned int width = parameters.GetImageInformation().GetWidth();
1205 const unsigned int height = parameters.GetImageInformation().GetHeight(); 1187 const unsigned int height = parameters.GetImageInformation().GetHeight();
1206 const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames(); 1188 const unsigned int depth = parameters.GetImageInformation().GetNumberOfFrames();
1207 1189
1208 { 1190 {
1209 std::auto_ptr<VolumeImageGeometry> geometry(new VolumeImageGeometry); 1191 VolumeImageGeometry geometry;
1210 geometry->SetSize(width, height, depth); 1192 geometry.SetSize(width, height, depth);
1211 geometry->SetAxialGeometry(parameters.GetGeometry()); 1193 geometry.SetAxialGeometry(parameters.GetGeometry());
1212 geometry->SetVoxelDimensions(parameters.GetPixelSpacingX(), 1194 geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(),
1213 parameters.GetPixelSpacingY(), 1195 parameters.GetPixelSpacingY(), spacingZ);
1214 spacingZ); 1196 volume_->Initialize(geometry, format);
1215 VolumeImageBase::Initialize(geometry.release(), format); 1197 }
1216 } 1198
1217 1199 volume_->GetPixelData().Clear();
1218 GetPixelData().Clear();
1219 1200
1220 ScheduleFrameDownloads(); 1201 ScheduleFrameDownloads();
1221 1202
1222 BroadcastMessage(IVolumeImage::GeometryReadyMessage(*this)); 1203 BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_));
1223 } 1204 }
1224 1205
1225 1206
1226 ORTHANC_FORCE_INLINE 1207 ORTHANC_FORCE_INLINE
1227 static void CopyPixel(uint32_t& target, 1208 static void CopyPixel(uint32_t& target,
1233 1214
1234 1215
1235 template <typename T> 1216 template <typename T>
1236 void CopyPixelData(const std::string& pixelData) 1217 void CopyPixelData(const std::string& pixelData)
1237 { 1218 {
1238 const Orthanc::PixelFormat format = GetPixelData().GetFormat(); 1219 ImageBuffer3D& target = volume_->GetPixelData();
1239 const unsigned int bpp = GetPixelData().GetBytesPerPixel(); 1220
1240 const unsigned int width = GetPixelData().GetWidth(); 1221 const Orthanc::PixelFormat format = target.GetFormat();
1241 const unsigned int height = GetPixelData().GetHeight(); 1222 const unsigned int bpp = target.GetBytesPerPixel();
1242 const unsigned int depth = GetPixelData().GetDepth(); 1223 const unsigned int width = target.GetWidth();
1224 const unsigned int height = target.GetHeight();
1225 const unsigned int depth = target.GetDepth();
1243 1226
1244 if (pixelData.size() != bpp * width * height * depth) 1227 if (pixelData.size() != bpp * width * height * depth)
1245 { 1228 {
1246 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, 1229 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
1247 "The pixel data has not the proper size"); 1230 "The pixel data has not the proper size");
1254 1237
1255 const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str()); 1238 const uint8_t* source = reinterpret_cast<const uint8_t*>(pixelData.c_str());
1256 1239
1257 for (unsigned int z = 0; z < depth; z++) 1240 for (unsigned int z = 0; z < depth; z++)
1258 { 1241 {
1259 ImageBuffer3D::SliceWriter writer(GetPixelData(), VolumeProjection_Axial, z); 1242 ImageBuffer3D::SliceWriter writer(target, VolumeProjection_Axial, z);
1260 1243
1261 assert (writer.GetAccessor().GetWidth() == width && 1244 assert (writer.GetAccessor().GetWidth() == width &&
1262 writer.GetAccessor().GetHeight() == height); 1245 writer.GetAccessor().GetHeight() == height);
1263 1246
1264 for (unsigned int y = 0; y < height; y++) 1247 for (unsigned int y = 0; y < height; y++)
1279 } 1262 }
1280 1263
1281 1264
1282 void SetUncompressedPixelData(const std::string& pixelData) 1265 void SetUncompressedPixelData(const std::string& pixelData)
1283 { 1266 {
1284 switch (GetPixelData().GetFormat()) 1267 switch (volume_->GetPixelData().GetFormat())
1285 { 1268 {
1286 case Orthanc::PixelFormat_Grayscale32: 1269 case Orthanc::PixelFormat_Grayscale32:
1287 CopyPixelData<uint32_t>(pixelData); 1270 CopyPixelData<uint32_t>(pixelData);
1288 break; 1271 break;
1289 1272
1290 default: 1273 default:
1291 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); 1274 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
1292 } 1275 }
1293 1276
1294 IncrementRevision(); 1277 volume_->IncrementRevision();
1295 1278
1296 BroadcastMessage(IVolumeImage::ContentUpdatedMessage(*this)); 1279 BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_));
1297 } 1280 }
1298 1281
1299 1282
1300 private:
1301 class ExtractedOrthogonalSlice : public DicomVolumeImageOrthogonalSlice
1302 {
1303 private:
1304 const OrthancMultiframeVolumeLoader& that_;
1305
1306 protected:
1307 virtual uint64_t GetRevisionInternal(VolumeProjection projection,
1308 unsigned int sliceIndex) const
1309 {
1310 return that_.GetRevision();
1311 }
1312
1313 public:
1314 ExtractedOrthogonalSlice(const OrthancMultiframeVolumeLoader& that,
1315 const CoordinateSystem3D& plane) :
1316 DicomVolumeImageOrthogonalSlice(that, plane),
1317 that_(that)
1318 {
1319 }
1320 };
1321
1322
1323 public: 1283 public:
1324 OrthancMultiframeVolumeLoader(IOracle& oracle, 1284 OrthancMultiframeVolumeLoader(const boost::shared_ptr<DicomVolumeImage>& volume,
1285 IOracle& oracle,
1325 IObservable& oracleObservable) : 1286 IObservable& oracleObservable) :
1326 IObserver(oracleObservable.GetBroker()), 1287 IObserver(oracleObservable.GetBroker()),
1327 IObservable(oracleObservable.GetBroker()), 1288 IObservable(oracleObservable.GetBroker()),
1289 volume_(volume),
1328 oracle_(oracle), 1290 oracle_(oracle),
1329 active_(false) 1291 active_(false)
1330 { 1292 {
1293 if (volume.get() == NULL)
1294 {
1295 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
1296 }
1297
1331 oracleObservable.RegisterObserverCallback( 1298 oracleObservable.RegisterObserverCallback(
1332 new Callable<OrthancMultiframeVolumeLoader, OrthancRestApiCommand::SuccessMessage> 1299 new Callable<OrthancMultiframeVolumeLoader, OrthancRestApiCommand::SuccessMessage>
1333 (*this, &OrthancMultiframeVolumeLoader::Handle)); 1300 (*this, &OrthancMultiframeVolumeLoader::Handle));
1334 } 1301 }
1335 1302
1363 } 1330 }
1364 1331
1365 1332
1366 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane) 1333 virtual IExtractedSlice* ExtractSlice(const CoordinateSystem3D& cuttingPlane)
1367 { 1334 {
1368 if (HasGeometry()) 1335 if (volume_->HasGeometry())
1369 { 1336 {
1370 return new ExtractedOrthogonalSlice(*this, cuttingPlane); 1337 return new DicomVolumeImageOrthogonalSlice(*volume_, cuttingPlane);
1371 } 1338 }
1372 else 1339 else
1373 { 1340 {
1374 return new IVolumeSlicer::InvalidSlice; 1341 return new IVolumeSlicer::InvalidSlice;
1375 } 1342 }
1406 } 1373 }
1407 1374
1408 virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, // possibly absent 1375 virtual ISceneLayer* CreateSceneLayer(const ILayerStyleConfigurator* configurator, // possibly absent
1409 const CoordinateSystem3D& cuttingPlane) 1376 const CoordinateSystem3D& cuttingPlane)
1410 { 1377 {
1378 VolumeReslicer& reslicer = that_.reslicer_;
1379
1411 if (configurator == NULL) 1380 if (configurator == NULL)
1412 { 1381 {
1413 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, 1382 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
1414 "Must provide a layer style configurator"); 1383 "Must provide a layer style configurator");
1415 } 1384 }
1416 1385
1417 that_.reslicer_.SetOutputFormat(that_.volume_->GetPixelData().GetFormat()); 1386 reslicer.SetOutputFormat(that_.volume_->GetPixelData().GetFormat());
1418 1387 reslicer.Apply(that_.volume_->GetPixelData(),
1419 if (that_.reslicer_.IsSuccess()) 1388 that_.volume_->GetGeometry(),
1389 cuttingPlane);
1390
1391 if (reslicer.IsSuccess())
1420 { 1392 {
1421 std::auto_ptr<TextureBaseSceneLayer> layer 1393 std::auto_ptr<TextureBaseSceneLayer> layer
1422 (configurator->CreateTextureFromDicom(that_.reslicer_.GetOutputSlice(), 1394 (configurator->CreateTextureFromDicom(reslicer.GetOutputSlice(),
1423 that_.volume_->GetDicomParameters())); 1395 that_.volume_->GetDicomParameters()));
1424 if (layer.get() == NULL) 1396 if (layer.get() == NULL)
1425 { 1397 {
1426 return NULL; 1398 return NULL;
1427 } 1399 }
1428 1400
1401 double s = reslicer.GetPixelSpacing();
1402 layer->SetPixelSpacing(s, s);
1403 layer->SetOrigin(reslicer.GetOutputExtent().GetX1() + 0.5 * s,
1404 reslicer.GetOutputExtent().GetY1() + 0.5 * s);
1405
1406 // TODO - Angle!!
1407
1429 return layer.release(); 1408 return layer.release();
1430 } 1409 }
1431 else 1410 else
1432 { 1411 {
1433 return NULL; 1412 return NULL;
1434 } 1413 }
1435 } 1414 }
1436 }; 1415 };
1437 1416
1438 boost::shared_ptr<IVolumeImage> volume_; 1417 boost::shared_ptr<DicomVolumeImage> volume_;
1439 VolumeReslicer reslicer_; 1418 VolumeReslicer reslicer_;
1440 1419
1441 public: 1420 public:
1442 VolumeImageReslicer(const boost::shared_ptr<IVolumeImage>& volume) : 1421 VolumeImageReslicer(const boost::shared_ptr<DicomVolumeImage>& volume) :
1443 volume_(volume) 1422 volume_(volume)
1444 { 1423 {
1445 if (volume.get() == NULL) 1424 if (volume.get() == NULL)
1446 { 1425 {
1447 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); 1426 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
1501 double distance; 1480 double distance;
1502 return (CoordinateSystem3D::ComputeDistance(distance, a, b) && 1481 return (CoordinateSystem3D::ComputeDistance(distance, a, b) &&
1503 LinearAlgebra::IsCloseToZero(distance)); 1482 LinearAlgebra::IsCloseToZero(distance));
1504 } 1483 }
1505 1484
1485 void ClearLayer()
1486 {
1487 scene_.DeleteLayer(layerDepth_);
1488 lastPlane_.reset(NULL);
1489 }
1490
1506 public: 1491 public:
1507 VolumeSceneLayerSource(Scene2D& scene, 1492 VolumeSceneLayerSource(Scene2D& scene,
1508 int layerDepth, 1493 int layerDepth,
1509 const boost::shared_ptr<IVolumeSlicer>& slicer) : 1494 const boost::shared_ptr<IVolumeSlicer>& slicer) :
1510 scene_(scene), 1495 scene_(scene),
1567 } 1552 }
1568 1553
1569 if (!slice->IsValid()) 1554 if (!slice->IsValid())
1570 { 1555 {
1571 // The slicer cannot handle this cutting plane: Clear the layer 1556 // The slicer cannot handle this cutting plane: Clear the layer
1572 scene_.DeleteLayer(layerDepth_); 1557 ClearLayer();
1573 lastPlane_.reset(NULL);
1574 } 1558 }
1575 else if (lastPlane_.get() != NULL && 1559 else if (lastPlane_.get() != NULL &&
1576 IsSameCuttingPlane(*lastPlane_, plane) && 1560 IsSameCuttingPlane(*lastPlane_, plane) &&
1577 lastRevision_ == slice->GetRevision()) 1561 lastRevision_ == slice->GetRevision())
1578 { 1562 {
1593 lastRevision_ = slice->GetRevision(); 1577 lastRevision_ = slice->GetRevision();
1594 1578
1595 std::auto_ptr<ISceneLayer> layer(slice->CreateSceneLayer(configurator_.get(), plane)); 1579 std::auto_ptr<ISceneLayer> layer(slice->CreateSceneLayer(configurator_.get(), plane));
1596 if (layer.get() == NULL) 1580 if (layer.get() == NULL)
1597 { 1581 {
1598 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 1582 ClearLayer();
1599 } 1583 }
1600 1584 else
1601 if (configurator_.get() != NULL) 1585 {
1602 { 1586 if (configurator_.get() != NULL)
1603 lastConfiguratorRevision_ = configurator_->GetRevision(); 1587 {
1604 configurator_->ApplyStyle(*layer); 1588 lastConfiguratorRevision_ = configurator_->GetRevision();
1605 } 1589 configurator_->ApplyStyle(*layer);
1606 1590 }
1607 scene_.SetLayer(layerDepth_, layer.release()); 1591
1592 scene_.SetLayer(layerDepth_, layer.release());
1593 }
1608 } 1594 }
1609 } 1595 }
1610 }; 1596 };
1611 1597
1612 1598
1726 writer.WriteToFile(buf, tmp); 1712 writer.WriteToFile(buf, tmp);
1727 } 1713 }
1728 } 1714 }
1729 1715
1730 1716
1731 void Handle(const OrthancStone::IVolumeImage::GeometryReadyMessage& message) 1717 void Handle(const OrthancStone::DicomVolumeImage::GeometryReadyMessage& message)
1732 { 1718 {
1733 printf("Geometry ready\n"); 1719 printf("Geometry ready\n");
1734 1720
1735 //plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry(); 1721 //plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry();
1736 //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry(); 1722 //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry();
1823 oracleObservable.RegisterObserverCallback 1809 oracleObservable.RegisterObserverCallback
1824 (new OrthancStone::Callable 1810 (new OrthancStone::Callable
1825 <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle)); 1811 <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle));
1826 } 1812 }
1827 1813
1814 void SetReferenceLoader(OrthancStone::IObservable& loader)
1815 {
1816 loader.RegisterObserverCallback
1817 (new OrthancStone::Callable
1818 <Toto, OrthancStone::DicomVolumeImage::GeometryReadyMessage>(*this, &Toto::Handle));
1819 }
1820
1828 void SetVolume1(int depth, 1821 void SetVolume1(int depth,
1829 const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, 1822 const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume,
1830 OrthancStone::ILayerStyleConfigurator* style) 1823 OrthancStone::ILayerStyleConfigurator* style)
1831 { 1824 {
1832 dynamic_cast<OrthancStone::IObservable&>(*volume).RegisterObserverCallback
1833 (new OrthancStone::Callable
1834 <Toto, OrthancStone::IVolumeImage::GeometryReadyMessage>(*this, &Toto::Handle));
1835
1836 source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); 1825 source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume));
1837 1826
1838 if (style != NULL) 1827 if (style != NULL)
1839 { 1828 {
1840 source1_->SetConfigurator(style); 1829 source1_->SetConfigurator(style);
1856 1845
1857 1846
1858 void Run(OrthancStone::NativeApplicationContext& context, 1847 void Run(OrthancStone::NativeApplicationContext& context,
1859 OrthancStone::ThreadedOracle& oracle) 1848 OrthancStone::ThreadedOracle& oracle)
1860 { 1849 {
1850 boost::shared_ptr<OrthancStone::DicomVolumeImage> ct(new OrthancStone::DicomVolumeImage);
1851 boost::shared_ptr<OrthancStone::DicomVolumeImage> dose(new OrthancStone::DicomVolumeImage);
1852
1853
1861 boost::shared_ptr<Toto> toto; 1854 boost::shared_ptr<Toto> toto;
1862 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> loader1, loader2; 1855 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader;
1863 boost::shared_ptr<OrthancStone::OrthancMultiframeVolumeLoader> loader3; 1856 boost::shared_ptr<OrthancStone::OrthancMultiframeVolumeLoader> doseLoader;
1864 1857
1865 { 1858 {
1866 OrthancStone::NativeApplicationContext::WriterLock lock(context); 1859 OrthancStone::NativeApplicationContext::WriterLock lock(context);
1867 toto.reset(new Toto(oracle, lock.GetOracleObservable())); 1860 toto.reset(new Toto(oracle, lock.GetOracleObservable()));
1868 loader1.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(oracle, lock.GetOracleObservable())); 1861 ctLoader.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct, oracle, lock.GetOracleObservable()));
1869 loader2.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(oracle, lock.GetOracleObservable())); 1862 doseLoader.reset(new OrthancStone::OrthancMultiframeVolumeLoader(dose, oracle, lock.GetOracleObservable()));
1870 loader3.reset(new OrthancStone::OrthancMultiframeVolumeLoader(oracle, lock.GetOracleObservable())); 1863 }
1871 } 1864
1865
1866 toto->SetReferenceLoader(*ctLoader);
1872 1867
1873 1868
1874 #if 1 1869 #if 1
1875 toto->SetVolume1(0, loader1, new OrthancStone::GrayscaleStyleConfigurator); 1870 toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator);
1876 #else 1871 #else
1877 { 1872 {
1878 boost::shared_ptr<OrthancStone::IVolumeSlicer> reslicer(new OrthancStone::VolumeImageReslicer(loader1)); 1873 boost::shared_ptr<OrthancStone::IVolumeSlicer> reslicer(new OrthancStone::VolumeImageReslicer(ct));
1879 toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator); 1874 toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator);
1880 } 1875 }
1881 #endif 1876 #endif
1882 1877
1883 1878
1884 { 1879 {
1885 std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator); 1880 std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator);
1886 config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); 1881 config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT);
1887 toto->SetVolume2(1, loader3, config.release()); 1882 toto->SetVolume2(1, doseLoader, config.release());
1888 } 1883 }
1889 1884
1890 oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100)); 1885 oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100));
1891 1886
1892 if (0) 1887 if (0)
1963 oracle.Schedule(*toto, command.release()); 1958 oracle.Schedule(*toto, command.release());
1964 } 1959 }
1965 } 1960 }
1966 1961
1967 // 2017-11-17-Anonymized 1962 // 2017-11-17-Anonymized
1968 loader1->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT 1963 ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT
1969 loader3->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE 1964 doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE
1970 1965
1971 // 2015-01-28-Multiframe 1966 // 2015-01-28-Multiframe
1972 //loader3->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279"); // Multiframe CT 1967 //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279"); // Multiframe CT
1973 1968
1974 // Delphine 1969 // Delphine
1975 //loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT 1970 //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT
1976 //loader1->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm 1971 //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm
1977 1972
1978 1973
1979 { 1974 {
1980 LOG(WARNING) << "...Waiting for Ctrl-C..."; 1975 LOG(WARNING) << "...Waiting for Ctrl-C...";
1981 1976