changeset 372:33dcd7f68df9

OrthancWSIDicomizer detects imaged volume size for Aperio files without OpenSlide
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 17 Mar 2025 15:19:50 +0100 (2 months ago)
parents 9ff5782c6a8b
children a818de088503
files Applications/Dicomizer.cpp Framework/ImageToolbox.cpp Framework/ImageToolbox.h Framework/Inputs/DicomPyramid.cpp Framework/Inputs/HierarchicalTiff.cpp Framework/Inputs/HierarchicalTiff.h NEWS ViewerPlugin/Plugin.cpp
diffstat 8 files changed, 115 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Dicomizer.cpp	Mon Mar 17 14:16:33 2025 +0100
+++ b/Applications/Dicomizer.cpp	Mon Mar 17 15:19:50 2025 +0100
@@ -1130,6 +1130,24 @@
       {
         std::unique_ptr<OrthancWSI::HierarchicalTiff> tiff(new OrthancWSI::HierarchicalTiff(path));
         sourceCompression = tiff->GetImageCompression();
+
+        // New in WSI 3.1
+        double width, height;
+        if (tiff->LookupImagedVolumeSize(width, height))
+        {
+          if (!volume.HasWidth())
+          {
+            volume.SetWidth(width);
+            LOG(WARNING) << "Width of the imaged volume according to TIFF metadata: " << width << "mm";
+          }
+
+          if (!volume.HasHeight())
+          {
+            volume.SetHeight(height);
+            LOG(WARNING) << "Height of the imaged volume according to TIFF metadata: " << height << "mm";
+          }
+        }
+
         return tiff.release();
       }
       catch (Orthanc::OrthancException&)
