changeset 274:2805246064aa iiif

added SetIIIFForcePowersOfTwoScaleFactors()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 12 Jul 2023 11:24:56 +0200
parents 1c95010d9d2e
children 801790c81f20
files Resources/TestIIIFTiles.py ViewerPlugin/IIIF.cpp ViewerPlugin/IIIF.h ViewerPlugin/Plugin.cpp
diffstat 4 files changed, 117 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/TestIIIFTiles.py	Wed Jul 12 11:24:56 2023 +0200
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+
+import requests
+import sys
+
+if len(sys.argv) != 2:
+    print('Usage: %s <URL to info.json>' % sys.argv[0])
+    exit(-1)
+
+r = requests.get(sys.argv[1])
+r.raise_for_status()
+
+info = r.json()
+
+assert(len(info['tiles']) == 1)
+assert(len(info['tiles'][0]['scaleFactors']) == len(info['sizes']))
+
+width = None
+height = None
+for size in info['sizes']:
+    if (width == None or
+        size['width'] > width):
+        width = size['width']
+        height = size['height']
+
+tw = info['tiles'][0]['width']
+th = info['tiles'][0]['height']
+
+assert(isinstance(width, int))
+assert(isinstance(height, int))
+assert(isinstance(tw, int))
+assert(isinstance(th, int))
+
+def CeilingDivision(a, b):
+    if a % b == 0:
+        return a // b
+    else:
+        return a // b + 1
+
+for s in info['tiles'][0]['scaleFactors']:
+    assert(isinstance(s, int))
+
+    countTilesX = CeilingDivision(width, tw * s)
+    countTilesY = CeilingDivision(height, th * s)
+    print(tw * s, th * s, countTilesX, countTilesY)
+
+    for m in range(countTilesY):
+        for n in range(countTilesX):
+
+            # Reference:
+            # https://iiif.io/api/image/3.0/implementation/#3-tile-region-parameter-calculation
+
+            # Calculate region parameters /xr,yr,wr,hr/
+            xr = n * tw * s
+            yr = m * th * s
+            wr = tw * s
+            if (xr + wr > width):
+                wr = width - xr
+            hr = th * s
+            if (yr + hr > height):
+                hr = height - yr
+
+            # Calculate size parameters /ws,hs/
+            ws = tw
+            if (xr + tw*s > width):
+                ws = (width - xr + s - 1) / s  # +s-1 in numerator to round up
+            hs = th
+            if (yr + th*s > height):
+                hs = (height - yr + s - 1) / s
+
+            url = '%s/%d,%d,%d,%d/%d,%d/0/default.jpg' % (info['id'], xr, yr, wr, hr, ws, hs)
+            r = requests.get(url)
+
+            if r.status_code == 200:
+                print('SUCCESS: %s' % url)
+            else:
+                print('ERROR:   %s' % url)
--- a/ViewerPlugin/IIIF.cpp	Wed Jul 12 08:48:16 2023 +0200
+++ b/ViewerPlugin/IIIF.cpp	Wed Jul 12 11:24:56 2023 +0200
@@ -40,6 +40,7 @@
 
 
 static std::string  iiifPublicUrl_;
