changeset 2169:fe5406abd43f

added separate class Windowing
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 21 Oct 2024 15:40:34 +0200
parents 14d6080660e7
children 7e8b918b0482
files Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Sources/Loaders/SeriesFramesLoader.cpp OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp OrthancStone/Sources/Toolbox/DicomInstanceParameters.h OrthancStone/Sources/Toolbox/Windowing.cpp OrthancStone/Sources/Toolbox/Windowing.h OrthancStone/UnitTestsSources/DicomTests.cpp
diffstat 8 files changed, 229 insertions(+), 175 deletions(-) [+]
line wrap: on
line diff
--- 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<double>(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<SeriesCursor>                cursor_;
   float                                        windowingCenter_;
   float                                        windowingWidth_;
-  std::vector<float>                           windowingPresetCenters_;
-  std::vector<float>                           windowingPresetWidths_;
-  float                                        windowingDefaultCenter_;
-  float                                        windowingDefaultWidth_;
+  std::vector<OrthancStone::Windowing>         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<std::string>(i + 1);
       }
@@ -4396,7 +4374,7 @@
   {
     try
     {
-      GetViewport(canvas)->SetWindowing(center, width);
+      GetViewport(canvas)->SetWindowing(OrthancStone::Windowing(center, width));
     }
     EXTERN_CATCH_EXCEPTIONS;
   }  
--- 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
--- 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<Orthanc::IDynamicObject>  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<float>(payload.GetWindowing().GetWidth());
         if (w <= 0.01f)
         {
           w = 0.01f;  // Prevent division by zero
         }
 
-        const float c = payload.GetWindowingCenter();
+        const float c = static_cast<float>(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<std::string, std::string> arguments, headers;
-        arguments["window"] = (boost::lexical_cast<std::string>(c) + "," +
-                               boost::lexical_cast<std::string>(w) + ",linear");
+        arguments["window"] = (boost::lexical_cast<std::string>(windowing.GetCenter()) + "," +
+                               boost::lexical_cast<std::string>(windowing.GetWidth()) + ",linear");
         headers["Accept"] = "image/jpeg";
 
         std::unique_ptr<Payload> payload(new Payload(source, index, sopInstanceUid, quality, protection.release()));
-        payload->SetWindowing(c, w);
+        payload->SetWindowing(windowing);
 
         {
           std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
--- 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<float>(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<float>(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<float>(center - width / 2.0);
-    high = static_cast<float>(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())
--- 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 <IDynamicObject.h>
 #include <DicomFormat/DicomImageInformation.h>
@@ -57,8 +58,7 @@
       bool                hasRescale_;
       double              rescaleIntercept_;
       double              rescaleSlope_;
-      Vector              windowingPresetCenters_;
-      Vector              windowingPresetWidths_;
+      std::vector<Windowing>  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;
 
--- /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
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#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_));
+  }
+}
--- /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
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#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;
+  };
+}
--- 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());
 }