changeset 4777:3b78ba359db3

Support detection of windowing and rescale in Philips multiframe images
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 30 Aug 2021 11:41:05 +0200
parents 79d4e155592b
children 50fd70169f6e
files NEWS OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp
diffstat 7 files changed, 278 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Aug 30 10:25:50 2021 +0200
+++ b/NEWS	Mon Aug 30 11:41:05 2021 +0200
@@ -21,6 +21,7 @@
 * Linux Standard Base (LSB) builds of Orthanc can load non-LSB builds of plugins
 * Fix upload of ZIP archives containing a DICOMDIR file
 * Fix computation of the estimated time of arrival in jobs
+* Support detection of windowing and rescale in Philips multiframe
 
 
 Version 1.9.6 (2021-07-21)
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Mon Aug 30 10:25:50 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp	Mon Aug 30 11:41:05 2021 +0200
@@ -3164,10 +3164,10 @@
   }
 
 
-  bool FromDcmtkBridge::LookupSubSequence(DicomMap& target,
-                                          DcmDataset& dataset,
-                                          const DicomPath& path,
-                                          size_t sequenceIndex)
+  bool FromDcmtkBridge::LookupSequenceItem(DicomMap& target,
+                                           DcmDataset& dataset,
+                                           const DicomPath& path,
+                                           size_t sequenceIndex)
   {
     class Visitor : public FromDcmtkBridge::IDicomPathVisitor
     {
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h	Mon Aug 30 10:25:50 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h	Mon Aug 30 11:41:05 2021 +0200
@@ -278,9 +278,9 @@
                             const DcmElement& element,
                             DicomReplaceMode mode);
 
-    static bool LookupSubSequence(DicomMap& target,
-                                  DcmDataset& dataset,
-                                  const DicomPath& path,
-                                  size_t sequenceIndex);
+    static bool LookupSequenceItem(DicomMap& target,
+                                   DcmDataset& dataset,
+                                   const DicomPath& path,
+                                   size_t sequenceIndex);
   };
 }
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Mon Aug 30 10:25:50 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Mon Aug 30 11:41:05 2021 +0200
@@ -78,6 +78,7 @@
 #include "../Images/PamReader.h"
 #include "../Logging.h"
 #include "../OrthancException.h"
+#include "../SerializationToolbox.h"
 #include "../Toolbox.h"
 
 #if ORTHANC_SANDBOXED == 0
@@ -1777,14 +1778,116 @@
   }
 
 
-  bool ParsedDicomFile::LookupSubSequence(DicomMap& target,
-                                          const DicomPath& path,
-                                          size_t sequenceIndex) const
+  bool ParsedDicomFile::LookupSequenceItem(DicomMap& target,
+                                           const DicomPath& path,
+                                           size_t sequenceIndex) const
+  {
+    DcmDataset& dataset = *const_cast<ParsedDicomFile&>(*this).GetDcmtkObject().getDataset();
+    return FromDcmtkBridge::LookupSequenceItem(target, dataset, path, sequenceIndex);
+  }
+  
+
+  void ParsedDicomFile::GetDefaultWindowing(double& windowCenter,
+                                            double& windowWidth,
+                                            unsigned int frame) const
   {
     DcmDataset& dataset = *const_cast<ParsedDicomFile&>(*this).GetDcmtkObject().getDataset();
-    return FromDcmtkBridge::LookupSubSequence(target, dataset, path, sequenceIndex);
+
+    const char* wc = NULL;
+    const char* ww = NULL;
+    DcmItem *item1 = NULL;
+    DcmItem *item2 = NULL;
+
+    if (dataset.findAndGetString(DCM_WindowCenter, wc).good() &&
+        dataset.findAndGetString(DCM_WindowWidth, ww).good() &&
+        wc != NULL &&
+        ww != NULL &&
+        SerializationToolbox::ParseFirstDouble(windowCenter, wc) &&
+        SerializationToolbox::ParseFirstDouble(windowWidth, ww))
+    {
+      return;  // OK
+    }
+    else if (dataset.findAndGetSequenceItem(DCM_PerFrameFunctionalGroupsSequence, item1, frame).good() &&
+             item1 != NULL &&
+             item1->findAndGetSequenceItem(DCM_FrameVOILUTSequence, item2, 0).good() &&
+             item2 != NULL &&
+             item2->findAndGetString(DCM_WindowCenter, wc).good() &&
+             item2->findAndGetString(DCM_WindowWidth, ww).good() &&
+             wc != NULL &&
+             ww != NULL &&
+             SerializationToolbox::ParseFirstDouble(windowCenter, wc) &&
+             SerializationToolbox::ParseFirstDouble(windowWidth, ww))
+    {
+      // New in Orthanc 1.9.7, to deal with Philips multiframe images
+      // (cf. private mail from Tomas Kenda on 2021-08-17)
+      return;  // OK
+    }
+    else
+    {
+      Uint16 bitsStored = 0;
+      if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good() ||
+          bitsStored == 0)
+      {
+        bitsStored = 8;  // Rough assumption
+      }
+
+      windowWidth = static_cast<double>(1 << bitsStored);
+      windowCenter = windowWidth / 2.0f;
+    }
   }