+static bool         iiifForcePowersOfTwoScaleFactors_ = false;
 
 
 static void ServeIIIFTiledImageInfo(OrthancPluginRestOutput* output,
@@ -77,7 +78,9 @@
   Json::Value sizes = Json::arrayValue;
   Json::Value scaleFactors = Json::arrayValue;
 
-  for (unsigned int i = pyramid.GetLevelCount(); i > 0; i--)
+  unsigned int power = 1;
+
+  for (unsigned int i = 0; i < pyramid.GetLevelCount(); i++)
   {
     /**
      * According to the IIIF Image API 3.0 specification,
@@ -90,16 +93,28 @@
      * width/height of the image is divisible by the width/height of
      * the level.
      **/
-    if (pyramid.GetLevelWidth(0) % pyramid.GetLevelWidth(i - 1) == 0 &&
-        pyramid.GetLevelHeight(0) % pyramid.GetLevelHeight(i - 1) == 0)
+    if (pyramid.GetLevelWidth(0) % pyramid.GetLevelWidth(i) == 0 &&
+        pyramid.GetLevelHeight(0) % pyramid.GetLevelHeight(i) == 0)
     {
-      Json::Value level;
-      level["width"] = pyramid.GetLevelWidth(i - 1);
-      level["height"] = pyramid.GetLevelHeight(i - 1);
-      sizes.append(level);
+      unsigned int scaleFactor = pyramid.GetLevelWidth(0) / pyramid.GetLevelWidth(i);
+
+      if (!iiifForcePowersOfTwoScaleFactors_ ||
+          scaleFactor == power)
+      {
+        Json::Value level;
+        level["width"] = pyramid.GetLevelWidth(i);
+        level["height"] = pyramid.GetLevelHeight(i);
+        sizes.append(level);
 
-      scaleFactors.append(pyramid.GetLevelWidth(0) /
-                          pyramid.GetLevelWidth(i - 1));
+        scaleFactors.append(scaleFactor);
+
+        power *= 2;
+      }
+      else
+      {
+        LOG(WARNING) << "IIIF - Dropping level " << i << " of series " << seriesId
+                     << ", as it doesn't follow the powers-of-two pattern";
+      }
     }
     else
     {
@@ -107,7 +122,7 @@
                    << ", as the full width/height ("
                    << pyramid.GetLevelWidth(0) << "x" << pyramid.GetLevelHeight(0)
                    << ") of the image is not an integer multiple of the level width/height ("
-                   << pyramid.GetLevelWidth(i - 1) << "x" << pyramid.GetLevelHeight(i - 1) << ")";
+                   << pyramid.GetLevelWidth(i) << "x" << pyramid.GetLevelHeight(i) << ")";
     }
   }
 
@@ -234,7 +249,7 @@
         !Orthanc::SerializationToolbox::ParseUnsignedInteger32(regionWidth, tokens[2]) ||
         !Orthanc::SerializationToolbox::ParseUnsignedInteger32(regionHeight, tokens[3]))
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (x,y,width,height) region, found: " + region);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Invalid (x,y,width,height) region, found: " + region);
     }
 
     uint32_t cropWidth, cropHeight;
@@ -258,7 +273,7 @@
 
     if (!ok)
     {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (width,height) crop, found: " + size);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Invalid (width,height) crop, found: " + size);
     }
 
     std::unique_ptr<OrthancWSI::RawTile> rawTile;
@@ -602,3 +617,8 @@
   OrthancPlugins::RegisterRestCallback<ServeIIIFFrameInfo>("/wsi/iiif/frames/([0-9a-f-]+)/([0-9]+)/info.json", true);
   OrthancPlugins::RegisterRestCallback<ServeIIIFFrameImage>("/wsi/iiif/frames/([0-9a-f-]+)/([0-9]+)/full/max/0/default.jpg", true);
 }
+
+void SetIIIFForcePowersOfTwoScaleFactors(bool force)
+{
+  iiifForcePowersOfTwoScaleFactors_ = force;
+}
--- a/ViewerPlugin/IIIF.h	Wed Jul 12 08:48:16 2023 +0200
+++ b/ViewerPlugin/IIIF.h	Wed Jul 12 11:24:56 2023 +0200
@@ -25,3 +25,10 @@
 #include <string>
 
 void InitializeIIIF(const std::string& iiifPublicUrl);
+
+/**
+ * Filter pyramids whose level sizes don't follow a powers-of-two
+ * pattern. This can be used to bypass issue 2379 on OpenSeadragon <=
+ * 4.1: https://github.com/openseadragon/openseadragon/issues/2379
+ **/
+void SetIIIFForcePowersOfTwoScaleFactors(bool force);
--- a/ViewerPlugin/Plugin.cpp	Wed Jul 12 08:48:16 2023 +0200
+++ b/ViewerPlugin/Plugin.cpp	Wed Jul 12 11:24:56 2023 +0200
@@ -310,6 +310,7 @@
       }
 
       InitializeIIIF(iiifPublicUrl);
+      SetIIIFForcePowersOfTwoScaleFactors(true);  // TODO => CONFIG
 
       serveMirador = true;  // TODO => CONFIG
       serveOpenSeadragon = true;  // TODO => CONFIG