changeset 354:5d3672320879

IIIF support for on-the-fly pyramids
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 20 Dec 2024 12:40:54 +0100
parents c36a1fee7a7d
children a307ec25a008
files ViewerPlugin/IIIF.cpp ViewerPlugin/OrthancExplorer.js
diffstat 2 files changed, 109 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/ViewerPlugin/IIIF.cpp	Fri Dec 20 12:33:10 2024 +0100
+++ b/ViewerPlugin/IIIF.cpp	Fri Dec 20 12:40:54 2024 +0100
@@ -533,14 +533,14 @@
 
 
 static void AddCanvas(Json::Value& manifest,
-                      const std::string& seriesId,
+                      const std::string& resourceBase,
                       const std::string& imageService,
                       unsigned int page,
                       unsigned int width,
                       unsigned int height,
                       const std::string& description)
 {
-  const std::string base = iiifPublicUrl_ + seriesId;
+  const std::string base = iiifPublicUrl_ + resourceBase;
 
   Json::Value service;
   service["id"] = iiifPublicUrl_ + imageService;
@@ -781,6 +781,67 @@
 }
 
 
+void ServeIIIFFramePyramidManifest(OrthancPluginRestOutput* output,
+                                   const char* url,
+                                   const OrthancPluginHttpRequest* request)
+{
+  const std::string instanceId(request->groups[0]);
+
+  unsigned int frameNumber;
+  if (!Orthanc::SerializationToolbox::ParseUnsignedInteger32(frameNumber, request->groups[1]))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+  }
+
+  LOG(INFO) << "IIIF: Presentation API call to frame " << frameNumber << " of instance " << instanceId;
+
+  Json::Value instance, study, series;
+  if (!OrthancPlugins::RestApiGet(instance, "/instances/" + instanceId, false) ||
+      !OrthancPlugins::RestApiGet(series, "/instances/" + instanceId + "/series", false) ||
+      !OrthancPlugins::RestApiGet(study, "/instances/" + instanceId + "/study", false))
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+  }
+
+  if (instance.type() != Json::objectValue ||
+      series.type() != Json::objectValue ||
+      study.type() != Json::objectValue)
+  {
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+  }
+
+  const std::string resourceBase = "frames-pyramids/" + instanceId + "/" + boost::lexical_cast<std::string>(frameNumber);
+  const std::string base = iiifPublicUrl_ + resourceBase;
+
+  Json::Value manifest;
+  manifest["@context"] = "http://iiif.io/api/presentation/3/context.json";
+  manifest["id"] = base + "/manifest.json";
+  manifest["type"] = "Manifest";
+  manifest["label"]["en"].append(study["MainDicomTags"]["StudyDate"].asString() + " - " +
+                                 series["MainDicomTags"]["Modality"].asString() + " - " +
+                                 study["MainDicomTags"]["StudyDescription"].asString() + " - " +
+                                 series["MainDicomTags"]["SeriesDescription"].asString());
+
+  /**
+   * This is based on IIIF cookbook: "Support Deep Viewing with Basic
+   * Use of a IIIF Image Service."
+   * https://iiif.io/api/cookbook/recipe/0005-image-service/
+   **/
+  unsigned int width, height;
+
+  {
+    OrthancWSI::DecodedPyramidCache::Accessor accessor(OrthancWSI::DecodedPyramidCache::GetInstance(), instanceId, frameNumber);
+    width = accessor.GetPyramid().GetLevelWidth(0);
+    height = accessor.GetPyramid().GetLevelHeight(0);
+  }
+
+  AddCanvas(manifest, resourceBase, resourceBase, 1, width, height, "");
+
+  std::string s = manifest.toStyledString();
+  OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Json));
+}
+
+
 void ServeIIIFFramePyramidInfo(OrthancPluginRestOutput* output,
                                const char* url,
                                const OrthancPluginHttpRequest* request)
@@ -871,6 +932,7 @@
   OrthancPlugins::RegisterRestCallback<ServeIIIFFrameImage>("/wsi/iiif/frames/([0-9a-f-]+)/([0-9]+)/full/max/0/default.jpg", true);
 
   // New in WSI 3.0
+  OrthancPlugins::RegisterRestCallback<ServeIIIFFramePyramidManifest>("/wsi/iiif/frames-pyramids/([0-9a-f-]+)/([0-9]+)/manifest.json", true);
   OrthancPlugins::RegisterRestCallback<ServeIIIFFramePyramidInfo>("/wsi/iiif/frames-pyramids/([0-9a-f-]+)/([0-9]+)/info.json", true);
   OrthancPlugins::RegisterRestCallback<ServeIIIFFramePyramidTile>("/wsi/iiif/frames-pyramids/([0-9a-f-]+)/([0-9]+)/([0-9a-z,:]+)/([0-9a-z,!:]+)/([0-9,!]+)/([a-z]+)\\.([a-z]+)", true);
 }
--- a/ViewerPlugin/OrthancExplorer.js	Fri Dec 20 12:33:10 2024 +0100
+++ b/ViewerPlugin/OrthancExplorer.js	Fri Dec 20 12:40:54 2024 +0100
@@ -25,9 +25,9 @@
   var seriesId = $.mobile.pageData.uuid;
 
   $('#wsi-series-button').remove();
+  $('#wsi-series-iiif-button').remove();
   $('#wsi-series-mirador-button').remove();
   $('#wsi-series-openseadragon-button').remove();
-  $('#wsi-series-iiif-button').remove();
 
   $('#series-access').listview('refresh');
 
@@ -124,6 +124,8 @@
   var instanceId = $.mobile.pageData.uuid;
 
   $('#wsi-instance-button').remove();
+  $('#wsi-instance-iiif-button').remove();
+  $('#wsi-instance-mirador-button').remove();
   $('#wsi-instance-openseadragon-button').remove();
 
   var b = $('<a>')
@@ -160,4 +162,46 @@
       }
     });
   }
+
+  if (${ENABLE_IIIF}) {
+    var b = $('<a>')
+        .attr('data-role', 'button')
+        .attr('href', '#')
+        .text('Copy link to IIIF manifest');
+
+    var li = $('<li>')
+        .attr('id', 'wsi-instance-iiif-button')
+        .attr('data-icon', 'gear')
+        .append(b);
+
+    $('#instance-access').append(li).listview('refresh');
+
+    b.click(function(e) {
+      if ($.mobile.pageData) {
+        e.preventDefault();
+        var url = new URL('../wsi/iiif/frames-pyramids/' + instanceId + '/0/manifest.json', window.location.href);
+        navigator.clipboard.writeText(url.href);
+        $(e.target).closest('li').buttonMarkup({ icon: 'check' });
+      }
+    });
+  }
+
+  if (${ENABLE_IIIF} &&
+      ${SERVE_MIRADOR}) {
+    var b = $('<a>')
+        .attr('id', 'wsi-instance-mirador-button')
+        .attr('data-role', 'button')
+        .attr('href', '#')
+        .attr('data-icon', 'search')
+        .attr('data-theme', 'e')
+        .text('Test IIIF in Mirador')
+        .button();
+
+    b.insertAfter($('#instance-info'));
+    b.click(function() {
+      if ($.mobile.pageData) {
+        window.open('../wsi/app/mirador.html?iiif-content=../iiif/frames-pyramids/' + instanceId + '/0/manifest.json');
+      }
+    });
+  }
 });