+
   
+  void ParsedDicomFile::GetRescale(double& rescaleIntercept,
+                                   double& rescaleSlope,
+                                   unsigned int frame) const
+  {
+    DcmDataset& dataset = *const_cast<ParsedDicomFile&>(*this).GetDcmtkObject().getDataset();
+
+    const char* sopClassUid = NULL;
+    const char* intercept = NULL;
+    const char* slope = NULL;
+    DcmItem *item1 = NULL;
+    DcmItem *item2 = NULL;
+
+    if (dataset.findAndGetString(DCM_SOPClassUID, sopClassUid).good() &&
+        sopClassUid != NULL &&
+        std::string(sopClassUid) == std::string(UID_RTDoseStorage))
+    {
+      // We must not take the rescale value into account in the case of doses
+      rescaleIntercept = 0;
+      rescaleSlope = 1;
+    }
+    else if (dataset.findAndGetString(DCM_RescaleIntercept, intercept).good() &&
+             dataset.findAndGetString(DCM_RescaleSlope, slope).good() &&
+             intercept != NULL &&
+             slope != NULL &&
+             SerializationToolbox::ParseFirstDouble(rescaleIntercept, intercept) &&
+             SerializationToolbox::ParseFirstDouble(rescaleSlope, slope))
+    {
+      return;  // OK
+    }
+    else if (dataset.findAndGetSequenceItem(DCM_PerFrameFunctionalGroupsSequence, item1, frame).good() &&
+             item1 != NULL &&
+             item1->findAndGetSequenceItem(DCM_PixelValueTransformationSequence, item2, 0).good() &&
+             item2 != NULL &&
+             item2->findAndGetString(DCM_RescaleIntercept, intercept).good() &&
+             item2->findAndGetString(DCM_RescaleSlope, slope).good() &&
+             intercept != NULL &&
+             slope != NULL &&
+             SerializationToolbox::ParseFirstDouble(rescaleIntercept, intercept) &&
+             SerializationToolbox::ParseFirstDouble(rescaleSlope, slope))
+    {
+      // New in Orthanc 1.9.7, to deal with Philips multiframe images
+      // (cf. private mail from Tomas Kenda on 2021-08-17)
+      return;  // OK
+    }
+    else
+    {
+      rescaleIntercept = 0;
+      rescaleSlope = 1;
+    }
+  }
+
 
 #if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1
   // Alias for binary compatibility with Orthanc Framework 1.7.2 => don't use it anymore
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Mon Aug 30 10:25:50 2021 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Mon Aug 30 11:41:05 2021 +0200
@@ -286,8 +286,16 @@
     void ClearPath(const DicomPath& path,
                    bool onlyIfExists);
 
-    bool LookupSubSequence(DicomMap& target,
-                           const DicomPath& path,
-                           size_t sequenceIndex) const;
+    bool LookupSequenceItem(DicomMap& target,
+                            const DicomPath& path,
+                            size_t sequenceIndex) const;
+
+    void GetDefaultWindowing(double& windowCenter,
+                             double& windowWidth,
+                             unsigned int frame) const;
+
+    void GetRescale(double& rescaleIntercept,
+                    double& rescaleSlope,
+                    unsigned int frame) const;
   };
 }
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Mon Aug 30 10:25:50 2021 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Mon Aug 30 11:41:05 2021 +0200
@@ -2744,24 +2744,24 @@
     std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
 
     DicomMap m;
-    ASSERT_TRUE(dicom->LookupSubSequence(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 0));
+    ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 0));
     ASSERT_EQ(2u, m.GetSize());
     ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.719",
               m.GetStringValue(DICOM_TAG_REFERENCED_SOP_INSTANCE_UID, "", false));
     