@@ -1237,7 +1255,7 @@
       {
         float pixelSpacingX = volume.GetWidth() / static_cast<float>(source->GetLevelHeight(0));
         float pixelSpacingY = volume.GetHeight() / static_cast<float>(source->GetLevelWidth(0));
-        if (std::abs(pixelSpacingX - pixelSpacingY) >= 100.0f * std::numeric_limits<float>::epsilon())
+        if (!OrthancWSI::ImageToolbox::IsNear(pixelSpacingX, pixelSpacingY))
         {
           LOG(WARNING) << "Your pixel spacing is different along the X and Y axes, make sure that "
                        << "you have not inversed the --" << OPTION_IMAGED_WIDTH << " and the --"
--- a/Framework/ImageToolbox.cpp	Mon Mar 17 14:16:33 2025 +0100
+++ b/Framework/ImageToolbox.cpp	Mon Mar 17 15:19:50 2025 +0100
@@ -36,6 +36,7 @@
 #include <Images/JpegWriter.h>
 #include <Logging.h>
 
+#include <limits>
 #include <string.h>
 #include <memory>
 
@@ -44,6 +45,13 @@
 {
   namespace ImageToolbox
   {
+    bool IsNear(double a,
+                double b)
+    {
+      return std::abs(a - b) < 10.0 * std::numeric_limits<float>::epsilon();
+    }
+
+
     Orthanc::ImageAccessor* Allocate(Orthanc::PixelFormat format,
                                      unsigned int width,
                                      unsigned int height)
--- a/Framework/ImageToolbox.h	Mon Mar 17 14:16:33 2025 +0100
+++ b/Framework/ImageToolbox.h	Mon Mar 17 15:19:50 2025 +0100
@@ -33,6 +33,9 @@
 {
   namespace ImageToolbox
   {
+    bool IsNear(double a,
+                double b);
+
     Orthanc::ImageAccessor* Allocate(Orthanc::PixelFormat format,
                                      unsigned int width,
                                      unsigned int height);
--- a/Framework/Inputs/DicomPyramid.cpp	Mon Mar 17 14:16:33 2025 +0100
+++ b/Framework/Inputs/DicomPyramid.cpp	Mon Mar 17 15:19:50 2025 +0100
@@ -24,6 +24,7 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "DicomPyramid.h"
 
+#include "../ImageToolbox.h"
 #include "../DicomToolbox.h"
 
 #include <Compatibility.h>
@@ -290,8 +291,8 @@
           width = instances_[i]->GetImagedVolumeWidth();
           height = instances_[i]->GetImagedVolumeHeight();
         }
-        else if (std::abs(width - instances_[i]->GetImagedVolumeWidth()) > 100.0 * std::numeric_limits<double>::epsilon() ||
-                 std::abs(height - instances_[i]->GetImagedVolumeHeight()) > 100.0 * std::numeric_limits<double>::epsilon())
+        else if (!ImageToolbox::IsNear(width, instances_[i]->GetImagedVolumeWidth()) ||
+                 !ImageToolbox::IsNear(height, instances_[i]->GetImagedVolumeHeight()))
         {
           LOG(WARNING) << "Inconsistency of imaged volume width/height in series: " << seriesId_;
           return false;
--- a/Framework/Inputs/HierarchicalTiff.cpp	Mon Mar 17 14:16:33 2025 +0100
+++ b/Framework/Inputs/HierarchicalTiff.cpp	Mon Mar 17 15:19:50 2025 +0100
@@ -24,8 +24,12 @@
 #include "../PrecompiledHeadersWSI.h"
 #include "HierarchicalTiff.h"
 
+#include "../ImageToolbox.h"
+
 #include <Logging.h>
 #include <OrthancException.h>
+#include <SerializationToolbox.h>
+#include <Toolbox.h>
 
 #include <iostream>
 #include <algorithm>
@@ -69,6 +73,17 @@
         headers_.assign(reinterpret_cast<const char*>(tables), size);
       }
     }
+
+    // Read the image description, if any
+    const char* description = NULL;
+    if (TIFFGetField(tiff, TIFFTAG_IMAGEDESCRIPTION, &description))
+    {
+      description_.assign(description);
+    }
+    else
+    {
+      description_.clear();
+    }
   }
 
   struct HierarchicalTiff::Comparator
@@ -261,4 +276,58 @@
     
     return true;
   }
+
+
+  bool HierarchicalTiff::LookupImagedVolumeSize(double& width,
+                                                double& height) const
+  {
+    static const char* const APERIO_DESCRIPTION = "Aperio ";
+    static const char* const MPP = "MPP";
+
+    bool found = false;
+
+    for (size_t i = 0; i < levels_.size(); i++)
+    {
+      if (Orthanc::Toolbox::StartsWith(levels_[i].description_, APERIO_DESCRIPTION))
+      {
+        std::vector<std::string> tokens;
+        Orthanc::Toolbox::TokenizeString(tokens, levels_[i].description_, '|');
+
+        for (size_t j = 0; j < tokens.size(); j++)
+        {
+          std::vector<std::string> assignment;
+          Orthanc::Toolbox::TokenizeString(assignment, tokens[j], '=');
+          if (assignment.size() == 2)
+          {
+            const std::string key = Orthanc::Toolbox::StripSpaces(assignment[0]);
+            const std::string value = Orthanc::Toolbox::StripSpaces(assignment[1]);
+
+            double mpp;
+            if (key == MPP &&
+                Orthanc::SerializationToolbox::ParseDouble(mpp, value))
+            {
+              // In the lines below, remember to switch X/Y when going from physical to pixel coordinates!
+              double thisHeight = static_cast<double>(levels_[i].width_) * mpp / 1000.0;
+              double thisWidth = static_cast<double>(levels_[i].height_) * mpp / 1000.0;
+
+              if (!found)
+              {
+                found = true;
+                width = thisWidth;
+                height = thisHeight;
+              }
+              else if (!ImageToolbox::IsNear(thisWidth, width) ||
+                       !ImageToolbox::IsNear(thisHeight, height))
+              {
+                LOG(WARNING) << "Inconsistency in the Aperio metadata regarding the size of the imaged volume";
+                return false;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return found;
+  }
 }
--- a/Framework/Inputs/HierarchicalTiff.h	Mon Mar 17 14:16:33 2025 +0100
+++ b/Framework/Inputs/HierarchicalTiff.h	Mon Mar 17 15:19:50 2025 +0100
@@ -40,6 +40,7 @@
       unsigned int  width_;
       unsigned int  height_;
       std::string  headers_;
+      std::string  description_;
 
       Level(TIFF* tiff,
             tdir_t    directory,
@@ -102,5 +103,8 @@
     {
       return compression_;
     }
+
+    bool LookupImagedVolumeSize(double& width,
+                                double& height) const;
   };
 }
--- a/NEWS	Mon Mar 17 14:16:33 2025 +0100
+++ b/NEWS	Mon Mar 17 15:19:50 2025 +0100
@@ -2,10 +2,11 @@
 ===============================
 
 * Upgraded to OpenLayers 10.4.0 (was previously 3.19.0)
-* The viewer now displays the scale if the imaged volume size is available
+* The viewer now displays the scale if imaged volume size is available in DICOM
+* Fix handling of "Image Type" in the viewer for compatibility with other vendors
 * OrthancWSIDicomizer does not fill anymore the imaged volume width/height
   tags if no information is available
-* Fix handling of "Image Type" in the viewer for compatibility with other vendors
+* OrthancWSIDicomizer detects imaged volume size for Aperio files without OpenSlide
 
 Compatibility notes about the viewer
 ------------------------------------
--- a/ViewerPlugin/Plugin.cpp	Mon Mar 17 14:16:33 2025 +0100
+++ b/ViewerPlugin/Plugin.cpp	Mon Mar 17 15:19:50 2025 +0100
@@ -416,12 +416,6 @@
 }
 
 
-static bool IsNear(float a, float b)
-{
-  return std::abs(a - b) < 100.0f * std::numeric_limits<float>::epsilon();
-}
-
-
 extern "C"
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
@@ -478,17 +472,17 @@
 
       OrthancWSI::LABColor lab;
       if (!OrthancWSI::LABColor::DecodeDicomRecommendedAbsentPixelCIELab(lab, "65535\\0\\0") ||
-          !IsNear(lab.GetL(), 100.0f) ||
-          !IsNear(lab.GetA(), -128.0f) ||
-          !IsNear(lab.GetB(), -128.0f))
+          !OrthancWSI::ImageToolbox::IsNear(lab.GetL(), 100.0f) ||
+          !OrthancWSI::ImageToolbox::IsNear(lab.GetA(), -128.0f) ||
+          !OrthancWSI::ImageToolbox::IsNear(lab.GetB(), -128.0f))
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
 
       if (!OrthancWSI::LABColor::DecodeDicomRecommendedAbsentPixelCIELab(lab, "0\\32896\\65535") ||
-          !IsNear(lab.GetL(), 0.0f) ||
-          !IsNear(lab.GetA(), 0.0f) ||
-          !IsNear(lab.GetB(), 127.0f))
+          !OrthancWSI::ImageToolbox::IsNear(lab.GetL(), 0.0f) ||
+          !OrthancWSI::ImageToolbox::IsNear(lab.GetA(), 0.0f) ||
+          !OrthancWSI::ImageToolbox::IsNear(lab.GetB(), 127.0f))
       {
         throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }