changeset 298:fa734a851551

New option: "tiff-alignment" to control deep zoom of plain TIFF over IIIF
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 18 Jul 2023 08:19:16 +0200
parents c1687b8fc800
children 9cd5690e7684
files Applications/CMakeLists.txt Applications/Dicomizer.cpp Framework/DicomizerParameters.cpp Framework/DicomizerParameters.h Framework/Inputs/PlainTiff.cpp Framework/Inputs/PlainTiff.h NEWS
diffstat 7 files changed, 268 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/CMakeLists.txt	Tue Jul 18 07:13:36 2023 +0200
+++ b/Applications/CMakeLists.txt	Tue Jul 18 08:19:16 2023 +0200
@@ -119,6 +119,7 @@
   ${ORTHANC_WSI_DIR}/Framework/Inputs/HierarchicalTiff.cpp
   ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlideLibrary.cpp
   ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlidePyramid.cpp
+  ${ORTHANC_WSI_DIR}/Framework/Inputs/PlainTiff.cpp
   ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp
   ${ORTHANC_WSI_DIR}/Framework/Inputs/SingleLevelDecodedPyramid.cpp
   ${ORTHANC_WSI_DIR}/Framework/Inputs/TiledPyramidStatistics.cpp
--- a/Applications/Dicomizer.cpp	Tue Jul 18 07:13:36 2023 +0200
+++ b/Applications/Dicomizer.cpp	Tue Jul 18 08:19:16 2023 +0200
@@ -29,6 +29,7 @@
 #include "../Framework/Inputs/CytomineImage.h"
 #include "../Framework/Inputs/HierarchicalTiff.h"
 #include "../Framework/Inputs/OpenSlidePyramid.h"
+#include "../Framework/Inputs/PlainTiff.h"
 #include "../Framework/Inputs/TiledJpegImage.h"
 #include "../Framework/Inputs/TiledPngImage.h"
 #include "../Framework/Inputs/TiledPyramidStatistics.h"
@@ -91,6 +92,9 @@
 static const char* OPTION_CYTOMINE_PRIVATE_KEY = "cytomine-private-key";
 static const char* OPTION_CYTOMINE_COMPRESSION = "cytomine-compression";
 
+// New in release 2.1
+static const char* OPTION_TIFF_ALIGNMENT = "tiff-alignment";
+
 
 #if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 9, 0)
 
@@ -592,6 +596,9 @@
      "Whether to repaint the background of the image (Boolean)")
     (OPTION_COLOR, boost::program_options::value<std::string>(),
      "Color of the background (e.g. \"255,0,0\")")
+    (OPTION_TIFF_ALIGNMENT, boost::program_options::value<int>()->default_value(64),
+     "Add padding to plain TIFF images to align the width/height to multiples "
+     "of this value, very useful to enable deep zoom with IIIF (1 means no padding)")
     ;
 
   boost::program_options::options_description cytomine("Options if importing from Cytomine");
@@ -1004,6 +1011,20 @@
     parameters.SetIccProfilePath(options[OPTION_ICC_PROFILE].as<std::string>());
   }
 
+  if (options.count(OPTION_TIFF_ALIGNMENT))
+  {
+    int value = options[OPTION_TIFF_ALIGNMENT].as<int>();
+    if (value <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                      "TIFF alignment must be >= 1");
+    }
+    else
+    {
+      parameters.SetTiffAlignment(static_cast<unsigned int>(value));
+    }
+  }
+
   return true;
 }
 
@@ -1061,8 +1082,17 @@
       }
       catch (Orthanc::OrthancException&)
       {
-        LOG(WARNING) << "This is not a standard hierarchical TIFF file";
+        LOG(WARNING) << "This is not a standard hierarchical TIFF file, fallback to plain TIFF";
       }