-    ASSERT_TRUE(dicom->LookupSubSequence(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 1));
+    ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 1));
     ASSERT_EQ(2u, m.GetSize());
     ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.726",
               m.GetStringValue(DICOM_TAG_REFERENCED_SOP_INSTANCE_UID, "", false));
     
-    ASSERT_FALSE(dicom->LookupSubSequence(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 2));
+    ASSERT_FALSE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 2));
     
-    ASSERT_TRUE(dicom->LookupSubSequence(m, DicomPath(DicomTag(0x0008, 0x1250), 0, DicomTag(0x0040, 0xa170)), 0));
+    ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x0008, 0x1250), 0, DicomTag(0x0040, 0xa170)), 0));
     ASSERT_EQ(2u, m.GetSize());
     ASSERT_EQ("122403", m.GetStringValue(DicomTag(0x0008, 0x0100), "", false));
     ASSERT_EQ("WORLD", m.GetStringValue(DICOM_TAG_SERIES_DESCRIPTION, "", false));
 
-    ASSERT_FALSE(dicom->LookupSubSequence(m, DicomPath(DicomTag(0x0008, 0x1250), 0, DicomTag(0x0040, 0xa170)), 1));
+    ASSERT_FALSE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x0008, 0x1250), 0, DicomTag(0x0040, 0xa170)), 1));
   }
 }
 
@@ -3019,6 +3019,121 @@
 
 
 
