# HG changeset patch # User Sebastien Jodogne # Date 1506974501 -7200 # Node ID ba83e38cf3ffc0e869afe5708cedc7e107a12749 # Parent a4d0b6c82b292ec5dc3aa79f143d3ecc69497745 rendering of rt-dose diff -r a4d0b6c82b29 -r ba83e38cf3ff Applications/Samples/SingleVolumeApplication.h --- a/Applications/Samples/SingleVolumeApplication.h Mon Oct 02 14:31:26 2017 +0200 +++ b/Applications/Samples/SingleVolumeApplication.h Mon Oct 02 22:01:41 2017 +0200 @@ -23,8 +23,7 @@ #include "SampleApplicationBase.h" #include "../../Framework/dev.h" -//#include "SampleInteractor.h" -#include "../../Framework/Widgets/LayerWidget.h" +#include "../../Framework/Layers/ILayerSource.h" #include "../../Framework/Layers/LineMeasureTracker.h" #include "../../Framework/Layers/CircleMeasureTracker.h" @@ -35,9 +34,44 @@ { namespace Samples { - class SingleVolumeApplication : - public SampleApplicationBase + class SingleVolumeApplication : public SampleApplicationBase { + private: + class Interactor : public VolumeImageInteractor + { + private: + LayerWidget& widget_; + size_t layer_; + + protected: + virtual void NotifySliceChange(const ISlicedVolume& volume, + const size_t& sliceIndex, + const Slice& slice) + { + const OrthancVolumeImage& image = dynamic_cast(volume); + + RenderStyle s = widget_.GetLayerStyle(layer_); + + if (image.FitWindowingToRange(s, slice.GetConverter())) + { + //printf("ICI: %f => %f\n", s.customWindowCenter_, s.customWindowWidth_); + widget_.SetLayerStyle(layer_, s); + } + } + + public: + Interactor(OrthancVolumeImage& volume, + LayerWidget& widget, + VolumeProjection projection, + size_t layer) : + VolumeImageInteractor(volume, widget, projection), + widget_(widget), + layer_(layer) + { + printf("OOO\n"); + } + }; + public: virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) { @@ -124,8 +158,8 @@ std::auto_ptr widget(new LayerWidget); -#if 1 - std::auto_ptr volume(new OrthancVolumeImage(context.GetWebService())); +#if 0 + std::auto_ptr volume(new OrthancVolumeImage(context.GetWebService(), true)); if (series.empty()) { volume->ScheduleLoadInstance(instance); @@ -137,19 +171,33 @@ widget->AddLayer(new VolumeImageSource(*volume)); - context.AddInteractor(new VolumeImageInteractor(*volume, *widget, projection)); + context.AddInteractor(new Interactor(*volume, *widget, projection, 0)); context.AddVolume(volume.release()); + + { + RenderStyle s; + s.alpha_ = 1; + s.applyLut_ = true; + s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; + s.interpolation_ = ImageInterpolation_Linear; + widget->SetLayerStyle(0, s); + } #else - std::auto_ptr ct(new OrthancVolumeImage(context.GetWebService())); - ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d"); + std::auto_ptr ct(new OrthancVolumeImage(context.GetWebService(), false)); + //ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d"); + //ct->ScheduleLoadSeries("3025d8df-a82f3b00-83942fa3-ee6a6be3-a8bf32e8"); + ct->ScheduleLoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); - std::auto_ptr pet(new OrthancVolumeImage(context.GetWebService())); - pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); + std::auto_ptr pet(new OrthancVolumeImage(context.GetWebService(), true)); + //pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); + //pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); + //pet->ScheduleLoadInstance("337876a1-a68a9718-f15abccd-38faafa1-b99b496a"); + pet->ScheduleLoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); widget->AddLayer(new VolumeImageSource(*ct)); widget->AddLayer(new VolumeImageSource(*pet)); - context.AddInteractor(new VolumeImageInteractor(*pet, *widget, projection)); + context.AddInteractor(new Interactor(*pet, *widget, projection, 1)); context.AddVolume(ct.release()); context.AddVolume(pet.release()); @@ -165,7 +213,7 @@ RenderStyle s; //s.drawGrid_ = true; s.SetColor(255, 0, 0); // Draw missing PET layer in red - s.alpha_ = 0.3; + s.alpha_ = 0.5; s.applyLut_ = true; s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; s.interpolation_ = ImageInterpolation_Linear; diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Layers/SliceOutlineRenderer.h --- a/Framework/Layers/SliceOutlineRenderer.h Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Layers/SliceOutlineRenderer.h Mon Oct 02 22:01:41 2017 +0200 @@ -29,12 +29,20 @@ class SliceOutlineRenderer : public ILayerRenderer { private: - Slice slice_; - RenderStyle style_; + CoordinateSystem3D geometry_; + double pixelSpacingX_; + double pixelSpacingY_; + unsigned int width_; + unsigned int height_; + RenderStyle style_; public: - SliceOutlineRenderer(const Slice& slice) : - slice_(slice) + SliceOutlineRenderer(const Slice& slice) : + geometry_(slice.GetGeometry()), + pixelSpacingX_(slice.GetPixelSpacingX()), + pixelSpacingY_(slice.GetPixelSpacingY()), + width_(slice.GetWidth()), + height_(slice.GetHeight()) { } @@ -48,7 +56,7 @@ virtual const CoordinateSystem3D& GetLayerSlice() { - return slice_.GetGeometry(); + return geometry_; } virtual bool IsFullQuality() diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Toolbox/DicomFrameConverter.cpp --- a/Framework/Toolbox/DicomFrameConverter.cpp Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Toolbox/DicomFrameConverter.cpp Mon Oct 02 22:01:41 2017 +0200 @@ -39,26 +39,7 @@ rescaleSlope_ = 1; defaultWindowCenter_ = 128; defaultWindowWidth_ = 256; - } - - - Orthanc::PixelFormat DicomFrameConverter::GetExpectedPixelFormat() const - { - // TODO Add more checks, e.g. on the number of bytes per value - // (cf. DicomImageInformation.h in Orthanc) - - if (isColor_) - { - return Orthanc::PixelFormat_RGB24; - } - else if (isSigned_) - { - return Orthanc::PixelFormat_SignedGrayscale16; - } - else - { - return Orthanc::PixelFormat_Grayscale16; - } + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; } @@ -85,11 +66,22 @@ isSigned_ = (tmp == 1); - if (dicom.ParseFloat(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && - dicom.ParseFloat(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) + double doseGridScaling; + bool isRTDose = false; + + if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && + dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) { hasRescale_ = true; } + else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) + { + // This is for RT-DOSE + hasRescale_ = true; + isRTDose = true; + rescaleIntercept_ = 0; + rescaleSlope_ = doseGridScaling; + } std::string photometric; if (dicom.CopyToString(photometric, Orthanc::DICOM_TAG_PHOTOMETRIC_INTERPRETATION, false)) @@ -104,6 +96,26 @@ isColor_ = (photometric != "MONOCHROME1" && photometric != "MONOCHROME2"); + + // TODO Add more checks, e.g. on the number of bytes per value + // (cf. DicomImageInformation.h in Orthanc) + + if (isRTDose) + { + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; + } + else if (isColor_) + { + expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; + } + else if (isSigned_) + { + expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; + } + else + { + expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; + } } @@ -130,6 +142,7 @@ } assert(sourceFormat == Orthanc::PixelFormat_Grayscale16 || + sourceFormat == Orthanc::PixelFormat_Grayscale32 || sourceFormat == Orthanc::PixelFormat_SignedGrayscale16); // This is the case of a grayscale frame. Convert it to Float32. @@ -142,25 +155,51 @@ source.reset(NULL); // We don't need the source frame anymore // Correct rescale slope/intercept if need be + ApplyRescale(*converted, sourceFormat != Orthanc::PixelFormat_Grayscale32); + + source = converted; + } + + + void DicomFrameConverter::ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const + { + if (image.GetFormat() != Orthanc::PixelFormat_Float32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + if (hasRescale_) { - for (unsigned int y = 0; y < converted->GetHeight(); y++) + for (unsigned int y = 0; y < image.GetHeight(); y++) { - float* p = reinterpret_cast(converted->GetRow(y)); - for (unsigned int x = 0; x < converted->GetWidth(); x++, p++) - { - float value = *p; + float* p = reinterpret_cast(image.GetRow(y)); - if (hasRescale_) + if (useDouble) + { + // Slower, accurate implementation using double + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) { - value = value * rescaleSlope_ + rescaleIntercept_; + double value = static_cast(*p); + *p = static_cast(value * rescaleSlope_ + rescaleIntercept_); } - - *p = value; + } + else + { + // Fast, approximate implementation using float + for (unsigned int x = 0; x < image.GetWidth(); x++, p++) + { + *p = (*p) * static_cast(rescaleSlope_) + static_cast(rescaleIntercept_); + } } } } - - source = converted; } + + + double DicomFrameConverter::Apply(double x) const + { + return x * rescaleSlope_ + rescaleIntercept_; + } + } diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Toolbox/DicomFrameConverter.h --- a/Framework/Toolbox/DicomFrameConverter.h Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Toolbox/DicomFrameConverter.h Mon Oct 02 22:01:41 2017 +0200 @@ -41,10 +41,12 @@ bool isSigned_; bool isColor_; bool hasRescale_; - float rescaleIntercept_; - float rescaleSlope_; - float defaultWindowCenter_; - float defaultWindowWidth_; + double rescaleIntercept_; + double rescaleSlope_; + double defaultWindowCenter_; + double defaultWindowWidth_; + + Orthanc::PixelFormat expectedPixelFormat_; void SetDefaultParameters(); @@ -54,20 +56,28 @@ SetDefaultParameters(); } - Orthanc::PixelFormat GetExpectedPixelFormat() const; + Orthanc::PixelFormat GetExpectedPixelFormat() const + { + return expectedPixelFormat_; + } void ReadParameters(const Orthanc::DicomMap& dicom); - float GetDefaultWindowCenter() const + double GetDefaultWindowCenter() const { return defaultWindowCenter_; } - float GetDefaultWindowWidth() const + double GetDefaultWindowWidth() const { return defaultWindowWidth_; } - void ConvertFrame(std::auto_ptr& source) const; + void ConvertFrame(std::auto_ptr& source) const; + + void ApplyRescale(Orthanc::ImageAccessor& image, + bool useDouble) const; + + double Apply(double x) const; }; } diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Toolbox/MessagingToolbox.cpp --- a/Framework/Toolbox/MessagingToolbox.cpp Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Toolbox/MessagingToolbox.cpp Mon Oct 02 22:01:41 2017 +0200 @@ -422,6 +422,7 @@ AddTag(target, source, Orthanc::DICOM_TAG_BITS_ALLOCATED); AddTag(target, source, Orthanc::DICOM_TAG_BITS_STORED); AddTag(target, source, Orthanc::DICOM_TAG_COLUMNS); + AddTag(target, source, Orthanc::DICOM_TAG_DOSE_GRID_SCALING); AddTag(target, source, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER); AddTag(target, source, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR); AddTag(target, source, Orthanc::DICOM_TAG_HIGH_BIT); diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Toolbox/OrthancSlicesLoader.cpp --- a/Framework/Toolbox/OrthancSlicesLoader.cpp Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Toolbox/OrthancSlicesLoader.cpp Mon Oct 02 22:01:41 2017 +0200 @@ -90,7 +90,8 @@ SliceImageQuality GetQuality() const { - assert(mode_ == Mode_LoadImage); + assert(mode_ == Mode_LoadImage || + mode_ == Mode_LoadRawImage); return quality_; } @@ -160,6 +161,7 @@ std::auto_ptr tmp(new Operation(Mode_LoadRawImage)); tmp->sliceIndex_ = sliceIndex; tmp->slice_ = &slice; + tmp->quality_ = SliceImageQuality_Full; return tmp.release(); } }; @@ -306,10 +308,10 @@ for (unsigned int frame = 0; frame < frames; frame++) { - Slice slice; - if (slice.ParseOrthancFrame(dicom, instances[i], frame)) + std::auto_ptr slice(new Slice); + if (slice->ParseOrthancFrame(dicom, instances[i], frame)) { - slices_.AddSlice(slice); + slices_.AddSlice(slice.release()); } else { @@ -376,10 +378,10 @@ for (unsigned int frame = 0; frame < frames; frame++) { - Slice slice; - if (slice.ParseOrthancFrame(dicom, instanceId, frame)) + std::auto_ptr slice(new Slice); + if (slice->ParseOrthancFrame(dicom, instanceId, frame)) { - slices_.AddSlice(slice); + slices_.AddSlice(slice.release()); } else { @@ -413,11 +415,11 @@ Orthanc::DicomMap dicom; MessagingToolbox::ConvertDataset(dicom, dataset); - Slice slice; - if (slice.ParseOrthancFrame(dicom, instanceId, frame)) + std::auto_ptr slice(new Slice); + if (slice->ParseOrthancFrame(dicom, instanceId, frame)) { LOG(INFO) << "Loaded instance " << instanceId; - slices_.AddSlice(slice); + slices_.AddSlice(slice.release()); userCallback_.NotifyGeometryReady(*this); } else @@ -613,8 +615,36 @@ NotifySliceImageSuccess(operation, image); } + + + class StringImage : + public Orthanc::ImageAccessor, + public boost::noncopyable + { + private: + std::string buffer_; - + public: + StringImage(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height, + std::string& buffer) + { + if (buffer.size() != Orthanc::GetBytesPerPixel(format) * width * height) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + + buffer_.swap(buffer); // The source buffer is now empty + + void* data = (buffer_.empty() ? NULL : &buffer_[0]); + + AssignWritable(format, width, height, + Orthanc::GetBytesPerPixel(format) * width, data); + } + }; + + void OrthancSlicesLoader::ParseSliceRawImage(const Operation& operation, const void* answer, size_t size) @@ -624,7 +654,39 @@ std::string raw; compressor.Uncompress(raw, answer, size); - printf("[%d => %d]\n", size, raw.size()); + const Orthanc::DicomImageInformation& info = operation.GetSlice().GetImageInformation(); + unsigned int frame = operation.GetSlice().GetFrame(); + + if (info.GetBitsAllocated() == 32 && + info.GetBitsStored() == 32 && + info.GetHighBit() == 31 && + info.GetChannelCount() == 1 && + !info.IsSigned() && + info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 && + raw.size() == info.GetWidth() * info.GetHeight() * 4) + { + // This is the case of RT-DOSE (uint32_t values) + + std::auto_ptr image + (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(), + info.GetHeight(), raw)); + + for (unsigned int y = 0; y < image->GetHeight(); y++) + { + uint32_t *p = reinterpret_cast(image->GetRow(y)); + for (unsigned int x = 0; x < image->GetWidth(); x++, p++) + { + *p = le32toh(*p); + } + } + + NotifySliceImageSuccess(operation, image); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Toolbox/Slice.cpp --- a/Framework/Toolbox/Slice.cpp Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Toolbox/Slice.cpp Mon Oct 02 22:01:41 2017 +0200 @@ -107,6 +107,7 @@ orthancInstanceId_ = instanceId; frame_ = frame; type_ = Type_OrthancDecodableFrame; + imageInformation_.reset(new Orthanc::DicomImageInformation(dataset)); if (!dataset.CopyToString(sopClassUid_, Orthanc::DICOM_TAG_SOP_CLASS_UID, false) || sopClassUid_.empty()) @@ -314,4 +315,18 @@ points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5 * sx, (h - 0.5) * sy)); points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, (h - 0.5) * sy)); } + + + const Orthanc::DicomImageInformation& Slice::GetImageInformation() const + { + if (imageInformation_.get() == NULL) + { + // Only available if constructing the "Slice" object with a DICOM map + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *imageInformation_; + } + } } diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Toolbox/Slice.h --- a/Framework/Toolbox/Slice.h Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Toolbox/Slice.h Mon Oct 02 22:01:41 2017 +0200 @@ -24,11 +24,11 @@ #include "CoordinateSystem3D.h" #include "DicomFrameConverter.h" -#include +#include namespace OrthancStone { - class Slice + class Slice : public boost::noncopyable { private: enum Type @@ -47,17 +47,20 @@ std::string orthancInstanceId_; std::string sopClassUid_; unsigned int frame_; - unsigned int frameCount_; + unsigned int frameCount_; // TODO : Redundant with "imageInformation_" CoordinateSystem3D geometry_; double pixelSpacingX_; double pixelSpacingY_; double thickness_; - unsigned int width_; - unsigned int height_; - DicomFrameConverter converter_; - + unsigned int width_; // TODO : Redundant with "imageInformation_" + unsigned int height_; // TODO : Redundant with "imageInformation_" + DicomFrameConverter converter_; // TODO : Partially redundant with "imageInformation_" + + std::auto_ptr imageInformation_; + public: - Slice() : type_(Type_Invalid) + Slice() : + type_(Type_Invalid) { } @@ -131,5 +134,7 @@ bool ContainsPlane(const CoordinateSystem3D& plane) const; void GetExtent(std::vector& points) const; + + const Orthanc::DicomImageInformation& GetImageInformation() const; }; } diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Toolbox/SlicesSorter.cpp --- a/Framework/Toolbox/SlicesSorter.cpp Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Toolbox/SlicesSorter.cpp Mon Oct 02 22:01:41 2017 +0200 @@ -28,20 +28,25 @@ class SlicesSorter::SliceWithDepth : public boost::noncopyable { private: - Slice slice_; - double depth_; + std::auto_ptr slice_; + double depth_; public: - SliceWithDepth(const Slice& slice) : + SliceWithDepth(Slice* slice) : slice_(slice), depth_(0) { + if (slice == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } } void SetNormal(const Vector& normal) { + assert(slice_.get() != NULL); depth_ = boost::numeric::ublas::inner_prod - (slice_.GetGeometry().GetOrigin(), normal); + (slice_->GetGeometry().GetOrigin(), normal); } double GetDepth() const @@ -51,7 +56,8 @@ const Slice& GetSlice() const { - return slice_; + assert(slice_.get() != NULL); + return *slice_; } }; @@ -76,7 +82,7 @@ } - void SlicesSorter::AddSlice(const Slice& slice) + void SlicesSorter::AddSlice(Slice* slice) { slices_.push_back(new SliceWithDepth(slice)); } diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Toolbox/SlicesSorter.h --- a/Framework/Toolbox/SlicesSorter.h Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Toolbox/SlicesSorter.h Mon Oct 02 22:01:41 2017 +0200 @@ -48,7 +48,7 @@ slices_.reserve(count); } - void AddSlice(const Slice& slice); + void AddSlice(Slice* slice); // Takes ownership size_t GetSliceCount() const { diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Volumes/ImageBuffer3D.cpp --- a/Framework/Volumes/ImageBuffer3D.cpp Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Volumes/ImageBuffer3D.cpp Mon Oct 02 22:01:41 2017 +0200 @@ -112,12 +112,15 @@ ImageBuffer3D::ImageBuffer3D(Orthanc::PixelFormat format, unsigned int width, unsigned int height, - unsigned int depth) : + unsigned int depth, + bool computeRange) : image_(format, width, height * depth, false), format_(format), width_(width), height_(height), - depth_(depth) + depth_(depth), + computeRange_(computeRange), + hasRange_(false) { GeometryToolbox::AssignVector(voxelDimensions_, 1, 1, 1); @@ -265,6 +268,91 @@ } + void ImageBuffer3D::ExtendImageRange(const Orthanc::ImageAccessor& slice) + { + if (!computeRange_ || + slice.GetWidth() == 0 || + slice.GetHeight() == 0) + { + return; + } + + float sliceMin, sliceMax; + + switch (slice.GetFormat()) + { + case Orthanc::PixelFormat_Grayscale8: + case Orthanc::PixelFormat_Grayscale16: + case Orthanc::PixelFormat_Grayscale32: + case Orthanc::PixelFormat_SignedGrayscale16: + { + int64_t a, b; + Orthanc::ImageProcessing::GetMinMaxIntegerValue(a, b, slice); + sliceMin = static_cast(a); + sliceMax = static_cast(b); + break; + } + + case Orthanc::PixelFormat_Float32: + Orthanc::ImageProcessing::GetMinMaxFloatValue(sliceMin, sliceMax, slice); + break; + + default: + return; + } + + if (hasRange_) + { + minValue_ = std::min(minValue_, sliceMin); + maxValue_ = std::max(maxValue_, sliceMax); + } + else + { + hasRange_ = true; + minValue_ = sliceMin; + maxValue_ = sliceMax; + } + } + + + bool ImageBuffer3D::GetRange(float& minValue, + float& maxValue) const + { + if (hasRange_) + { + minValue = minValue_; + maxValue = maxValue_; + return true; + } + else + { + return false; + } + } + + + bool ImageBuffer3D::FitWindowingToRange(RenderStyle& style, + const DicomFrameConverter& converter) const + { + if (hasRange_) + { + style.windowing_ = ImageWindowing_Custom; + style.customWindowCenter_ = converter.Apply((minValue_ + maxValue_) / 2.0); + style.customWindowWidth_ = converter.Apply(maxValue_ - minValue_); + + if (style.customWindowWidth_ > 1) + { + return true; + } + } + + style.windowing_ = ImageWindowing_Custom; + style.customWindowCenter_ = 128.0; + style.customWindowWidth_ = 256.0; + return false; + } + + ImageBuffer3D::SliceReader::SliceReader(ImageBuffer3D& that, VolumeProjection projection, unsigned int slice) @@ -299,6 +387,10 @@ // TODO throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } + + // Update the dynamic range of the underlying image, if + // "computeRange_" is set to true + that_.ExtendImageRange(accessor_); } } @@ -306,7 +398,8 @@ ImageBuffer3D::SliceWriter::SliceWriter(ImageBuffer3D& that, VolumeProjection projection, unsigned int slice) : - modified_(false) + that_(that), + modified_(false) { switch (projection) { diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Volumes/ImageBuffer3D.h --- a/Framework/Volumes/ImageBuffer3D.h Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Volumes/ImageBuffer3D.h Mon Oct 02 22:01:41 2017 +0200 @@ -22,7 +22,9 @@ #pragma once #include "../Enumerations.h" +#include "../Layers/RenderStyle.h" #include "../Toolbox/CoordinateSystem3D.h" +#include "../Toolbox/DicomFrameConverter.h" #include "../Toolbox/ParallelSlices.h" #include @@ -39,6 +41,12 @@ unsigned int width_; unsigned int height_; unsigned int depth_; + bool computeRange_; + bool hasRange_; + float minValue_; + float maxValue_; + + void ExtendImageRange(const Orthanc::ImageAccessor& slice); Orthanc::ImageAccessor GetAxialSliceAccessor(unsigned int slice, bool readOnly); @@ -52,7 +60,8 @@ ImageBuffer3D(Orthanc::PixelFormat format, unsigned int width, unsigned int height, - unsigned int depth); + unsigned int depth, + bool computeRange); void Clear(); @@ -94,6 +103,12 @@ uint64_t GetEstimatedMemorySize() const; + bool GetRange(float& minValue, + float& maxValue) const; + + bool FitWindowingToRange(RenderStyle& style, + const DicomFrameConverter& converter) const; + class SliceReader : public boost::noncopyable { @@ -116,6 +131,7 @@ class SliceWriter : public boost::noncopyable { private: + ImageBuffer3D& that_; bool modified_; Orthanc::ImageAccessor accessor_; std::auto_ptr sagittal_; // Unused for axial and coronal diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Widgets/LayerWidget.cpp --- a/Framework/Widgets/LayerWidget.cpp Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Widgets/LayerWidget.cpp Mon Oct 02 22:01:41 2017 +0200 @@ -350,6 +350,18 @@ } + const RenderStyle& LayerWidget::GetLayerStyle(size_t layer) const + { + if (layer >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(layers_.size() == styles_.size()); + return styles_[layer]; + } + + void LayerWidget::SetLayerStyle(size_t layer, const RenderStyle& style) { diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/Widgets/LayerWidget.h --- a/Framework/Widgets/LayerWidget.h Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/Widgets/LayerWidget.h Mon Oct 02 22:01:41 2017 +0200 @@ -98,6 +98,8 @@ return layers_.size(); } + const RenderStyle& GetLayerStyle(size_t layer) const; + void SetLayerStyle(size_t layer, const RenderStyle& style); diff -r a4d0b6c82b29 -r ba83e38cf3ff Framework/dev.h --- a/Framework/dev.h Mon Oct 02 14:31:26 2017 +0200 +++ b/Framework/dev.h Mon Oct 02 22:01:41 2017 +0200 @@ -47,7 +47,7 @@ OrthancSlicesLoader loader_; std::auto_ptr image_; std::auto_ptr downloadStack_; - + bool computeRange_; void ScheduleSliceDownload() { @@ -151,7 +151,7 @@ LOG(INFO) << "Creating a volume image of size " << width << "x" << height << "x" << loader.GetSliceCount() << " in " << Orthanc::EnumerationToString(format); - image_.reset(new ImageBuffer3D(format, width, height, loader.GetSliceCount())); + image_.reset(new ImageBuffer3D(format, width, height, loader.GetSliceCount(), computeRange_)); image_->SetAxialGeometry(loader.GetSlice(0).GetGeometry()); image_->SetVoxelDimensions(loader.GetSlice(0).GetPixelSpacingX(), loader.GetSlice(0).GetPixelSpacingY(), spacingZ); @@ -201,8 +201,10 @@ } public: - OrthancVolumeImage(IWebService& orthanc) : - loader_(*this, orthanc) + OrthancVolumeImage(IWebService& orthanc, + bool computeRange) : + loader_(*this, orthanc), + computeRange_(computeRange) { } @@ -244,6 +246,19 @@ return *image_; } } + + bool FitWindowingToRange(RenderStyle& style, + const DicomFrameConverter& converter) const + { + if (image_.get() == NULL) + { + return false; + } + else + { + return image_->FitWindowingToRange(style, converter); + } + } }; @@ -322,8 +337,8 @@ axialThickness * axial.GetGeometry().GetNormal()); reference_ = CoordinateSystem3D(origin, - axial.GetGeometry().GetAxisX(), - -axial.GetGeometry().GetNormal()); + axial.GetGeometry().GetAxisX(), + -axial.GetGeometry().GetNormal()); } void SetupSagittal(const OrthancVolumeImage& volume) @@ -344,8 +359,8 @@ axialThickness * axial.GetGeometry().GetNormal()); reference_ = CoordinateSystem3D(origin, - axial.GetGeometry().GetAxisY(), - axial.GetGeometry().GetNormal()); + axial.GetGeometry().GetAxisY(), + axial.GetGeometry().GetNormal()); } public: @@ -376,6 +391,8 @@ default: throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } + + //sliceThickness_ = 3.0f; // TODO XXXXX } size_t GetSliceCount() const @@ -416,7 +433,7 @@ } } - Slice GetSlice(size_t slice) const + Slice* GetSlice(size_t slice) const { if (slice < 0 || slice >= depth_) @@ -426,12 +443,12 @@ else { CoordinateSystem3D origin(reference_.GetOrigin() + - static_cast(slice) * sliceThickness_ * reference_.GetNormal(), - reference_.GetAxisX(), - reference_.GetAxisY()); + static_cast(slice) * sliceThickness_ * reference_.GetNormal(), + reference_.GetAxisX(), + reference_.GetAxisY()); - return Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_, - width_, height_, converter_); + return new Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_, + width_, height_, converter_); } } }; @@ -564,7 +581,8 @@ { // As the slices of the volumic image are arranged in a box, // we only consider one single reference slice (the one with index 0). - GetProjectionGeometry(projection).GetSlice(0).GetExtent(points); + std::auto_ptr slice(GetProjectionGeometry(projection).GetSlice(0)); + slice->GetExtent(points); return true; } @@ -595,11 +613,11 @@ frame.reset(Orthanc::Image::Clone(reader.GetAccessor())); } - Slice slice = geometry.GetSlice(closest); + std::auto_ptr slice(geometry.GetSlice(closest)); LayerSourceBase::NotifyLayerReady( - FrameRenderer::CreateRenderer(frame.release(), slice, isFullQuality), + FrameRenderer::CreateRenderer(frame.release(), *slice, isFullQuality), //new SliceOutlineRenderer(slice), - slice, false); + *slice, false); return; } } @@ -613,14 +631,15 @@ class VolumeImageInteractor : public IWorldSceneInteractor, - private ISlicedVolume::IObserver + protected ISlicedVolume::IObserver { private: LayerWidget& widget_; VolumeProjection projection_; std::auto_ptr slices_; size_t slice_; - + + protected: virtual void NotifyGeometryReady(const ISlicedVolume& volume) { if (slices_.get() == NULL) @@ -761,7 +780,9 @@ if (slices_.get() != NULL) { slice_ = slice; - widget_.SetSlice(slices_->GetSlice(slice_).GetGeometry()); + + std::auto_ptr tmp(slices_->GetSlice(slice_)); + widget_.SetSlice(tmp->GetGeometry()); } } };