+
+      sourceCompression = OrthancWSI::ImageCompression_Unknown;
+      return new OrthancWSI::PlainTiff(path,
+                                       parameters.GetTargetTileWidth(512),
+                                       parameters.GetTargetTileHeight(512),
+                                       parameters.GetTiffAlignment(),
+                                       parameters.GetBackgroundColorRed(),
+                                       parameters.GetBackgroundColorGreen(),
+                                       parameters.GetBackgroundColorBlue());
     }
 
     default:
--- a/Framework/DicomizerParameters.cpp	Tue Jul 18 07:13:36 2023 +0200
+++ b/Framework/DicomizerParameters.cpp	Tue Jul 18 08:19:16 2023 +0200
@@ -73,7 +73,8 @@
     opticalPath_(OpticalPath_Brightfield),
     isCytomineSource_(false),
     cytomineImageInstanceId_(-1),
-    cytomineCompression_(ImageCompression_Png)
+    cytomineCompression_(ImageCompression_Png),
+    tiffAlignment_(1)
   {
     backgroundColor_[0] = 255;
     backgroundColor_[1] = 255;
@@ -359,4 +360,17 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
   }
+
+
+  void DicomizerParameters::SetTiffAlignment(unsigned int alignment)
+  {
+    if (alignment <= 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      tiffAlignment_ = alignment;
+    }
+  }
 }
--- a/Framework/DicomizerParameters.h	Tue Jul 18 07:13:36 2023 +0200
+++ b/Framework/DicomizerParameters.h	Tue Jul 18 08:19:16 2023 +0200
@@ -67,6 +67,9 @@
     std::string                    cytominePrivateKey_;
     ImageCompression               cytomineCompression_;
 
+    // New in release 2.1
+    unsigned int  tiffAlignment_;
+
   public:
     DicomizerParameters();
 
@@ -278,5 +281,12 @@
     int GetCytomineImageInstanceId() const;
 
     ImageCompression GetCytomineCompression() const;