+TEST(ParsedDicomFile, ImageInformation)
+{
+  double wc, ww;
+  double ri, rs;
+  PhotometricInterpretation p;
+
+  {
+    ParsedDicomFile dicom(false);
+    dicom.GetDefaultWindowing(wc, ww, 5);
+    dicom.GetRescale(ri, rs, 5);
+    ASSERT_DOUBLE_EQ(128.0, wc);
+    ASSERT_DOUBLE_EQ(256.0, ww);
+    ASSERT_FALSE(dicom.LookupPhotometricInterpretation(p));
+    ASSERT_DOUBLE_EQ(0.0, ri);
+    ASSERT_DOUBLE_EQ(1.0, rs);
+  }
+
+  {
+    ParsedDicomFile dicom(false);
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_BitsStored, "4").good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_PhotometricInterpretation, "RGB").good());
+    dicom.GetDefaultWindowing(wc, ww, 5);
+    ASSERT_DOUBLE_EQ(8.0, wc);
+    ASSERT_DOUBLE_EQ(16.0, ww);
+    ASSERT_TRUE(dicom.LookupPhotometricInterpretation(p));
+    ASSERT_EQ(PhotometricInterpretation_RGB, p);
+  }
+
+  {
+    ParsedDicomFile dicom(false);
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowCenter, "12").good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowWidth, "-22").good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleIntercept, "-22").good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleSlope, "-23").good());
+    dicom.GetDefaultWindowing(wc, ww, 5);
+    dicom.GetRescale(ri, rs, 5);
+    ASSERT_DOUBLE_EQ(12.0, wc);
+    ASSERT_DOUBLE_EQ(-22.0, ww);
+    ASSERT_DOUBLE_EQ(-22.0, ri);
+    ASSERT_DOUBLE_EQ(-23.0, rs);
+  }
+
+  {
+    ParsedDicomFile dicom(false);
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowCenter, "12\\13\\14").good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowWidth, "-22\\-23\\-24").good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleIntercept, "32\\33\\34").good());
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleSlope, "-42\\-43\\-44").good());
+    dicom.GetDefaultWindowing(wc, ww, 5);
+    dicom.GetRescale(ri, rs, 5);
+    ASSERT_DOUBLE_EQ(12.0, wc);
+    ASSERT_DOUBLE_EQ(-22.0, ww);
+    ASSERT_DOUBLE_EQ(32.0, ri);
+    ASSERT_DOUBLE_EQ(-42.0, rs);
+  }
+
+  {
+    // Philips multiframe
+    Json::Value v = Json::objectValue;
+    v["PerFrameFunctionalGroupsSequence"][0]["FrameVOILUTSequence"][0]["WindowCenter"] = "614";
+    v["PerFrameFunctionalGroupsSequence"][0]["FrameVOILUTSequence"][0]["WindowWidth"] = "1067";
+    v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "12";
+    v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "2.551648";
+    v["PerFrameFunctionalGroupsSequence"][1]["FrameVOILUTSequence"][0]["WindowCenter"] = "-61";
+    v["PerFrameFunctionalGroupsSequence"][1]["FrameVOILUTSequence"][0]["WindowWidth"] = "-63";
+    v["PerFrameFunctionalGroupsSequence"][1]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "13";
+    v["PerFrameFunctionalGroupsSequence"][1]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "-14";
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+    
+    dicom->GetDefaultWindowing(wc, ww, 0);
+    dicom->GetRescale(ri, rs, 0);
+    ASSERT_DOUBLE_EQ(614.0, wc);
+    ASSERT_DOUBLE_EQ(1067.0, ww);
+    ASSERT_DOUBLE_EQ(12.0, ri);
+    ASSERT_DOUBLE_EQ(2.551648, rs);
+    
+    dicom->GetDefaultWindowing(wc, ww, 1);
+    dicom->GetRescale(ri, rs, 1);
+    ASSERT_DOUBLE_EQ(-61.0, wc);
+    ASSERT_DOUBLE_EQ(-63.0, ww);
+    ASSERT_DOUBLE_EQ(13.0, ri);
+    ASSERT_DOUBLE_EQ(-14.0, rs);
+    
+    dicom->GetDefaultWindowing(wc, ww, 2);
+    dicom->GetRescale(ri, rs, 2);
+    ASSERT_DOUBLE_EQ(128.0, wc);
+    ASSERT_DOUBLE_EQ(256.0, ww);
+    ASSERT_DOUBLE_EQ(0.0, ri);
+    ASSERT_DOUBLE_EQ(1.0, rs);
+  }
+
+  {
+    // RT-DOSE
+    Json::Value v = Json::objectValue;
+    v["RescaleIntercept"] = "10";
+    v["RescaleSlope"] = "20";
+    v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "30";
+    v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "40";
+    std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+    
+    dicom->GetRescale(ri, rs, 0);
+    ASSERT_DOUBLE_EQ(10.0, ri);
+    ASSERT_DOUBLE_EQ(20.0, rs);
+
+    v["SOPClassUID"] = "1.2.840.10008.5.1.4.1.1.481.2";
+    dicom.reset(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
+    dicom->GetRescale(ri, rs, 0);
+    ASSERT_DOUBLE_EQ(0.0, ri);
+    ASSERT_DOUBLE_EQ(1.0, rs);
+  }
+}
+
+
+
+
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
 
 #include "../Sources/DicomNetworking/DicomStoreUserConnection.h"
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon Aug 30 10:25:50 2021 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon Aug 30 11:41:05 2021 +0200
@@ -666,7 +666,8 @@
       // "dicom" is non-NULL iff. "RequiresDicomTags() == true"
       virtual void Handle(RestApiGetCall& call,
                           std::unique_ptr<ImageAccessor>& decoded,
-                          const ParsedDicomFile* dicom) = 0;
+                          const ParsedDicomFile* dicom,
+                          unsigned int frame) = 0;
 
       virtual bool RequiresDicomTags() const = 0;
 
@@ -799,11 +800,11 @@
              * interpretation, and with windowing parameters.
              **/ 
             ServerContext::DicomCacheLocker locker(context, publicId);
-            handler.Handle(call, decoded, &locker.GetDicom());
+            handler.Handle(call, decoded, &locker.GetDicom(), frame);
           }
           else
           {
-            handler.Handle(call, decoded, NULL);
+            handler.Handle(call, decoded, NULL, frame);
           }
         }
         catch (OrthancException& e)
