# HG changeset patch # User Sebastien Jodogne # Date 1558356963 -7200 # Node ID f334b098b243284833b999e393765ce37aa47d04 # Parent 70d1c28560b38103d56a0bdf9b84cad93bdf2400 IDicomVolumeSource diff -r 70d1c28560b3 -r f334b098b243 Samples/Sdl/Loader.cpp --- a/Samples/Sdl/Loader.cpp Mon May 20 13:40:23 2019 +0200 +++ b/Samples/Sdl/Loader.cpp Mon May 20 14:56:03 2019 +0200 @@ -19,17 +19,19 @@ **/ // From Stone +#include "../../Framework/Loaders/BasicFetchingItemsSorter.h" +#include "../../Framework/Loaders/BasicFetchingStrategy.h" #include "../../Framework/Messages/ICallable.h" #include "../../Framework/Messages/IMessage.h" #include "../../Framework/Messages/IObservable.h" #include "../../Framework/Messages/MessageBroker.h" +#include "../../Framework/Scene2D/ColorTextureSceneLayer.h" +#include "../../Framework/Scene2D/FloatTextureSceneLayer.h" +#include "../../Framework/Scene2D/Scene2D.h" #include "../../Framework/StoneInitialization.h" #include "../../Framework/Toolbox/GeometryToolbox.h" #include "../../Framework/Toolbox/SlicesSorter.h" #include "../../Framework/Volumes/ImageBuffer3D.h" -#include "../../Framework/Scene2D/Scene2D.h" -#include "../../Framework/Loaders/BasicFetchingStrategy.h" -#include "../../Framework/Loaders/BasicFetchingItemsSorter.h" // From Orthanc framework #include @@ -814,7 +816,7 @@ OrthancStone::Vector frameOffsets_; bool isColor_; bool hasRescale_; - double rescaleOffset_; + double rescaleIntercept_; double rescaleSlope_; bool hasDefaultWindowing_; float defaultWindowingCenter_; @@ -908,7 +910,7 @@ double doseGridScaling; - if (dicom.ParseDouble(rescaleOffset_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && + if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) { hasRescale_ = true; @@ -916,7 +918,7 @@ else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) { hasRescale_ = true; - rescaleOffset_ = 0; + rescaleIntercept_ = 0; rescaleSlope_ = doseGridScaling; } else @@ -1018,7 +1020,47 @@ return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) && distance <= thickness_ / 2.0); } + + + void ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const + { + if (image.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (hasRescale_) + { + const unsigned int width = image.GetWidth(); + const unsigned int height = image.GetHeight(); + + for (unsigned int y = 0; y < height; y++) + { + float* p = reinterpret_cast(image.GetRow(y)); + + if (useDouble) + { + // Slower, accurate implementation using double + for (unsigned int x = 0; x < width; x++, p++) + { + double value = static_cast(*p); + *p = static_cast(value * rescaleSlope_ + rescaleIntercept_); + } + } + else + { + // Fast, approximate implementation using float + for (unsigned int x = 0; x < width; x++, p++) + { + *p = (*p) * static_cast(rescaleSlope_) + static_cast(rescaleIntercept_); + } + } + } + } + } }; + Data data_; @@ -1111,11 +1153,11 @@ return data_.hasRescale_; } - double GetRescaleOffset() const + double GetRescaleIntercept() const { if (data_.hasRescale_) { - return data_.rescaleOffset_; + return data_.rescaleIntercept_; } else { @@ -1168,6 +1210,58 @@ { return data_.expectedPixelFormat_; } + + + OrthancStone::TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const + { + assert(sizeof(float) == 4); + + Orthanc::PixelFormat sourceFormat = source.GetFormat(); + + if (sourceFormat != GetExpectedPixelFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + if (sourceFormat == Orthanc::PixelFormat_RGB24) + { + // This is the case of a color image. No conversion has to be done. + return new OrthancStone::ColorTextureSceneLayer(source); + } + else + { + if (sourceFormat != Orthanc::PixelFormat_Grayscale16 && + sourceFormat != Orthanc::PixelFormat_Grayscale32 && + sourceFormat != Orthanc::PixelFormat_SignedGrayscale16) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + std::auto_ptr texture; + + { + // This is the case of a grayscale frame. Convert it to Float32. + std::auto_ptr converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, + source.GetWidth(), + source.GetHeight(), + false)); + Orthanc::ImageProcessing::Convert(*converted, source); + + // Correct rescale slope/intercept if need be + data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32)); + + texture.reset(new OrthancStone::FloatTextureSceneLayer(*converted)); + } + + if (data_.hasDefaultWindowing_) + { + texture->SetCustomWindowing(data_.defaultWindowingCenter_, + data_.defaultWindowingWidth_); + } + + return texture.release(); + } + } }; @@ -1399,10 +1493,26 @@ } } }; + + + + class IDicomVolumeSource : public boost::noncopyable + { + public: + virtual ~IDicomVolumeSource() + { + } + + virtual const DicomVolumeImage& GetVolume() const = 0; + + virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0; + }; - class VolumeSeriesOrthancLoader : public OrthancStone::IObserver + class VolumeSeriesOrthancLoader : + public OrthancStone::IObserver, + public IDicomVolumeSource { private: static const unsigned int LOW_QUALITY = 0; @@ -1579,6 +1689,26 @@ oracle_.Schedule(*this, command.release()); } + + + virtual const DicomVolumeImage& GetVolume() const + { + return volume_; + } + + + virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) + { + if (strategy_.get() == NULL) + { + // Should have called GetVolume().HasGeometry() before + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + strategy_->SetCurrent(sliceIndex); + } + } }; @@ -1661,31 +1791,32 @@ - class DicomVolumeSlicer : public IVolumeSlicer + class DicomVolumeMPRSlicer : public IVolumeSlicer { private: OrthancStone::Scene2D& scene_; int layerDepth_; - const DicomVolumeImage& volume_; + IDicomVolumeSource& source_; bool first_; OrthancStone::VolumeProjection lastProjection_; unsigned int lastSliceIndex_; uint64_t lastSliceRevision_; public: - DicomVolumeSlicer(OrthancStone::Scene2D& scene, - int layerDepth, - const DicomVolumeImage& volume) : + DicomVolumeMPRSlicer(OrthancStone::Scene2D& scene, + int layerDepth, + IDicomVolumeSource& source) : scene_(scene), layerDepth_(layerDepth), - volume_(volume), + source_(source), first_(true) { } virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) { - if (!volume_.HasGeometry()) + if (!source_.GetVolume().HasGeometry() || + source_.GetVolume().GetSlicesCount() == 0) { scene_.DeleteLayer(layerDepth_); return; @@ -1693,7 +1824,7 @@ OrthancStone::VolumeProjection projection; unsigned int sliceIndex; - if (!volume_.GetImage().GetGeometry().DetectSlice(projection, sliceIndex, plane)) + if (!source_.GetVolume().GetImage().GetGeometry().DetectSlice(projection, sliceIndex, plane)) { // The cutting plane is neither axial, nor coronal, nor // sagittal. Could use "VolumeReslicer" here. @@ -1704,35 +1835,53 @@ uint64_t sliceRevision; if (projection == OrthancStone::VolumeProjection_Axial) { - sliceRevision = volume_.GetSliceRevision(sliceIndex); + sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex); + + if (first_ || + lastSliceIndex_ != sliceIndex) + { + // Reorder the prefetching queue + source_.NotifyAxialSliceAccessed(sliceIndex); + } } else { // For coronal and sagittal projections, we take the global // revision of the volume - sliceRevision = volume_.GetRevision(); + sliceRevision = source_.GetVolume().GetRevision(); } if (first_ || - lastProjection_ == projection || - lastSliceIndex_ == sliceIndex || - lastSliceRevision_ == sliceRevision) + lastProjection_ != projection || + lastSliceIndex_ != sliceIndex || + lastSliceRevision_ != sliceRevision) { - // Eiter the viewport plane, or the content of the slice have not + // Either the viewport plane, or the content of the slice have not // changed since the last time the layer was set: Update is needed first_ = false; lastProjection_ = projection; lastSliceIndex_ = sliceIndex; lastSliceRevision_ = sliceRevision; + + std::auto_ptr texture; { - OrthancStone::ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, sliceIndex); + const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters + (projection == OrthancStone::VolumeProjection_Axial ? sliceIndex : 0); + + OrthancStone::ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex); + texture.reset(parameters.CreateTexture(reader.GetAccessor())); + } - // TODO: Convert the image to Float32 or RGB24 - - // TODO: Set the layer - } + // TODO - + // void SetOrigin(double x, double y); + // void SetPixelSpacing(double sx, double sy); + // void SetAngle(double angle); + // void SetLinearInterpolation(bool isLinearInterpolation); + + + scene_.SetLayer(layerDepth_, texture.release()); } } };