Mercurial > hg > orthanc-wsi
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'); + } + }); + } });