Mercurial > hg > orthanc-wsi
changeset 348:6109d3177416
first rendering of on-the-fly pyramid using IIIF
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 10 Dec 2024 22:49:50 +0100 |
parents | 18ba15f8a6ef |
children | 4216cef74415 |
files | ViewerPlugin/IIIF.cpp ViewerPlugin/OrthancExplorer.js |
diffstat | 2 files changed, 145 insertions(+), 31 deletions(-) [+] |
line wrap: on
line diff
--- a/ViewerPlugin/IIIF.cpp Tue Dec 10 18:24:43 2024 +0100 +++ b/ViewerPlugin/IIIF.cpp Tue Dec 10 22:49:50 2024 +0100 @@ -174,26 +174,6 @@ } -static unsigned int GetPhysicalTileWidth(const OrthancWSI::ITiledPyramid& pyramid, - unsigned int level) -{ - return static_cast<unsigned int>(boost::math::iround( - static_cast<float>(pyramid.GetTileWidth(level)) * - static_cast<float>(pyramid.GetLevelWidth(0)) / - static_cast<float>(pyramid.GetLevelWidth(level)))); -} - - -static unsigned int GetPhysicalTileHeight(const OrthancWSI::ITiledPyramid& pyramid, - unsigned int level) -{ - return static_cast<unsigned int>(boost::math::iround( - static_cast<float>(pyramid.GetTileHeight(level)) * - static_cast<float>(pyramid.GetLevelHeight(0)) / - static_cast<float>(pyramid.GetLevelHeight(level)))); -} - - namespace { class RegionParameters @@ -338,6 +318,22 @@ std::unique_ptr<OrthancWSI::RawTile> rawTile_; std::unique_ptr<Orthanc::ImageAccessor> toCrop_; + static unsigned int GetPhysicalTileWidth(const OrthancWSI::ITiledPyramid& pyramid, + unsigned int level) + { + return static_cast<unsigned int>(boost::math::iround(static_cast<float>(pyramid.GetTileWidth(level)) * + static_cast<float>(pyramid.GetLevelWidth(0)) / + static_cast<float>(pyramid.GetLevelWidth(level)))); + } + + static unsigned int GetPhysicalTileHeight(const OrthancWSI::ITiledPyramid& pyramid, + unsigned int level) + { + return static_cast<unsigned int>(boost::math::iround(static_cast<float>(pyramid.GetTileHeight(level)) * + static_cast<float>(pyramid.GetLevelHeight(0)) / + static_cast<float>(pyramid.GetLevelHeight(level)))); + } + public: RegionRenderer(const RegionParameters& parameters, OrthancWSI::ITiledPyramid& pyramid) : @@ -370,19 +366,35 @@ } else { - rawTile_.reset(new OrthancWSI::RawTile(pyramid, level, - parameters.GetX() / GetPhysicalTileWidth(pyramid, level), - parameters.GetY() / GetPhysicalTileHeight(pyramid, level))); - - assert(rawTile_->GetTileWidth() == pyramid.GetTileWidth(level)); - assert(rawTile_->GetTileHeight() == pyramid.GetTileHeight(level)); + const unsigned int tileX = parameters.GetX() / GetPhysicalTileWidth(pyramid, level); + const unsigned int tileY = parameters.GetY() / GetPhysicalTileHeight(pyramid, level); + rawTile_.reset(new OrthancWSI::RawTile(pyramid, level, tileX, tileY)); - if (!rawTile_->IsEmpty() && - (parameters.GetCropWidth() < pyramid.GetTileWidth(level) || - parameters.GetCropHeight() < pyramid.GetTileHeight(level))) + if (rawTile_->IsEmpty()) { - toCrop_.reset(rawTile_->Decode()); - rawTile_.reset(NULL); + bool isEmpty; + toCrop_.reset(pyramid.DecodeTile(isEmpty, level, tileX, tileY)); + if (isEmpty) + { + toCrop_.reset(NULL); + } + else + { + rawTile_.reset(NULL); + } + } + else + { + assert(rawTile_->GetTileWidth() == pyramid.GetTileWidth(level)); + assert(rawTile_->GetTileHeight() == pyramid.GetTileHeight(level)); + + if (!rawTile_->IsEmpty() && + (parameters.GetCropWidth() < pyramid.GetTileWidth(level) || + parameters.GetCropHeight() < pyramid.GetTileHeight(level))) + { + toCrop_.reset(rawTile_->Decode()); + rawTile_.reset(NULL); + } } } } @@ -769,6 +781,85 @@ } +void ServeIIIFFramePyramidInfo(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: Image API call to whole-slide pyramid of frame " << frameNumber << " of instance " << instanceId; + + Json::Value result; + + { + OrthancWSI::DecodedPyramidCache::Accessor accessor(OrthancWSI::DecodedPyramidCache::GetInstance(), instanceId, frameNumber); + GeneratePyramidInfo(result, accessor.GetPyramid(), "instance " + instanceId + " (frame " + boost::lexical_cast<std::string>(frameNumber) + ")"); + } + + result["id"] = iiifPublicUrl_ + "frames-pyramids/" + instanceId + "/" + boost::lexical_cast<std::string>(frameNumber); + + std::string s = result.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Json)); +} + + +void ServeIIIFFramePyramidTile(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + const std::string instanceId(request->groups[0]); + const std::string region(request->groups[2]); + const std::string size(request->groups[3]); + const std::string rotation(request->groups[4]); + const std::string quality(request->groups[5]); + const std::string format(request->groups[6]); + + unsigned int frameNumber; + if (!Orthanc::SerializationToolbox::ParseUnsignedInteger32(frameNumber, request->groups[1])) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + LOG(INFO) << "IIIF: Image API call to tile of frame " << frameNumber << " in instance " << instanceId << ": " + << "region=" << region << "; size=" << size << "; rotation=" + << rotation << "; quality=" << quality << "; format=" << format; + + const RegionParameters parameters(region, size, rotation, quality, format); + + if (parameters.IsFull()) + { + std::unique_ptr<Orthanc::ImageAccessor> image; + + { + OrthancWSI::DecodedPyramidCache::Accessor accessor(OrthancWSI::DecodedPyramidCache::GetInstance(), instanceId, frameNumber); + image.reset(RenderFullImage(accessor.GetPyramid())); + } + + std::string encoded; + OrthancWSI::RawTile::Encode(encoded, *image, Orthanc::MimeType_Jpeg); + + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), + encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); + } + else + { + std::unique_ptr<RegionRenderer> renderer; + + { + OrthancWSI::DecodedPyramidCache::Accessor accessor(OrthancWSI::DecodedPyramidCache::GetInstance(), instanceId, frameNumber); + renderer.reset(new RegionRenderer(parameters, accessor.GetPyramid())); + } + + renderer->Answer(output); + } +} + + void InitializeIIIF(const std::string& iiifPublicUrl) { iiifPublicUrl_ = iiifPublicUrl; @@ -778,6 +869,10 @@ OrthancPlugins::RegisterRestCallback<ServeIIIFManifest>("/wsi/iiif/series/([0-9a-f-]+)/manifest.json", true); 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); + + // New in WSI 3.0 + 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); } void SetIIIFForcePowersOfTwoScaleFactors(bool force)
--- a/ViewerPlugin/OrthancExplorer.js Tue Dec 10 18:24:43 2024 +0100 +++ b/ViewerPlugin/OrthancExplorer.js Tue Dec 10 22:49:50 2024 +0100 @@ -124,6 +124,7 @@ var instanceId = $.mobile.pageData.uuid; $('#wsi-instance-button').remove(); + $('#openseadragon-instance-button').remove(); var b = $('<a>') .attr('id', 'wsi-instance-button') @@ -140,4 +141,22 @@ window.open('../wsi/app/viewer.html?instance=' + instanceId); } }); + + if (${SERVE_OPEN_SEADRAGON}) { + var b = $('<a>') + .attr('id', 'openseadragon-instance-button') + .attr('data-role', 'button') + .attr('href', '#') + .attr('data-icon', 'search') + .attr('data-theme', 'e') + .text('Test IIIF in OpenSeadragon') + .button(); + + b.insertAfter($('#instance-info')); + b.click(function () { + if ($.mobile.pageData) { + window.open('../wsi/app/openseadragon.html?image=../iiif/frames-pyramids/' + instanceId + '/0/info.json'); + } + }); + } });