Mercurial > hg > orthanc-stone
diff OrthancStone/Sources/Deprecated/dev.h @ 1512:244ad1e4e76a
reorganization of folders
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 07 Jul 2020 16:21:02 +0200 |
parents | Framework/Deprecated/dev.h@30deba7bc8e2 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Deprecated/dev.h Tue Jul 07 16:21:02 2020 +0200 @@ -0,0 +1,958 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "Layers/FrameRenderer.h" +#include "Layers/LineLayerRenderer.h" +#include "Layers/SliceOutlineRenderer.h" +#include "Toolbox/DownloadStack.h" +#include "Toolbox/GeometryToolbox.h" +#include "Toolbox/OrthancSlicesLoader.h" +#include "Volumes/ISlicedVolume.h" +#include "Volumes/ImageBuffer3D.h" +#include "Widgets/SliceViewerWidget.h" + +#include <Logging.h> +#include <Images/ImageProcessing.h> +#include <OrthancException.h> + +#include <boost/math/special_functions/round.hpp> + + +namespace Deprecated +{ + // TODO: Handle errors while loading + class OrthancVolumeImage : + public ISlicedVolume, + public OrthancStone::IObserver + { + private: + OrthancSlicesLoader loader_; + std::unique_ptr<OrthancStone::ImageBuffer3D> image_; + std::unique_ptr<DownloadStack> downloadStack_; + bool computeRange_; + size_t pendingSlices_; + + void ScheduleSliceDownload() + { + assert(downloadStack_.get() != NULL); + + unsigned int slice; + if (downloadStack_->Pop(slice)) + { + loader_.ScheduleLoadSliceImage(slice, OrthancStone::SliceImageQuality_Jpeg90); + } + } + + + static bool IsCompatible(const Slice& a, + const Slice& b) + { + if (!OrthancStone::GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(), + b.GetGeometry().GetNormal())) + { + LOG(ERROR) << "A slice in the volume image is not parallel to the others."; + return false; + } + + if (a.GetConverter().GetExpectedPixelFormat() != b.GetConverter().GetExpectedPixelFormat()) + { + LOG(ERROR) << "The pixel format changes across the slices of the volume image."; + return false; + } + + if (a.GetWidth() != b.GetWidth() || + a.GetHeight() != b.GetHeight()) + { + LOG(ERROR) << "The slices dimensions (width/height) are varying throughout the volume image"; + return false; + } + + if (!OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) || + !OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY())) + { + LOG(ERROR) << "The pixel spacing of the slices change across the volume image"; + return false; + } + + return true; + } + + + static double GetDistance(const Slice& a, + const Slice& b) + { + return fabs(a.GetGeometry().ProjectAlongNormal(a.GetGeometry().GetOrigin()) - + a.GetGeometry().ProjectAlongNormal(b.GetGeometry().GetOrigin())); + } + + + void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) + { + assert(&message.GetOrigin() == &loader_); + + if (loader_.GetSlicesCount() == 0) + { + LOG(ERROR) << "Empty volume image"; + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); + return; + } + + for (size_t i = 1; i < loader_.GetSlicesCount(); i++) + { + if (!IsCompatible(loader_.GetSlice(0), loader_.GetSlice(i))) + { + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); + return; + } + } + + double spacingZ; + + if (loader_.GetSlicesCount() > 1) + { + spacingZ = GetDistance(loader_.GetSlice(0), loader_.GetSlice(1)); + } + else + { + // This is a volume with one single slice: Choose a dummy + // z-dimension for voxels + spacingZ = 1; + } + + for (size_t i = 1; i < loader_.GetSlicesCount(); i++) + { + if (!OrthancStone::LinearAlgebra::IsNear(spacingZ, GetDistance(loader_.GetSlice(i - 1), loader_.GetSlice(i)), + 0.001 /* this is expressed in mm */)) + { + LOG(ERROR) << "The distance between successive slices is not constant in a volume image"; + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); + return; + } + } + + unsigned int width = loader_.GetSlice(0).GetWidth(); + unsigned int height = loader_.GetSlice(0).GetHeight(); + Orthanc::PixelFormat format = loader_.GetSlice(0).GetConverter().GetExpectedPixelFormat(); + LOG(INFO) << "Creating a volume image of size " << width << "x" << height + << "x" << loader_.GetSlicesCount() << " in " << Orthanc::EnumerationToString(format); + + image_.reset(new OrthancStone::ImageBuffer3D(format, width, height, static_cast<unsigned int>(loader_.GetSlicesCount()), computeRange_)); + image_->GetGeometry().SetAxialGeometry(loader_.GetSlice(0).GetGeometry()); + image_->GetGeometry().SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(), + loader_.GetSlice(0).GetPixelSpacingY(), spacingZ); + image_->Clear(); + + downloadStack_.reset(new DownloadStack(static_cast<unsigned int>(loader_.GetSlicesCount()))); + pendingSlices_ = loader_.GetSlicesCount(); + + for (unsigned int i = 0; i < 4; i++) // Limit to 4 simultaneous downloads + { + ScheduleSliceDownload(); + } + + // TODO Check the DicomFrameConverter are constant + + BroadcastMessage(ISlicedVolume::GeometryReadyMessage(*this)); + } + + + void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) + { + assert(&message.GetOrigin() == &loader_); + + LOG(ERROR) << "Unable to download a volume image"; + BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); + } + + + void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) + { + assert(&message.GetOrigin() == &loader_); + + { + OrthancStone::ImageBuffer3D::SliceWriter writer(*image_, OrthancStone::VolumeProjection_Axial, message.GetSliceIndex()); + Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage()); + } + + BroadcastMessage(ISlicedVolume::SliceContentChangedMessage + (*this, message.GetSliceIndex(), message.GetSlice())); + + if (pendingSlices_ == 1) + { + BroadcastMessage(ISlicedVolume::VolumeReadyMessage(*this)); + pendingSlices_ = 0; + } + else if (pendingSlices_ > 1) + { + pendingSlices_ -= 1; + } + + ScheduleSliceDownload(); + } + + + void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) + { + assert(&message.GetOrigin() == &loader_); + + LOG(ERROR) << "Cannot download slice " << message.GetSliceIndex() << " in a volume image"; + ScheduleSliceDownload(); + } + + + public: + OrthancVolumeImage(OrthancStone::MessageBroker& broker, + OrthancApiClient& orthanc, + bool computeRange) : + ISlicedVolume(broker), + IObserver(broker), + loader_(broker, orthanc), + computeRange_(computeRange), + pendingSlices_(0) + { + loader_.RegisterObserverCallback( + new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryReadyMessage> + (*this, &OrthancVolumeImage::OnSliceGeometryReady)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryErrorMessage> + (*this, &OrthancVolumeImage::OnSliceGeometryError)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageReadyMessage> + (*this, &OrthancVolumeImage::OnSliceImageReady)); + + loader_.RegisterObserverCallback( + new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageErrorMessage> + (*this, &OrthancVolumeImage::OnSliceImageError)); + } + + void ScheduleLoadSeries(const std::string& seriesId) + { + loader_.ScheduleLoadSeries(seriesId); + } + + void ScheduleLoadInstance(const std::string& instanceId) + { + loader_.ScheduleLoadInstance(instanceId); + } + + void ScheduleLoadFrame(const std::string& instanceId, + unsigned int frame) + { + loader_.ScheduleLoadFrame(instanceId, frame); + } + + virtual size_t GetSlicesCount() const + { + return loader_.GetSlicesCount(); + } + + virtual const Slice& GetSlice(size_t index) const + { + return loader_.GetSlice(index); + } + + OrthancStone::ImageBuffer3D& GetImage() const + { + if (image_.get() == NULL) + { + // The geometry is not ready yet + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *image_; + } + } + + bool FitWindowingToRange(RenderStyle& style, + const DicomFrameConverter& converter) const + { + if (image_.get() == NULL) + { + return false; + } + else + { + return image_->FitWindowingToRange(style, converter); + } + } + }; + + + class VolumeImageGeometry + { + private: + unsigned int width_; + unsigned int height_; + size_t depth_; + double pixelSpacingX_; + double pixelSpacingY_; + double sliceThickness_; + OrthancStone::CoordinateSystem3D reference_; + DicomFrameConverter converter_; + + double ComputeAxialThickness(const OrthancVolumeImage& volume) const + { + double thickness; + + size_t n = volume.GetSlicesCount(); + if (n > 1) + { + const Slice& a = volume.GetSlice(0); + const Slice& b = volume.GetSlice(n - 1); + thickness = ((reference_.ProjectAlongNormal(b.GetGeometry().GetOrigin()) - + reference_.ProjectAlongNormal(a.GetGeometry().GetOrigin())) / + (static_cast<double>(n) - 1.0)); + } + else + { + thickness = volume.GetSlice(0).GetThickness(); + } + + if (thickness <= 0) + { + // The slices should have been sorted with increasing Z + // (along the normal) by the OrthancSlicesLoader + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + else + { + return thickness; + } + } + + void SetupAxial(const OrthancVolumeImage& volume) + { + const Slice& axial = volume.GetSlice(0); + + width_ = axial.GetWidth(); + height_ = axial.GetHeight(); + depth_ = volume.GetSlicesCount(); + + pixelSpacingX_ = axial.GetPixelSpacingX(); + pixelSpacingY_ = axial.GetPixelSpacingY(); + sliceThickness_ = ComputeAxialThickness(volume); + + reference_ = axial.GetGeometry(); + } + + void SetupCoronal(const OrthancVolumeImage& volume) + { + const Slice& axial = volume.GetSlice(0); + double axialThickness = ComputeAxialThickness(volume); + + width_ = axial.GetWidth(); + height_ = static_cast<unsigned int>(volume.GetSlicesCount()); + depth_ = axial.GetHeight(); + + pixelSpacingX_ = axial.GetPixelSpacingX(); + pixelSpacingY_ = axialThickness; + sliceThickness_ = axial.GetPixelSpacingY(); + + OrthancStone::Vector origin = axial.GetGeometry().GetOrigin(); + origin += (static_cast<double>(volume.GetSlicesCount() - 1) * + axialThickness * axial.GetGeometry().GetNormal()); + + reference_ = OrthancStone::CoordinateSystem3D(origin, + axial.GetGeometry().GetAxisX(), + - axial.GetGeometry().GetNormal()); + } + + void SetupSagittal(const OrthancVolumeImage& volume) + { + const Slice& axial = volume.GetSlice(0); + double axialThickness = ComputeAxialThickness(volume); + + width_ = axial.GetHeight(); + height_ = static_cast<unsigned int>(volume.GetSlicesCount()); + depth_ = axial.GetWidth(); + + pixelSpacingX_ = axial.GetPixelSpacingY(); + pixelSpacingY_ = axialThickness; + sliceThickness_ = axial.GetPixelSpacingX(); + + OrthancStone::Vector origin = axial.GetGeometry().GetOrigin(); + origin += (static_cast<double>(volume.GetSlicesCount() - 1) * + axialThickness * axial.GetGeometry().GetNormal()); + + reference_ = OrthancStone::CoordinateSystem3D(origin, + axial.GetGeometry().GetAxisY(), + axial.GetGeometry().GetNormal()); + } + + public: + VolumeImageGeometry(const OrthancVolumeImage& volume, + OrthancStone::VolumeProjection projection) + { + if (volume.GetSlicesCount() == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + converter_ = volume.GetSlice(0).GetConverter(); + + switch (projection) + { + case OrthancStone::VolumeProjection_Axial: + SetupAxial(volume); + break; + + case OrthancStone::VolumeProjection_Coronal: + SetupCoronal(volume); + break; + + case OrthancStone::VolumeProjection_Sagittal: + SetupSagittal(volume); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + size_t GetSlicesCount() const + { + return depth_; + } + + const OrthancStone::Vector& GetNormal() const + { + return reference_.GetNormal(); + } + + bool LookupSlice(size_t& index, + const OrthancStone::CoordinateSystem3D& slice) const + { + bool opposite; + if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, + reference_.GetNormal(), + slice.GetNormal())) + { + return false; + } + + double z = (reference_.ProjectAlongNormal(slice.GetOrigin()) - + reference_.ProjectAlongNormal(reference_.GetOrigin())) / sliceThickness_; + + int s = static_cast<int>(boost::math::iround(z)); + + if (s < 0 || + s >= static_cast<int>(depth_)) + { + return false; + } + else + { + index = static_cast<size_t>(s); + return true; + } + } + + Slice* GetSlice(size_t slice) const + { + if (slice >= depth_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + OrthancStone::CoordinateSystem3D origin(reference_.GetOrigin() + + static_cast<double>(slice) * sliceThickness_ * reference_.GetNormal(), + reference_.GetAxisX(), + reference_.GetAxisY()); + + return new Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_, + width_, height_, converter_); + } + } + }; + + + + class VolumeImageMPRSlicer : + public IVolumeSlicer, + public OrthancStone::IObserver + { + private: + class RendererFactory : public LayerReadyMessage::IRendererFactory + { + private: + const Orthanc::ImageAccessor& frame_; + const Slice& slice_; + bool isFullQuality_; + + public: + RendererFactory(const Orthanc::ImageAccessor& frame, + const Slice& slice, + bool isFullQuality) : + frame_(frame), + slice_(slice), + isFullQuality_(isFullQuality) + { + } + + virtual ILayerRenderer* CreateRenderer() const + { + return FrameRenderer::CreateRenderer(frame_, slice_, isFullQuality_); + } + }; + + + OrthancVolumeImage& volume_; + std::unique_ptr<VolumeImageGeometry> axialGeometry_; + std::unique_ptr<VolumeImageGeometry> coronalGeometry_; + std::unique_ptr<VolumeImageGeometry> sagittalGeometry_; + + + bool IsGeometryReady() const + { + return axialGeometry_.get() != NULL; + } + + void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message) + { + assert(&message.GetOrigin() == &volume_); + + // These 3 values are only used to speed up the IVolumeSlicer + axialGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Axial)); + coronalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Coronal)); + sagittalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Sagittal)); + + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + } + + void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message) + { + assert(&message.GetOrigin() == &volume_); + + BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); + } + + void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message) + { + assert(&message.GetOrigin() == &volume_); + + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); + } + + void OnSliceContentChanged(const ISlicedVolume::SliceContentChangedMessage& message) + { + assert(&message.GetOrigin() == &volume_); + + //IVolumeSlicer::OnSliceContentChange(slice); + + // TODO Improve this? + BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); + } + + const VolumeImageGeometry& GetProjectionGeometry(OrthancStone::VolumeProjection projection) + { + if (!IsGeometryReady()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + switch (projection) + { + case OrthancStone::VolumeProjection_Axial: + return *axialGeometry_; + + case OrthancStone::VolumeProjection_Sagittal: + return *sagittalGeometry_; + + case OrthancStone::VolumeProjection_Coronal: + return *coronalGeometry_; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + bool DetectProjection(OrthancStone::VolumeProjection& projection, + const OrthancStone::CoordinateSystem3D& viewportSlice) + { + bool isOpposite; // Ignored + + if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, + viewportSlice.GetNormal(), + axialGeometry_->GetNormal())) + { + projection = OrthancStone::VolumeProjection_Axial; + return true; + } + else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, + viewportSlice.GetNormal(), + sagittalGeometry_->GetNormal())) + { + projection = OrthancStone::VolumeProjection_Sagittal; + return true; + } + else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, + viewportSlice.GetNormal(), + coronalGeometry_->GetNormal())) + { + projection = OrthancStone::VolumeProjection_Coronal; + return true; + } + else + { + return false; + } + } + + + public: + VolumeImageMPRSlicer(OrthancStone::MessageBroker& broker, + OrthancVolumeImage& volume) : + IVolumeSlicer(broker), + IObserver(broker), + volume_(volume) + { + volume_.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryReadyMessage> + (*this, &VolumeImageMPRSlicer::OnGeometryReady)); + + volume_.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryErrorMessage> + (*this, &VolumeImageMPRSlicer::OnGeometryError)); + + volume_.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::ContentChangedMessage> + (*this, &VolumeImageMPRSlicer::OnContentChanged)); + + volume_.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::SliceContentChangedMessage> + (*this, &VolumeImageMPRSlicer::OnSliceContentChanged)); + } + + virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE + { + OrthancStone::VolumeProjection projection; + + if (!IsGeometryReady() || + !DetectProjection(projection, viewportSlice)) + { + return false; + } + else + { + // As the slices of the volumic image are arranged in a box, + // we only consider one single reference slice (the one with index 0). + std::unique_ptr<Slice> slice(GetProjectionGeometry(projection).GetSlice(0)); + slice->GetExtent(points); + + return true; + } + } + + virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE + { + OrthancStone::VolumeProjection projection; + + if (IsGeometryReady() && + DetectProjection(projection, viewportSlice)) + { + const VolumeImageGeometry& geometry = GetProjectionGeometry(projection); + + size_t closest; + + if (geometry.LookupSlice(closest, viewportSlice)) + { + bool isFullQuality = true; // TODO + + std::unique_ptr<Orthanc::Image> frame; + + { + OrthancStone::ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, static_cast<unsigned int>(closest)); + + // TODO Transfer ownership if non-axial, to avoid memcpy + frame.reset(Orthanc::Image::Clone(reader.GetAccessor())); + } + + std::unique_ptr<Slice> slice(geometry.GetSlice(closest)); + + RendererFactory factory(*frame, *slice, isFullQuality); + + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry())); + return; + } + } + + // Error + OrthancStone::CoordinateSystem3D slice; + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, slice)); + } + }; + + + class VolumeImageInteractor : + public IWorldSceneInteractor, + public OrthancStone::IObserver + { + private: + SliceViewerWidget& widget_; + OrthancStone::VolumeProjection projection_; + std::unique_ptr<VolumeImageGeometry> slices_; + size_t slice_; + + protected: + void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message) + { + if (slices_.get() == NULL) + { + const OrthancVolumeImage& image = + dynamic_cast<const OrthancVolumeImage&>(message.GetOrigin()); + + slices_.reset(new VolumeImageGeometry(image, projection_)); + SetSlice(slices_->GetSlicesCount() / 2); + + widget_.FitContent(); + } + } + + virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, + const ViewportGeometry& view, + OrthancStone::MouseButton button, + OrthancStone::KeyboardModifiers modifiers, + int viewportX, + int viewportY, + double x, + double y, + IStatusBar* statusBar, + const std::vector<Touch>& touches) ORTHANC_OVERRIDE + { + return NULL; + } + + virtual void MouseOver(OrthancStone::CairoContext& context, + WorldSceneWidget& widget, + const ViewportGeometry& view, + double x, + double y, + IStatusBar* statusBar) ORTHANC_OVERRIDE + { + } + + virtual void MouseWheel(WorldSceneWidget& widget, + OrthancStone::MouseWheelDirection direction, + OrthancStone::KeyboardModifiers modifiers, + IStatusBar* statusBar) ORTHANC_OVERRIDE + { + int scale = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1); + + switch (direction) + { + case OrthancStone::MouseWheelDirection_Up: + OffsetSlice(-scale); + break; + + case OrthancStone::MouseWheelDirection_Down: + OffsetSlice(scale); + break; + + default: + break; + } + } + + virtual void KeyPressed(WorldSceneWidget& widget, + OrthancStone::KeyboardKeys key, + char keyChar, + OrthancStone::KeyboardModifiers modifiers, + IStatusBar* statusBar) ORTHANC_OVERRIDE + { + switch (keyChar) + { + case 's': + widget.FitContent(); + break; + + default: + break; + } + } + + public: + VolumeImageInteractor(OrthancStone::MessageBroker& broker, + OrthancVolumeImage& volume, + SliceViewerWidget& widget, + OrthancStone::VolumeProjection projection) : + IObserver(broker), + widget_(widget), + projection_(projection) + { + widget.SetInteractor(*this); + + volume.RegisterObserverCallback( + new OrthancStone::Callable<VolumeImageInteractor, ISlicedVolume::GeometryReadyMessage> + (*this, &VolumeImageInteractor::OnGeometryReady)); + } + + bool IsGeometryReady() const + { + return slices_.get() != NULL; + } + + size_t GetSlicesCount() const + { + if (slices_.get() == NULL) + { + return 0; + } + else + { + return slices_->GetSlicesCount(); + } + } + + void OffsetSlice(int offset) + { + if (slices_.get() != NULL) + { + int slice = static_cast<int>(slice_) + offset; + + if (slice < 0) + { + slice = 0; + } + + if (slice >= static_cast<int>(slices_->GetSlicesCount())) + { + slice = static_cast<unsigned int>(slices_->GetSlicesCount()) - 1; + } + + if (slice != static_cast<int>(slice_)) + { + SetSlice(slice); + } + } + } + + void SetSlice(size_t slice) + { + if (slices_.get() != NULL) + { + slice_ = slice; + + std::unique_ptr<Slice> tmp(slices_->GetSlice(slice_)); + widget_.SetSlice(tmp->GetGeometry()); + } + } + }; + + + + class ReferenceLineSource : public IVolumeSlicer + { + private: + class RendererFactory : public LayerReadyMessage::IRendererFactory + { + private: + double x1_; + double y1_; + double x2_; + double y2_; + const OrthancStone::CoordinateSystem3D& slice_; + + public: + RendererFactory(double x1, + double y1, + double x2, + double y2, + const OrthancStone::CoordinateSystem3D& slice) : + x1_(x1), + y1_(y1), + x2_(x2), + y2_(y2), + slice_(slice) + { + } + + virtual ILayerRenderer* CreateRenderer() const + { + return new LineLayerRenderer(x1_, y1_, x2_, y2_, slice_); + } + }; + + SliceViewerWidget& otherPlane_; + + public: + ReferenceLineSource(OrthancStone::MessageBroker& broker, + SliceViewerWidget& otherPlane) : + IVolumeSlicer(broker), + otherPlane_(otherPlane) + { + BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); + } + + virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, + const OrthancStone::CoordinateSystem3D& viewportSlice) + { + return false; + } + + virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) + { + Slice reference(viewportSlice, 0.001); + + OrthancStone::Vector p, d; + + const OrthancStone::CoordinateSystem3D& slice = otherPlane_.GetSlice(); + + // Compute the line of intersection between the two slices + if (!OrthancStone::GeometryToolbox::IntersectTwoPlanes(p, d, + slice.GetOrigin(), slice.GetNormal(), + viewportSlice.GetOrigin(), viewportSlice.GetNormal())) + { + // The two slice are parallel, don't try and display the intersection + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); + } + else + { + double x1, y1, x2, y2; + viewportSlice.ProjectPoint(x1, y1, p); + viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d); + + const OrthancStone::Extent2D extent = otherPlane_.GetSceneExtent(); + + if (OrthancStone::GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, + x1, y1, x2, y2, + extent.GetX1(), extent.GetY1(), + extent.GetX2(), extent.GetY2())) + { + RendererFactory factory(x1, y1, x2, y2, slice); + BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry())); + } + else + { + // Error: Parallel slices + BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); + } + } + } + }; +}