@@ -868,7 +869,8 @@
 
       virtual void Handle(RestApiGetCall& call,
                           std::unique_ptr<ImageAccessor>& decoded,
-                          const ParsedDicomFile* dicom) ORTHANC_OVERRIDE
+                          const ParsedDicomFile* dicom,
+                          unsigned int frame) ORTHANC_OVERRIDE
       {
         bool invert = false;
 
@@ -899,43 +901,8 @@
     class RenderedFrameHandler : public IDecodedFrameHandler
     {
     private:
-      static void GetDicomParameters(bool& invert,
-                                     float& rescaleSlope,
-                                     float& rescaleIntercept,
-                                     float& windowWidth,
-                                     float& windowCenter,
-                                     const ParsedDicomFile& dicom)
-      {
-        DicomMap tags;
-        OrthancConfiguration::DefaultExtractDicomSummary(tags, dicom);
-        
-        DicomImageInformation info(tags);
-
-        invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1);
-
-        rescaleSlope = 1.0f;
-        rescaleIntercept = 0.0f;
-
-        if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) &&
-            dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT))
-        {
-          tags.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE);
-          tags.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
-        }
-
-        windowWidth = static_cast<float>(1 << info.GetBitsStored()) * rescaleSlope;
-        windowCenter = windowWidth / 2.0f + rescaleIntercept;
-
-        if (tags.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) &&
-            tags.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH))
-        {
-          tags.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER);
-          tags.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH);
-        }
-      }
-
-      static void GetUserArguments(float& windowWidth /* inout */,
-                                   float& windowCenter /* inout */,
+      static void GetUserArguments(double& windowWidth /* inout */,
+                                   double& windowCenter /* inout */,
                                    unsigned int& argWidth,
                                    unsigned int& argHeight,
                                    bool& smooth,
@@ -947,30 +914,18 @@
         static const char* ARG_HEIGHT = "height";
         static const char* ARG_SMOOTH = "smooth";
 
-        if (call.HasArgument(ARG_WINDOW_WIDTH))
+        if (call.HasArgument(ARG_WINDOW_WIDTH) &&
+            !SerializationToolbox::ParseDouble(windowWidth, call.GetArgument(ARG_WINDOW_WIDTH, "")))
         {
-          try
-          {
-            windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, ""));
-          }
-          catch (boost::bad_lexical_cast&)
-          {
-            throw OrthancException(ErrorCode_ParameterOutOfRange,
-                                   "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH));
-          }
+          throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                 "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH));
         }
 
-        if (call.HasArgument(ARG_WINDOW_CENTER))
+        if (call.HasArgument(ARG_WINDOW_CENTER) &&
+            !SerializationToolbox::ParseDouble(windowCenter, call.GetArgument(ARG_WINDOW_CENTER, "")))
         {
-          try
-          {
-            windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, ""));
-          }
-          catch (boost::bad_lexical_cast&)
-          {
-            throw OrthancException(ErrorCode_ParameterOutOfRange,
-                                   "Bad value for argument: " + std::string(ARG_WINDOW_CENTER));
-          }
+          throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                 "Bad value for argument: " + std::string(ARG_WINDOW_CENTER));
         }
 
         argWidth = 0;
@@ -1032,17 +987,22 @@
     public:
       virtual void Handle(RestApiGetCall& call,
                           std::unique_ptr<ImageAccessor>& decoded,
-                          const ParsedDicomFile* dicom) ORTHANC_OVERRIDE
+                          const ParsedDicomFile* dicom,
+                          unsigned int frame) ORTHANC_OVERRIDE
       {
         if (dicom == NULL)
         {
           throw OrthancException(ErrorCode_InternalError);
         }
         
-        bool invert;
-        float rescaleSlope, rescaleIntercept, windowWidth, windowCenter;
-        GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, *dicom);
-
+        PhotometricInterpretation photometric;
+        const bool invert = (dicom->LookupPhotometricInterpretation(photometric) &&
+                             photometric == PhotometricInterpretation_Monochrome1);
+          
+        double rescaleIntercept, rescaleSlope, windowCenter, windowWidth;
+        dicom->GetRescale(rescaleIntercept, rescaleSlope, frame);
+        dicom->GetDefaultWindowing(windowCenter, windowWidth, frame);
+        
         unsigned int argWidth, argHeight;
         bool smooth;
         GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call);
@@ -1112,16 +1072,16 @@
             windowWidth = 1;
           }
 
-          if (std::abs(rescaleSlope) <= 0.1f)
+          if (std::abs(rescaleSlope) <= 0.1)
           {
-            rescaleSlope = 0.1f;
+            rescaleSlope = 0.1;
           }
 
-          const float scaling = 255.0f * rescaleSlope / windowWidth;
-          const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope;
+          const double scaling = 255.0 * rescaleSlope / windowWidth;
+          const double offset = (rescaleIntercept - windowCenter + windowWidth / 2.0) / rescaleSlope;
 
           std::unique_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false));
-          ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false);
+          ImageProcessing::ShiftScale(*rescaled, converted, static_cast<float>(offset), static_cast<float>(scaling), false);
 
           if (targetWidth == decoded->GetWidth() &&
               targetHeight == decoded->GetHeight())