+
+    void SetTiffAlignment(unsigned int alignment);
+
+    unsigned int GetTiffAlignment() const
+    {
+      return tiffAlignment_;
+    }
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Inputs/PlainTiff.cpp	Tue Jul 18 08:19:16 2023 +0200
@@ -0,0 +1,166 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * 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-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PlainTiff.h"
+
+#include "../TiffReader.h"
+
+#include <Images/Image.h>
+#include <Images/ImageProcessing.h>
+#include <Logging.h>
+#include <OrthancException.h>
+
+#include <cassert>
+#include <string.h>
+
+
+namespace OrthancWSI
+{
+  PlainTiff::PlainTiff(const std::string& path,
+                       unsigned int tileWidth,
+                       unsigned int tileHeight,
+                       unsigned int paddingAlignement,
+                       uint8_t paddingRed,
+                       uint8_t paddingGreen,
+                       uint8_t paddingBlue) :
+    SingleLevelDecodedPyramid(tileWidth, tileHeight)
+  {
+    TiffReader reader(path);
+
+    // Look for the largest sub-image
+    bool first = true;
+    unsigned int width = 0;
+    unsigned int height = 0;
+    tdir_t largest = 0;
+
+    tdir_t pos = 0;
+
+    do
+    {
+      uint32_t w, h, tw, th;
+
+      if (TIFFSetDirectory(reader.GetTiff(), pos) &&
+          !TIFFGetField(reader.GetTiff(), TIFFTAG_TILEWIDTH, &tw) &&   // Must not be a tiled image
+          !TIFFGetField(reader.GetTiff(), TIFFTAG_TILELENGTH, &th) &&  // Must not be a tiled image
+          TIFFGetField(reader.GetTiff(), TIFFTAG_IMAGEWIDTH, &w) &&
+          TIFFGetField(reader.GetTiff(), TIFFTAG_IMAGELENGTH, &h) &&
+          w > 0 &&
+          h > 0)
+      {
+        if (first)
+        {
+          first = false;
+          width = w;
+          height = h;
+          largest = pos;
+        }
+        else if (w > width &&
+                 h > height)
+        {
+          width = w;
+          height = h;
+          largest = pos;
+        }
+      }
+
+      pos++;
+    }
+    while (TIFFReadDirectory(reader.GetTiff()));
+
+    if (first)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "This is an empty TIFF image");
+    }
+
+    // Back to the largest directory
+    if (!TIFFSetDirectory(reader.GetTiff(), largest))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_CorruptedFile);
+    }
+
+    ImageCompression compression;
+    Orthanc::PixelFormat pixelFormat;
+    Orthanc::PhotometricInterpretation photometric;
+
+    if (!reader.GetCurrentDirectoryInformation(compression, pixelFormat, photometric))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    if (pixelFormat != Orthanc::PixelFormat_RGB24)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+
+    LOG(WARNING) << "Size of the source TIFF image: " << width << "x" << height;
+
+    const unsigned int paddedWidth = paddingAlignement * CeilingDivision(width, paddingAlignement);
+    const unsigned int paddedHeight = paddingAlignement * CeilingDivision(height, paddingAlignement);
+    assert(paddedWidth >= width &&
+           paddedHeight >= height);
+
+    LOG(WARNING) << "Size of the padded TIFF image: " << paddedWidth << "x" << paddedHeight;
+
+    decoded_.reset(new Orthanc::Image(pixelFormat, paddedWidth, paddedHeight, false));
+    Orthanc::ImageProcessing::Set(*decoded_, paddingRed, paddingGreen, paddingBlue, 255);
+
+    std::string strip;
+    strip.resize(TIFFStripSize(reader.GetTiff()));
+
+    const size_t stripPitch = width * Orthanc::GetBytesPerPixel(pixelFormat);
+
+    if (strip.empty() ||
+        stripPitch == 0 ||
+        strip.size() < stripPitch ||
+        strip.size() % stripPitch != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    const size_t stripHeight = (strip.size() / stripPitch);
+    const size_t stripCount = CeilingDivision(height, stripHeight);
+
+    if (TIFFNumberOfStrips(reader.GetTiff()) != stripCount)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+
+    for (unsigned int i = 0; i < stripCount; i++)
+    {
+      TIFFReadEncodedStrip(reader.GetTiff(), i, &strip[0], static_cast<tsize_t>(-1));
+
+      const unsigned int y = i * stripHeight;
+
+      const uint8_t* p = reinterpret_cast<const uint8_t*>(&strip[0]);
+      uint8_t* q = reinterpret_cast<uint8_t*>(decoded_->GetRow(y));
+
+      for (unsigned j = 0; j < stripHeight && y + j < height; j++)
+      {
+        memcpy(q, p, stripPitch);
+        p += stripPitch;
+        q += decoded_->GetPitch();
+      }
+    }
+
+    SetImage(*decoded_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Inputs/PlainTiff.h	Tue Jul 18 08:19:16 2023 +0200
@@ -0,0 +1,44 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * 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-2023 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero 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
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "SingleLevelDecodedPyramid.h"
+
+
+namespace OrthancWSI
+{
+  class PlainTiff : public SingleLevelDecodedPyramid
+  {
+  private:
+    std::unique_ptr<Orthanc::ImageAccessor>  decoded_;
+
+  public:
+    PlainTiff(const std::string& path,
+              unsigned int tileWidth,
+              unsigned int tileHeight,
+              unsigned int paddingAlignement,
+              uint8_t paddingRed,
+              uint8_t paddingGreen,
+              uint8_t paddingBlue);
+  };
+}
--- a/NEWS	Tue Jul 18 07:13:36 2023 +0200
+++ b/NEWS	Tue Jul 18 08:19:16 2023 +0200
@@ -2,6 +2,7 @@
 ===============================
 
 * OrthancWSIDicomizer supports plain TIFF, besides hierarchical TIFF
+* New option: "tiff-alignment" to control deep zoom of plain TIFF over IIIF
 
 
 Version 2.0 (2023-10-07)