Mercurial > hg > orthanc-stone
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 |