# HG changeset patch # User Sebastien Jodogne # Date 1729518034 -7200 # Node ID fe5406abd43f90077b7700aa193a848213f3f39b # Parent 14d6080660e79176e4b19f598f2a4773f1107ea6 added separate class Windowing diff -r 14d6080660e7 -r fe5406abd43f Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Oct 09 12:50:10 2024 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Mon Oct 21 15:40:34 2024 +0200 @@ -1775,17 +1775,15 @@ static_cast(params.GetHeight())); } - GetViewport().windowingPresetCenters_.resize(params.GetWindowingPresetsCount()); - GetViewport().windowingPresetWidths_.resize(params.GetWindowingPresetsCount()); + GetViewport().windowingPresets_.resize(params.GetWindowingPresetsCount()); for (size_t i = 0; i < params.GetWindowingPresetsCount(); i++) { LOG(INFO) << "Preset windowing " << (i + 1) << "/" << params.GetWindowingPresetsCount() - << ": " << params.GetWindowingPresetCenter(i) - << "," << params.GetWindowingPresetWidth(i); - - GetViewport().windowingPresetCenters_[i] = params.GetWindowingPresetCenter(i); - GetViewport().windowingPresetWidths_[i] = params.GetWindowingPresetWidth(i); + << ": " << params.GetWindowingPreset(i).GetCenter() + << "," << params.GetWindowingPreset(i).GetWidth(); + + GetViewport().windowingPresets_[i] = params.GetWindowingPreset(i); } if (params.GetWindowingPresetsCount() == 0) @@ -1793,21 +1791,7 @@ LOG(INFO) << "No preset windowing"; } - uint32_t bitsStored, pixelRepresentation; - if (dicom.ParseUnsignedInteger32(bitsStored, Orthanc::DICOM_TAG_BITS_STORED) && - dicom.ParseUnsignedInteger32(pixelRepresentation, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION)) - { - // Added in Stone Web viewer > 2.5 - const bool isSigned = (pixelRepresentation != 0); - const float maximum = powf(2.0, bitsStored); - GetViewport().windowingDefaultCenter_ = (isSigned ? 0.0f : maximum / 2.0f); - GetViewport().windowingDefaultWidth_ = maximum; - } - else - { - GetViewport().windowingDefaultCenter_ = 128; - GetViewport().windowingDefaultWidth_ = 256; - } + GetViewport().fallbackWindowing_ = params.GetFallbackWindowing(); GetViewport().SetWindowingPreset(); } @@ -1994,14 +1978,13 @@ } else { - if (GetViewport().windowingPresetCenters_.empty()) + if (GetViewport().windowingPresets_.empty()) { // New in Stone Web viewer 2.2: Deal with Philips multiframe // (cf. mail from Tomas Kenda on 2021-08-17) double windowingCenter, windowingWidth; message.GetDicom().GetDefaultWindowing(windowingCenter, windowingWidth, frameNumber_); - GetViewport().windowingPresetCenters_.push_back(windowingCenter); - GetViewport().windowingPresetWidths_.push_back(windowingWidth); + GetViewport().windowingPresets_.push_back(OrthancStone::Windowing(windowingCenter, windowingWidth)); GetViewport().SetWindowingPreset(); } @@ -2100,10 +2083,8 @@ std::unique_ptr cursor_; float windowingCenter_; float windowingWidth_; - std::vector windowingPresetCenters_; - std::vector windowingPresetWidths_; - float windowingDefaultCenter_; - float windowingDefaultWidth_; + std::vector windowingPresets_; + OrthancStone::Windowing fallbackWindowing_; unsigned int cineRate_; bool inverted_; bool fitNextContent_; @@ -2624,8 +2605,6 @@ context_(context), source_(source), framesCache_(cache), - windowingDefaultCenter_(128), - windowingDefaultWidth_(256), fitNextContent_(true), hasFocusOnInstance_(false), focusFrameNumber_(0), @@ -3174,28 +3153,25 @@ void SetWindowingPreset() { - assert(windowingPresetCenters_.size() == windowingPresetWidths_.size()); - - if (windowingPresetCenters_.empty()) - { - SetWindowing(windowingDefaultCenter_, windowingDefaultWidth_); + if (windowingPresets_.empty()) + { + SetWindowing(fallbackWindowing_); } else { - SetWindowing(windowingPresetCenters_[0], windowingPresetWidths_[0]); + SetWindowing(windowingPresets_[0]); } } - void SetWindowing(float windowingCenter, - float windowingWidth) - { - windowingCenter_ = windowingCenter; - windowingWidth_ = windowingWidth; + void SetWindowing(const OrthancStone::Windowing& windowing) + { + windowingCenter_ = windowing.GetCenter(); + windowingWidth_ = windowing.GetWidth(); UpdateCurrentTextureParameters(); if (observer_.get() != NULL) { - observer_->SignalWindowingUpdated(*this, windowingCenter, windowingWidth); + observer_->SignalWindowingUpdated(*this, windowingCenter_, windowingWidth_); } } @@ -3224,7 +3200,9 @@ Orthanc::ImageProcessing::GetMinMaxFloatValue(minValue, maxValue, texture); } - SetWindowing((minValue + maxValue) / 2.0f, maxValue - minValue); + const float center = (minValue + maxValue) / 2.0f; + const float width = maxValue - minValue; + SetWindowing(OrthancStone::Windowing(center, width)); } void FlipX() @@ -3520,13 +3498,13 @@ target = Json::arrayValue; - for (size_t i = 0; i < windowingPresetCenters_.size(); i++) - { - const float c = windowingPresetCenters_[i]; - const float w = windowingPresetWidths_[i]; + for (size_t i = 0; i < windowingPresets_.size(); i++) + { + const double c = windowingPresets_[i].GetCenter(); + const double w = windowingPresets_[i].GetWidth(); std::string name = "Preset"; - if (windowingPresetCenters_.size() > 1) + if (windowingPresets_.size() > 1) { name += " " + boost::lexical_cast(i + 1); } @@ -4396,7 +4374,7 @@ { try { - GetViewport(canvas)->SetWindowing(center, width); + GetViewport(canvas)->SetWindowing(OrthancStone::Windowing(center, width)); } EXTERN_CATCH_EXCEPTIONS; } diff -r 14d6080660e7 -r fe5406abd43f OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake --- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Wed Oct 09 12:50:10 2024 +0200 +++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Mon Oct 21 15:40:34 2024 +0200 @@ -451,6 +451,8 @@ ${ORTHANC_STONE_ROOT}/Toolbox/UndoRedoStack.h ${ORTHANC_STONE_ROOT}/Toolbox/UnionOfRectangles.cpp ${ORTHANC_STONE_ROOT}/Toolbox/UnionOfRectangles.h + ${ORTHANC_STONE_ROOT}/Toolbox/Windowing.cpp + ${ORTHANC_STONE_ROOT}/Toolbox/Windowing.h ${ORTHANC_STONE_ROOT}/Viewport/DefaultViewportInteractor.cpp ${ORTHANC_STONE_ROOT}/Viewport/IViewport.h diff -r 14d6080660e7 -r fe5406abd43f OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp --- a/OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp Wed Oct 09 12:50:10 2024 +0200 +++ b/OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp Mon Oct 21 15:40:34 2024 +0200 @@ -47,8 +47,7 @@ std::string sopInstanceUid_; // Only used for debug purpose unsigned int quality_; bool hasWindowing_; - float windowingCenter_; - float windowingWidth_; + Windowing windowing_; std::unique_ptr userPayload_; public: @@ -62,8 +61,6 @@ sopInstanceUid_(sopInstanceUid), quality_(quality), hasWindowing_(false), - windowingCenter_(0), - windowingWidth_(0), userPayload_(userPayload) { } @@ -83,12 +80,10 @@ return quality_; } - void SetWindowing(float center, - float width) + void SetWindowing(const Windowing& windowing) { hasWindowing_ = true; - windowingCenter_ = center; - windowingWidth_ = width; + windowing_ = windowing; } bool HasWindowing() const @@ -96,23 +91,11 @@ return hasWindowing_; } - float GetWindowingCenter() const + const Windowing& GetWindowing() const { if (hasWindowing_) { - return windowingCenter_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - float GetWindowingWidth() const - { - if (hasWindowing_) - { - return windowingWidth_; + return windowing_; } else { @@ -227,13 +210,13 @@ Orthanc::Image scaled(parameters.GetExpectedPixelFormat(), reader.GetWidth(), reader.GetHeight(), false); Orthanc::ImageProcessing::Convert(scaled, reader); - float w = payload.GetWindowingWidth(); + float w = static_cast(payload.GetWindowing().GetWidth()); if (w <= 0.01f) { w = 0.01f; // Prevent division by zero } - const float c = payload.GetWindowingCenter(); + const float c = static_cast(payload.GetWindowing().GetCenter()); const float scaling = w / 255.0f; const float offset = (c - w / 2.0f) / scaling; @@ -417,16 +400,15 @@ { const DicomInstanceParameters& parameters = frames_.GetInstanceParameters(index); - float c, w; - parameters.GetWindowingPresetsUnion(c, w); + Windowing windowing = parameters.GetWindowingPresetsUnion(); std::map arguments, headers; - arguments["window"] = (boost::lexical_cast(c) + "," + - boost::lexical_cast(w) + ",linear"); + arguments["window"] = (boost::lexical_cast(windowing.GetCenter()) + "," + + boost::lexical_cast(windowing.GetWidth()) + ",linear"); headers["Accept"] = "image/jpeg"; std::unique_ptr payload(new Payload(source, index, sopInstanceUid, quality, protection.release())); - payload->SetWindowing(c, w); + payload->SetWindowing(windowing); { std::unique_ptr lock(context_.Lock()); diff -r 14d6080660e7 -r fe5406abd43f OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp --- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp Wed Oct 09 12:50:10 2024 +0200 +++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp Mon Oct 21 15:40:34 2024 +0200 @@ -190,29 +190,29 @@ } } - bool ok = false; + + windowingPresets_.clear(); + + Vector centers, widths; - if (LinearAlgebra::ParseVector(windowingPresetCenters_, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && - LinearAlgebra::ParseVector(windowingPresetWidths_, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH)) + if (LinearAlgebra::ParseVector(centers, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && + LinearAlgebra::ParseVector(widths, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH)) { - if (windowingPresetCenters_.size() == windowingPresetWidths_.size()) + if (centers.size() == widths.size()) { - ok = true; + windowingPresets_.resize(centers.size()); + + for (size_t i = 0; i < centers.size(); i++) + { + windowingPresets_[i] = Windowing(centers[i], widths[i]); + } } else { LOG(ERROR) << "Mismatch in the number of preset windowing widths/centers, ignoring this"; - ok = false; } } - if (!ok) - { - // Don't use "Vector::clear()", as it has not the same meaning as "std::vector::clear()" - windowingPresetCenters_.resize(0); - windowingPresetWidths_.resize(0); - } - // This computes the "IndexInSeries" metadata from Orthanc (check // out "Orthanc::ServerIndex::Store()") hasIndexInSeries_ = ( @@ -399,18 +399,45 @@ } + Windowing DicomInstanceParameters::GetFallbackWindowing() const + { + double a, b; + if (tags_->ParseDouble(a, Orthanc::DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE) && + tags_->ParseDouble(b, Orthanc::DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE)) + { + const double center = (a + b) / 2.0f; + const double width = (b - a); + return Windowing(center, width); + } + + // Added in Stone Web viewer > 2.5 + uint32_t bitsStored, pixelRepresentation; + if (tags_->ParseUnsignedInteger32(bitsStored, Orthanc::DICOM_TAG_BITS_STORED) && + tags_->ParseUnsignedInteger32(pixelRepresentation, Orthanc::DICOM_TAG_PIXEL_REPRESENTATION)) + { + const bool isSigned = (pixelRepresentation != 0); + const float maximum = powf(2.0, bitsStored); + return Windowing(isSigned ? 0.0f : maximum / 2.0f, maximum); + } + else + { + // Cannot infer a suitable windowing from the available tags + return Windowing(); + } + } + + size_t DicomInstanceParameters::GetWindowingPresetsCount() const { - assert(data_.windowingPresetCenters_.size() == data_.windowingPresetWidths_.size()); - return data_.windowingPresetCenters_.size(); + return data_.windowingPresets_.size(); } - float DicomInstanceParameters::GetWindowingPresetCenter(size_t i) const + Windowing DicomInstanceParameters::GetWindowingPreset(size_t i) const { if (i < GetWindowingPresetsCount()) { - return static_cast(data_.windowingPresetCenters_[i]); + return data_.windowingPresets_[i]; } else { @@ -418,32 +445,8 @@ } } - - float DicomInstanceParameters::GetWindowingPresetWidth(size_t i) const - { - if (i < GetWindowingPresetsCount()) - { - return static_cast(data_.windowingPresetWidths_[i]); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - - - static void GetWindowingBounds(float& low, - float& high, - double center, // in - double width) // in - { - low = static_cast(center - width / 2.0); - high = static_cast(center + width / 2.0); - } - - void DicomInstanceParameters::GetWindowingPresetsUnion(float& center, - float& width) const + Windowing DicomInstanceParameters::GetWindowingPresetsUnion() const { assert(tags_.get() != NULL); size_t s = GetWindowingPresetsCount(); @@ -452,48 +455,29 @@ { // Use the largest windowing given all the preset windowings // that are available in the DICOM tags - float low, high; - GetWindowingBounds(low, high, GetWindowingPresetCenter(0), GetWindowingPresetWidth(0)); + double low, high; + GetWindowingPreset(0).GetBounds(low, high); for (size_t i = 1; i < s; i++) { - float a, b; - GetWindowingBounds(a, b, GetWindowingPresetCenter(i), GetWindowingPresetWidth(i)); + double a, b; + GetWindowingPreset(i).GetBounds(a, b); low = std::min(low, a); high = std::max(high, b); } assert(low <= high); - if (LinearAlgebra::IsNear(low, high)) + if (!LinearAlgebra::IsNear(low, high)) { - // Cannot infer a suitable windowing from the available tags - center = 128.0f; - width = 256.0f; - } - else - { - center = (low + high) / 2.0f; - width = (high - low); + const double center = (low + high) / 2.0f; + const double width = (high - low); + return Windowing(center, width); } } - else - { - float a, b; - if (tags_->ParseFloat(a, Orthanc::DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE) && - tags_->ParseFloat(b, Orthanc::DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE) && - a < b) - { - center = (a + b) / 2.0f; - width = (b - a); - } - else - { - // Cannot infer a suitable windowing from the available tags - center = 128.0f; - width = 256.0f; - } - } + + // No preset, or presets with an empty range + return GetFallbackWindowing(); } @@ -565,7 +549,8 @@ if (GetWindowingPresetsCount() > 0) { - floatTexture.SetCustomWindowing(GetWindowingPresetCenter(0), GetWindowingPresetWidth(0)); + Windowing preset = GetWindowingPreset(0); + floatTexture.SetCustomWindowing(preset.GetCenter(), preset.GetWidth()); } switch (GetImageInformation().GetPhotometricInterpretation()) diff -r 14d6080660e7 -r fe5406abd43f OrthancStone/Sources/Toolbox/DicomInstanceParameters.h --- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h Wed Oct 09 12:50:10 2024 +0200 +++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h Mon Oct 21 15:40:34 2024 +0200 @@ -26,6 +26,7 @@ #include "../Scene2D/LookupTableTextureSceneLayer.h" #include "../StoneEnumerations.h" #include "../Toolbox/CoordinateSystem3D.h" +#include "Windowing.h" #include #include @@ -57,8 +58,7 @@ bool hasRescale_; double rescaleIntercept_; double rescaleSlope_; - Vector windowingPresetCenters_; - Vector windowingPresetWidths_; + std::vector windowingPresets_; bool hasIndexInSeries_; unsigned int indexInSeries_; std::string doseUnits_; @@ -185,14 +185,13 @@ double GetRescaleSlope() const; + Windowing GetFallbackWindowing() const; + size_t GetWindowingPresetsCount() const; - float GetWindowingPresetCenter(size_t i) const; + Windowing GetWindowingPreset(size_t i) const; - float GetWindowingPresetWidth(size_t i) const; - - void GetWindowingPresetsUnion(float& center, - float& width) const; + Windowing GetWindowingPresetsUnion() const; Orthanc::PixelFormat GetExpectedPixelFormat() const; diff -r 14d6080660e7 -r fe5406abd43f OrthancStone/Sources/Toolbox/Windowing.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Toolbox/Windowing.cpp Mon Oct 21 15:40:34 2024 +0200 @@ -0,0 +1,52 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#include "Windowing.h" + +#include "LinearAlgebra.h" + + +namespace OrthancStone +{ + Windowing::Windowing(double center, + double width) + { + center_ = center; + width_ = std::abs(width); + } + + + void Windowing::GetBounds(double& low, + double& high) const + { + low = center_ - width_ / 2.0; + high = center_ + width_ / 2.0; + } + + + bool Windowing::IsNear(const Windowing& other) const + { + return (LinearAlgebra::IsNear(center_, other.center_) && + LinearAlgebra::IsNear(width_, other.width_)); + } +} diff -r 14d6080660e7 -r fe5406abd43f OrthancStone/Sources/Toolbox/Windowing.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Toolbox/Windowing.h Mon Oct 21 15:40:34 2024 +0200 @@ -0,0 +1,59 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +namespace OrthancStone +{ + class Windowing + { + private: + double center_; + double width_; + + public: + Windowing() : + center_(128), + width_(256) + { + } + + Windowing(double center, + double width); + + double GetCenter() const + { + return center_; + } + + double GetWidth() const + { + return width_; + } + + void GetBounds(double& low, + double& high) const; + + bool IsNear(const Windowing& other) const; + }; +} diff -r 14d6080660e7 -r fe5406abd43f OrthancStone/UnitTestsSources/DicomTests.cpp --- a/OrthancStone/UnitTestsSources/DicomTests.cpp Wed Oct 09 12:50:10 2024 +0200 +++ b/OrthancStone/UnitTestsSources/DicomTests.cpp Mon Oct 21 15:40:34 2024 +0200 @@ -66,13 +66,11 @@ ASSERT_THROW(p->GetRescaleIntercept(), Orthanc::OrthancException); ASSERT_THROW(p->GetRescaleSlope(), Orthanc::OrthancException); ASSERT_EQ(0u, p->GetWindowingPresetsCount()); - ASSERT_THROW(p->GetWindowingPresetCenter(0), Orthanc::OrthancException); - ASSERT_THROW(p->GetWindowingPresetWidth(0), Orthanc::OrthancException); + ASSERT_THROW(p->GetWindowingPreset(0), Orthanc::OrthancException); - float c, w; - p->GetWindowingPresetsUnion(c, w); - ASSERT_FLOAT_EQ(128.0f, c); - ASSERT_FLOAT_EQ(256.0f, w); + OrthancStone::Windowing w = p->GetWindowingPresetsUnion(); + ASSERT_FLOAT_EQ(128.0f, w.GetCenter()); + ASSERT_FLOAT_EQ(256.0f, w.GetWidth()); ASSERT_THROW(p->GetExpectedPixelFormat(), Orthanc::OrthancException); ASSERT_FALSE(p->HasIndexInSeries()); @@ -96,20 +94,19 @@ OrthancStone::DicomInstanceParameters p(m); ASSERT_EQ(3u, p.GetWindowingPresetsCount()); - ASSERT_FLOAT_EQ(10, p.GetWindowingPresetCenter(0)); - ASSERT_FLOAT_EQ(100, p.GetWindowingPresetCenter(1)); - ASSERT_FLOAT_EQ(1000, p.GetWindowingPresetCenter(2)); - ASSERT_FLOAT_EQ(50, p.GetWindowingPresetWidth(0)); - ASSERT_FLOAT_EQ(60, p.GetWindowingPresetWidth(1)); - ASSERT_FLOAT_EQ(70, p.GetWindowingPresetWidth(2)); + ASSERT_FLOAT_EQ(10, p.GetWindowingPreset(0).GetCenter()); + ASSERT_FLOAT_EQ(100, p.GetWindowingPreset(1).GetCenter()); + ASSERT_FLOAT_EQ(1000, p.GetWindowingPreset(2).GetCenter()); + ASSERT_FLOAT_EQ(50, p.GetWindowingPreset(0).GetWidth()); + ASSERT_FLOAT_EQ(60, p.GetWindowingPreset(1).GetWidth()); + ASSERT_FLOAT_EQ(70, p.GetWindowingPreset(2).GetWidth()); const float a = 10.0f - 50.0f / 2.0f; const float b = 1000.0f + 70.0f / 2.0f; - float c, w; - p.GetWindowingPresetsUnion(c, w); - ASSERT_FLOAT_EQ((a + b) / 2.0f, c); - ASSERT_FLOAT_EQ(b - a, w); + OrthancStone::Windowing w = p.GetWindowingPresetsUnion(); + ASSERT_FLOAT_EQ((a + b) / 2.0f, w.GetCenter()); + ASSERT_FLOAT_EQ(b - a, w.GetWidth()); }