# HG changeset patch # User Sebastien Jodogne # Date 1688972380 -7200 # Node ID c72fbdecdc382876c14ce542538df1a15e6e8b50 # Parent 35c241231af2599f86a6cede481e47224f9c19de reorganization diff -r 35c241231af2 -r c72fbdecdc38 ViewerPlugin/CMakeLists.txt --- a/ViewerPlugin/CMakeLists.txt Mon Jul 10 08:31:50 2023 +0200 +++ b/ViewerPlugin/CMakeLists.txt Mon Jul 10 08:59:40 2023 +0200 @@ -183,6 +183,7 @@ set(ORTHANC_WSI_SOURCES DicomPyramidCache.cpp + IIIF.cpp OrthancPluginConnection.cpp Plugin.cpp RawTile.cpp diff -r 35c241231af2 -r c72fbdecdc38 ViewerPlugin/DicomPyramidCache.cpp --- a/ViewerPlugin/DicomPyramidCache.cpp Mon Jul 10 08:31:50 2023 +0200 +++ b/ViewerPlugin/DicomPyramidCache.cpp Mon Jul 10 08:59:40 2023 +0200 @@ -23,10 +23,15 @@ #include "../Framework/PrecompiledHeadersWSI.h" #include "DicomPyramidCache.h" +#include "OrthancPluginConnection.h" + #include // For std::unique_ptr #include +static std::unique_ptr singleton_; + + namespace OrthancWSI { DicomPyramid* DicomPyramidCache::GetCachedPyramid(const std::string& seriesId) @@ -67,8 +72,9 @@ // time-consuming operation, we don't want it to block other clients) lock.unlock(); + assert(orthanc_.get() != NULL); std::unique_ptr pyramid - (new DicomPyramid(orthanc_, seriesId, true /* use metadata cache */)); + (new DicomPyramid(*orthanc_, seriesId, true /* use metadata cache */)); { // The pyramid is constructed: Store it into the cache @@ -112,11 +118,15 @@ } - DicomPyramidCache::DicomPyramidCache(OrthancStone::IOrthancConnection& orthanc, + DicomPyramidCache::DicomPyramidCache(OrthancStone::IOrthancConnection* orthanc /* takes ownership */, size_t maxSize) : orthanc_(orthanc), maxSize_(maxSize) { + if (orthanc == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } } @@ -135,6 +145,45 @@ } + void DicomPyramidCache::InitializeInstance(size_t maxSize) + { + if (singleton_.get() == NULL) + { + singleton_.reset(new DicomPyramidCache(new OrthancWSI::OrthancPluginConnection, maxSize)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void DicomPyramidCache::FinalizeInstance() + { + if (singleton_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + singleton_.reset(NULL); + } + } + + + DicomPyramidCache& DicomPyramidCache::GetInstance() + { + if (singleton_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *singleton_; + } + } + + void DicomPyramidCache::Invalidate(const std::string& seriesId) { boost::mutex::scoped_lock lock(mutex_); @@ -151,10 +200,10 @@ } - DicomPyramidCache::Locker::Locker(DicomPyramidCache& cache, - const std::string& seriesId) : - lock_(cache.mutex_), - pyramid_(cache.GetPyramid(seriesId, lock_)) + DicomPyramidCache::Locker::Locker(const std::string& seriesId) : + cache_(DicomPyramidCache::GetInstance()), + lock_(cache_.mutex_), + pyramid_(cache_.GetPyramid(seriesId, lock_)) { } } diff -r 35c241231af2 -r c72fbdecdc38 ViewerPlugin/DicomPyramidCache.h --- a/ViewerPlugin/DicomPyramidCache.h Mon Jul 10 08:31:50 2023 +0200 +++ b/ViewerPlugin/DicomPyramidCache.h Mon Jul 10 08:59:40 2023 +0200 @@ -27,6 +27,8 @@ #include #include +#include + namespace OrthancWSI { @@ -35,11 +37,14 @@ private: typedef Orthanc::LeastRecentlyUsedIndex Cache; - boost::mutex mutex_; - OrthancStone::IOrthancConnection& orthanc_; - size_t maxSize_; - Cache cache_; + std::unique_ptr orthanc_; + boost::mutex mutex_; + size_t maxSize_; + Cache cache_; + + DicomPyramidCache(OrthancStone::IOrthancConnection* orthanc /* takes ownership */, + size_t maxSize); DicomPyramid* GetCachedPyramid(const std::string& seriesId); @@ -47,22 +52,25 @@ boost::mutex::scoped_lock& lock); public: - DicomPyramidCache(OrthancStone::IOrthancConnection& orthanc, - size_t maxSize); + ~DicomPyramidCache(); + + static void InitializeInstance(size_t maxSize); - ~DicomPyramidCache(); + static void FinalizeInstance(); + + static DicomPyramidCache& GetInstance(); void Invalidate(const std::string& seriesId); class Locker : public boost::noncopyable { private: + DicomPyramidCache& cache_; boost::mutex::scoped_lock lock_; DicomPyramid& pyramid_; public: - Locker(DicomPyramidCache& cache, - const std::string& seriesId); + Locker(const std::string& seriesId); DicomPyramid& GetPyramid() const { diff -r 35c241231af2 -r c72fbdecdc38 ViewerPlugin/IIIF.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/IIIF.cpp Mon Jul 10 08:59:40 2023 +0200 @@ -0,0 +1,424 @@ +/** + * 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 . + **/ + + +#include "../Framework/PrecompiledHeadersWSI.h" +#include "IIIF.h" + +#include "DicomPyramidCache.h" +#include "RawTile.h" +#include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h" + +#include +#include +#include + +#include +#include + + +static std::string iiifPublicUrl_; + + +static void ServeIIIFImageInfo(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + std::string seriesId(request->groups[0]); + + LOG(INFO) << "IIIF: Image API call to whole-slide pyramid of series " << seriesId; + + OrthancWSI::DicomPyramidCache::Locker locker(seriesId); + + if (locker.GetPyramid().GetLevelCount() == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + if (locker.GetPyramid().GetTileWidth(0) != locker.GetPyramid().GetTileHeight(0)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, + "IIIF doesn't support non-isotropic tile sizes"); + } + + for (unsigned int i = 1; i < locker.GetPyramid().GetLevelCount(); i++) + { + if (locker.GetPyramid().GetTileWidth(i) != locker.GetPyramid().GetTileWidth(0) || + locker.GetPyramid().GetTileHeight(i) != locker.GetPyramid().GetTileHeight(0)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, + "IIIF doesn't support levels with varying tile sizes"); + } + } + + Json::Value sizes = Json::arrayValue; + Json::Value scaleFactors = Json::arrayValue; + + for (unsigned int i = locker.GetPyramid().GetLevelCount(); i > 0; i--) + { + /** + * Openseadragon seems to have difficulties in rendering + * non-integer scale factors. Consequently, we only keep the + * levels with an integer scale factor. + **/ + if (locker.GetPyramid().GetLevelWidth(0) % locker.GetPyramid().GetLevelWidth(i - 1) == 0 && + locker.GetPyramid().GetLevelHeight(0) % locker.GetPyramid().GetLevelHeight(i - 1) == 0) + { + Json::Value level; + level["width"] = locker.GetPyramid().GetLevelWidth(i - 1); + level["height"] = locker.GetPyramid().GetLevelHeight(i - 1); + sizes.append(level); + + scaleFactors.append(static_cast(locker.GetPyramid().GetLevelWidth(0)) / + static_cast(locker.GetPyramid().GetLevelWidth(i - 1))); + } + } + + Json::Value tiles; + tiles["width"] = locker.GetPyramid().GetTileWidth(0); + tiles["height"] = locker.GetPyramid().GetTileHeight(0); + tiles["scaleFactors"] = scaleFactors; + + Json::Value result; + result["@context"] = "http://iiif.io/api/image/2/context.json"; + result["@id"] = iiifPublicUrl_ + seriesId; + result["profile"] = "http://iiif.io/api/image/2/level0.json"; + result["protocol"] = "http://iiif.io/api/image"; + result["width"] = locker.GetPyramid().GetLevelWidth(0); + result["height"] = locker.GetPyramid().GetLevelHeight(0); + result["sizes"] = sizes; + + result["tiles"] = Json::arrayValue; + result["tiles"].append(tiles); + + std::string s = result.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); +} + + +static unsigned int GetPhysicalTileWidth(const OrthancWSI::ITiledPyramid& pyramid, + unsigned int level) +{ + return static_cast(boost::math::iround( + static_cast(pyramid.GetTileWidth(level)) * + static_cast(pyramid.GetLevelWidth(0)) / + static_cast(pyramid.GetLevelWidth(level)))); +} + + +static unsigned int GetPhysicalTileHeight(const OrthancWSI::ITiledPyramid& pyramid, + unsigned int level) +{ + return static_cast(boost::math::iround( + static_cast(pyramid.GetTileHeight(level)) * + static_cast(pyramid.GetLevelHeight(0)) / + static_cast(pyramid.GetLevelHeight(level)))); +} + + +static void ServeIIIFImageTile(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + std::string seriesId(request->groups[0]); + std::string region(request->groups[1]); + std::string size(request->groups[2]); + std::string rotation(request->groups[3]); + std::string quality(request->groups[4]); + std::string format(request->groups[5]); + + LOG(INFO) << "IIIF: Image API call to tile of series " << seriesId << ": " + << "region=" << region << "; size=" << size << "; rotation=" + << rotation << "; quality=" << quality << "; format=" << format; + + if (rotation != "0") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported rotation: " + rotation); + } + + if (quality != "default") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported quality: " + quality); + } + + if (format != "jpg") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported format: " + format); + } + + if (region == "full") + { + OrthancWSI::DicomPyramidCache::Locker locker(seriesId); + + OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid(); + const unsigned int level = pyramid.GetLevelCount() - 1; + + Orthanc::Image full(Orthanc::PixelFormat_RGB24, pyramid.GetLevelWidth(level), pyramid.GetLevelHeight(level), false); + Orthanc::ImageProcessing::Set(full, 255, 255, 255, 0); + + const unsigned int nx = OrthancWSI::CeilingDivision(pyramid.GetLevelWidth(level), pyramid.GetTileWidth(level)); + const unsigned int ny = OrthancWSI::CeilingDivision(pyramid.GetLevelHeight(level), pyramid.GetTileHeight(level)); + for (unsigned int ty = 0; ty < ny; ty++) + { + const unsigned int y = ty * pyramid.GetTileHeight(level); + const unsigned int height = std::min(pyramid.GetTileHeight(level), full.GetHeight() - y); + + for (unsigned int tx = 0; tx < nx; tx++) + { + const unsigned int x = tx * pyramid.GetTileWidth(level); + std::unique_ptr tile(pyramid.DecodeTile(level, tx, ty)); + + const unsigned int width = std::min(pyramid.GetTileWidth(level), full.GetWidth() - x); + + Orthanc::ImageAccessor source, target; + tile->GetRegion(source, 0, 0, width, height); + full.GetRegion(target, x, y, width, height); + + Orthanc::ImageProcessing::Copy(target, source); + } + } + + std::string encoded; + OrthancWSI::RawTile::Encode(encoded, full, Orthanc::MimeType_Jpeg); + + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), + encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); + } + else + { + int regionX, regionY, regionWidth, regionHeight; + + bool ok = false; + boost::regex regionPattern("([0-9]+),([0-9]+),([0-9]+),([0-9]+)"); + boost::cmatch regionWhat; + if (regex_match(region.c_str(), regionWhat, regionPattern)) + { + try + { + regionX = boost::lexical_cast(regionWhat[1]); + regionY = boost::lexical_cast(regionWhat[2]); + regionWidth = boost::lexical_cast(regionWhat[3]); + regionHeight = boost::lexical_cast(regionWhat[4]); + ok = (regionX >= 0 && + regionY >= 0 && + regionWidth > 0 && + regionHeight > 0); + } + catch (boost::bad_lexical_cast&) + { + } + } + + if (!ok) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (x,y,width,height) region: " + region); + } + + int cropWidth; + boost::regex sizePattern("([0-9]+),"); + boost::cmatch sizeWhat; + if (regex_match(size.c_str(), sizeWhat, sizePattern)) + { + try + { + cropWidth = boost::lexical_cast(sizeWhat[1]); + ok = (cropWidth > 0); + } + catch (boost::bad_lexical_cast&) + { + } + } + + if (!ok) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (width,) size: " + size); + } + + std::unique_ptr rawTile; + std::unique_ptr toCrop; + + { + OrthancWSI::DicomPyramidCache::Locker locker(seriesId); + + OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid(); + + unsigned int level; + for (level = 0; level < pyramid.GetLevelCount(); level++) + { + const unsigned int physicalTileWidth = GetPhysicalTileWidth(pyramid, level); + const unsigned int physicalTileHeight = GetPhysicalTileHeight(pyramid, level); + + if (regionX % physicalTileWidth == 0 && + regionY % physicalTileHeight == 0 && + regionWidth <= physicalTileWidth && + regionHeight <= physicalTileHeight && + regionX + regionWidth <= pyramid.GetLevelWidth(0) && + regionY + regionHeight <= pyramid.GetLevelHeight(0)) + { + break; + } + } + + if (level == pyramid.GetLevelCount()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Cannot locate the level of interest"); + } + else if (cropWidth > pyramid.GetTileWidth(level)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Request for a cropping that is too large for the tile size"); + } + else + { + rawTile.reset(new OrthancWSI::RawTile(locker.GetPyramid(), level, + regionX / GetPhysicalTileWidth(pyramid, level), + regionY / GetPhysicalTileHeight(pyramid, level))); + + if (cropWidth < pyramid.GetTileWidth(level)) + { + toCrop.reset(rawTile->Decode()); + rawTile.reset(NULL); + } + } + } + + if (rawTile.get() != NULL) + { + assert(toCrop.get() == NULL); + + // Level 0 Compliance of IIIF expects JPEG files + rawTile->Answer(output, Orthanc::MimeType_Jpeg); + } + else if (toCrop.get() != NULL) + { + assert(rawTile.get() == NULL); + assert(cropWidth < toCrop->GetWidth()); + + Orthanc::ImageAccessor cropped; + toCrop->GetRegion(cropped, 0, 0, cropWidth, toCrop->GetHeight()); + + std::string encoded; + OrthancWSI::RawTile::Encode(encoded, cropped, Orthanc::MimeType_Jpeg); + + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), + encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } +} + + +static void ServeIIIFManifest(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + /** + * 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/ + **/ + + std::string seriesId(request->groups[0]); + + LOG(INFO) << "IIIF: Presentation API call to whole-slide pyramid of series " << seriesId; + + Json::Value study, series; + if (!OrthancPlugins::RestApiGet(series, "/series/" + seriesId, false) || + !OrthancPlugins::RestApiGet(study, "/series/" + seriesId + "/study", false)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } + + unsigned int width, height; + + { + OrthancWSI::DicomPyramidCache::Locker locker(seriesId); + width = locker.GetPyramid().GetLevelWidth(0); + height = locker.GetPyramid().GetLevelHeight(0); + } + + const std::string base = iiifPublicUrl_ + seriesId; + + Json::Value service; + service["id"] = base; + service["profile"] = "level0"; + service["type"] = "ImageService3"; + + Json::Value body; + body["id"] = base + "/full/max/0/default.jpg"; + body["type"] = "Image"; + body["format"] = Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg); + body["height"] = height; + body["width"] = width; + body["service"].append(service); + + Json::Value annotation; + annotation["id"] = base + "/annotation/p0001-image"; + annotation["type"] = "Annotation"; + annotation["motivation"] = "painting"; + annotation["body"] = body; + annotation["target"] = base + "/canvas/p1"; + + Json::Value annotationPage; + annotationPage["id"] = base + "/page/p1/1"; + annotationPage["type"] = "AnnotationPage"; + annotationPage["items"].append(annotation); + + Json::Value canvas; + canvas["id"] = annotation["target"]; + canvas["type"] = "Canvas"; + canvas["width"] = width; + canvas["height"] = height; + + Json::Value labels = Json::arrayValue; + labels.append(series["MainDicomTags"]["SeriesDate"].asString() + " - " + + series["MainDicomTags"]["SeriesDescription"].asString()); + canvas["label"]["en"] = labels; + + canvas["items"].append(annotationPage); + + Json::Value manifest; + manifest["@context"] = "http://iiif.io/api/presentation/3/context.json"; + manifest["id"] = base + "/manifest.json"; + manifest["type"] = "Manifest"; + + labels = Json::arrayValue; + labels.append(study["MainDicomTags"]["StudyDate"].asString() + " - " + + study["MainDicomTags"]["StudyDescription"].asString()); + manifest["label"]["en"] = labels; + + manifest["items"].append(canvas); + + std::string s = manifest.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); +} + + +void InitializeIIIF(const std::string& iiifPublicUrl) +{ + iiifPublicUrl_ = iiifPublicUrl; + + OrthancPlugins::RegisterRestCallback("/wsi/iiif/([0-9a-f-]+)/info.json", true); + OrthancPlugins::RegisterRestCallback("/wsi/iiif/([0-9a-f-]+)/([0-9a-z,:]+)/([0-9a-z,!:]+)/([0-9,!]+)/([a-z]+)\\.([a-z]+)", true); + OrthancPlugins::RegisterRestCallback("/wsi/iiif/([0-9a-f-]+)/manifest.json", true); +} diff -r 35c241231af2 -r c72fbdecdc38 ViewerPlugin/IIIF.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ViewerPlugin/IIIF.h Mon Jul 10 08:59:40 2023 +0200 @@ -0,0 +1,27 @@ +/** + * 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 . + **/ + + +#pragma once + +#include + +void InitializeIIIF(const std::string& iiifPublicUrl); diff -r 35c241231af2 -r c72fbdecdc38 ViewerPlugin/Plugin.cpp --- a/ViewerPlugin/Plugin.cpp Mon Jul 10 08:31:50 2023 +0200 +++ b/ViewerPlugin/Plugin.cpp Mon Jul 10 08:59:40 2023 +0200 @@ -23,7 +23,7 @@ #include "../Framework/PrecompiledHeadersWSI.h" #include "DicomPyramidCache.h" -#include "OrthancPluginConnection.h" +#include "IIIF.h" #include "RawTile.h" #include // For std::unique_ptr @@ -38,12 +38,6 @@ #include #include -#include -#include - -static std::unique_ptr orthanc_; -static std::unique_ptr cache_; -static std::string publicIIIFUrl_; static void AnswerSparseTile(OrthancPluginRestOutput* output, unsigned int tileWidth, @@ -85,7 +79,7 @@ OrthancPluginLogInfo(OrthancPlugins::GetGlobalContext(), tmp); - OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); + OrthancWSI::DicomPyramidCache::Locker locker(seriesId); unsigned int totalWidth = locker.GetPyramid().GetLevelWidth(0); unsigned int totalHeight = locker.GetPyramid().GetLevelHeight(0); @@ -154,14 +148,14 @@ } // Retrieve the raw tile from the WSI pyramid - std::unique_ptr rawTile; + std::unique_ptr rawTile; { - OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); - rawTile.reset(new RawTile(locker.GetPyramid(), - static_cast(level), - static_cast(tileX), - static_cast(tileY))); + OrthancWSI::DicomPyramidCache::Locker locker(seriesId); + rawTile.reset(new OrthancWSI::RawTile(locker.GetPyramid(), + static_cast(level), + static_cast(tileX), + static_cast(tileY))); } /** @@ -185,7 +179,7 @@ sprintf(tmp, "New instance has been added to series %s, invalidating it", resourceId); OrthancPluginLogInfo(OrthancPlugins::GetGlobalContext(), tmp); - cache_->Invalidate(resourceId); + OrthancWSI::DicomPyramidCache::GetInstance().Invalidate(resourceId); } return OrthancPluginErrorCode_Success; @@ -234,383 +228,6 @@ } - -void ServeIIIFImageInfo(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - std::string seriesId(request->groups[0]); - - LOG(INFO) << "IIIF: Image API call to whole-slide pyramid of series " << seriesId; - - OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); - - if (locker.GetPyramid().GetLevelCount() == 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - if (locker.GetPyramid().GetTileWidth(0) != locker.GetPyramid().GetTileHeight(0)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, - "IIIF doesn't support non-isotropic tile sizes"); - } - - for (unsigned int i = 1; i < locker.GetPyramid().GetLevelCount(); i++) - { - if (locker.GetPyramid().GetTileWidth(i) != locker.GetPyramid().GetTileWidth(0) || - locker.GetPyramid().GetTileHeight(i) != locker.GetPyramid().GetTileHeight(0)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, - "IIIF doesn't support levels with varying tile sizes"); - } - } - - Json::Value sizes = Json::arrayValue; - Json::Value scaleFactors = Json::arrayValue; - - for (unsigned int i = locker.GetPyramid().GetLevelCount(); i > 0; i--) - { - /** - * Openseadragon seems to have difficulties in rendering - * non-integer scale factors. Consequently, we only keep the - * levels with an integer scale factor. - **/ - if (locker.GetPyramid().GetLevelWidth(0) % locker.GetPyramid().GetLevelWidth(i - 1) == 0 && - locker.GetPyramid().GetLevelHeight(0) % locker.GetPyramid().GetLevelHeight(i - 1) == 0) - { - Json::Value level; - level["width"] = locker.GetPyramid().GetLevelWidth(i - 1); - level["height"] = locker.GetPyramid().GetLevelHeight(i - 1); - sizes.append(level); - - scaleFactors.append(static_cast(locker.GetPyramid().GetLevelWidth(0)) / - static_cast(locker.GetPyramid().GetLevelWidth(i - 1))); - } - } - - Json::Value tiles; - tiles["width"] = locker.GetPyramid().GetTileWidth(0); - tiles["height"] = locker.GetPyramid().GetTileHeight(0); - tiles["scaleFactors"] = scaleFactors; - - Json::Value result; - result["@context"] = "http://iiif.io/api/image/2/context.json"; - result["@id"] = publicIIIFUrl_ + seriesId; - result["profile"] = "http://iiif.io/api/image/2/level0.json"; - result["protocol"] = "http://iiif.io/api/image"; - result["width"] = locker.GetPyramid().GetLevelWidth(0); - result["height"] = locker.GetPyramid().GetLevelHeight(0); - result["sizes"] = sizes; - - result["tiles"] = Json::arrayValue; - result["tiles"].append(tiles); - - std::string s = result.toStyledString(); - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); -} - - -static unsigned int GetPhysicalTileWidth(const OrthancWSI::ITiledPyramid& pyramid, - unsigned int level) -{ - return static_cast(boost::math::iround( - static_cast(pyramid.GetTileWidth(level)) * - static_cast(pyramid.GetLevelWidth(0)) / - static_cast(pyramid.GetLevelWidth(level)))); -} - - -static unsigned int GetPhysicalTileHeight(const OrthancWSI::ITiledPyramid& pyramid, - unsigned int level) -{ - return static_cast(boost::math::iround( - static_cast(pyramid.GetTileHeight(level)) * - static_cast(pyramid.GetLevelHeight(0)) / - static_cast(pyramid.GetLevelHeight(level)))); -} - - -void ServeIIIFImageTile(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - std::string seriesId(request->groups[0]); - std::string region(request->groups[1]); - std::string size(request->groups[2]); - std::string rotation(request->groups[3]); - std::string quality(request->groups[4]); - std::string format(request->groups[5]); - - LOG(INFO) << "IIIF: Image API call to tile of series " << seriesId << ": " - << "region=" << region << "; size=" << size << "; rotation=" - << rotation << "; quality=" << quality << "; format=" << format; - - if (rotation != "0") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported rotation: " + rotation); - } - - if (quality != "default") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported quality: " + quality); - } - - if (format != "jpg") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Unsupported format: " + format); - } - - if (region == "full") - { - OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); - - OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid(); - const unsigned int level = pyramid.GetLevelCount() - 1; - - Orthanc::Image full(Orthanc::PixelFormat_RGB24, pyramid.GetLevelWidth(level), pyramid.GetLevelHeight(level), false); - Orthanc::ImageProcessing::Set(full, 255, 255, 255, 0); - - const unsigned int nx = OrthancWSI::CeilingDivision(pyramid.GetLevelWidth(level), pyramid.GetTileWidth(level)); - const unsigned int ny = OrthancWSI::CeilingDivision(pyramid.GetLevelHeight(level), pyramid.GetTileHeight(level)); - for (unsigned int ty = 0; ty < ny; ty++) - { - const unsigned int y = ty * pyramid.GetTileHeight(level); - const unsigned int height = std::min(pyramid.GetTileHeight(level), full.GetHeight() - y); - - for (unsigned int tx = 0; tx < nx; tx++) - { - const unsigned int x = tx * pyramid.GetTileWidth(level); - std::unique_ptr tile(pyramid.DecodeTile(level, tx, ty)); - - const unsigned int width = std::min(pyramid.GetTileWidth(level), full.GetWidth() - x); - - Orthanc::ImageAccessor source, target; - tile->GetRegion(source, 0, 0, width, height); - full.GetRegion(target, x, y, width, height); - - Orthanc::ImageProcessing::Copy(target, source); - } - } - - std::string encoded; - RawTile::Encode(encoded, full, Orthanc::MimeType_Jpeg); - - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), - encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); - } - else - { - int regionX, regionY, regionWidth, regionHeight; - - bool ok = false; - boost::regex regionPattern("([0-9]+),([0-9]+),([0-9]+),([0-9]+)"); - boost::cmatch regionWhat; - if (regex_match(region.c_str(), regionWhat, regionPattern)) - { - try - { - regionX = boost::lexical_cast(regionWhat[1]); - regionY = boost::lexical_cast(regionWhat[2]); - regionWidth = boost::lexical_cast(regionWhat[3]); - regionHeight = boost::lexical_cast(regionWhat[4]); - ok = (regionX >= 0 && - regionY >= 0 && - regionWidth > 0 && - regionHeight > 0); - } - catch (boost::bad_lexical_cast&) - { - } - } - - if (!ok) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (x,y,width,height) region: " + region); - } - - int cropWidth; - boost::regex sizePattern("([0-9]+),"); - boost::cmatch sizeWhat; - if (regex_match(size.c_str(), sizeWhat, sizePattern)) - { - try - { - cropWidth = boost::lexical_cast(sizeWhat[1]); - ok = (cropWidth > 0); - } - catch (boost::bad_lexical_cast&) - { - } - } - - if (!ok) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, "IIIF - Not a (width,) size: " + size); - } - - std::unique_ptr rawTile; - std::unique_ptr toCrop; - - { - OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); - - OrthancWSI::ITiledPyramid& pyramid = locker.GetPyramid(); - - unsigned int level; - for (level = 0; level < pyramid.GetLevelCount(); level++) - { - const unsigned int physicalTileWidth = GetPhysicalTileWidth(pyramid, level); - const unsigned int physicalTileHeight = GetPhysicalTileHeight(pyramid, level); - - if (regionX % physicalTileWidth == 0 && - regionY % physicalTileHeight == 0 && - regionWidth <= physicalTileWidth && - regionHeight <= physicalTileHeight && - regionX + regionWidth <= pyramid.GetLevelWidth(0) && - regionY + regionHeight <= pyramid.GetLevelHeight(0)) - { - break; - } - } - - if (level == pyramid.GetLevelCount()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Cannot locate the level of interest"); - } - else if (cropWidth > pyramid.GetTileWidth(level)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "IIIF - Request for a cropping that is too large for the tile size"); - } - else - { - rawTile.reset(new RawTile(locker.GetPyramid(), level, - regionX / GetPhysicalTileWidth(pyramid, level), - regionY / GetPhysicalTileHeight(pyramid, level))); - - if (cropWidth < pyramid.GetTileWidth(level)) - { - toCrop.reset(rawTile->Decode()); - rawTile.reset(NULL); - } - } - } - - if (rawTile.get() != NULL) - { - assert(toCrop.get() == NULL); - - // Level 0 Compliance of IIIF expects JPEG files - rawTile->Answer(output, Orthanc::MimeType_Jpeg); - } - else if (toCrop.get() != NULL) - { - assert(rawTile.get() == NULL); - assert(cropWidth < toCrop->GetWidth()); - - Orthanc::ImageAccessor cropped; - toCrop->GetRegion(cropped, 0, 0, cropWidth, toCrop->GetHeight()); - - std::string encoded; - RawTile::Encode(encoded, cropped, Orthanc::MimeType_Jpeg); - - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, encoded.c_str(), - encoded.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } -} - - -void ServeIIIFManifest(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - /** - * 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/ - **/ - - std::string seriesId(request->groups[0]); - - LOG(INFO) << "IIIF: Presentation API call to whole-slide pyramid of series " << seriesId; - - Json::Value study, series; - if (!OrthancPlugins::RestApiGet(series, "/series/" + seriesId, false) || - !OrthancPlugins::RestApiGet(study, "/series/" + seriesId + "/study", false)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); - } - - unsigned int width, height; - - { - OrthancWSI::DicomPyramidCache::Locker locker(*cache_, seriesId); - width = locker.GetPyramid().GetLevelWidth(0); - height = locker.GetPyramid().GetLevelHeight(0); - } - - const std::string base = publicIIIFUrl_ + seriesId; - - Json::Value service; - service["id"] = base; - service["profile"] = "level0"; - service["type"] = "ImageService3"; - - Json::Value body; - body["id"] = base + "/full/max/0/default.jpg"; - body["type"] = "Image"; - body["format"] = Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg); - body["height"] = height; - body["width"] = width; - body["service"].append(service); - - Json::Value annotation; - annotation["id"] = base + "/annotation/p0001-image"; - annotation["type"] = "Annotation"; - annotation["motivation"] = "painting"; - annotation["body"] = body; - annotation["target"] = base + "/canvas/p1"; - - Json::Value annotationPage; - annotationPage["id"] = base + "/page/p1/1"; - annotationPage["type"] = "AnnotationPage"; - annotationPage["items"].append(annotation); - - Json::Value canvas; - canvas["id"] = annotation["target"]; - canvas["type"] = "Canvas"; - canvas["width"] = width; - canvas["height"] = height; - - Json::Value labels = Json::arrayValue; - labels.append(series["MainDicomTags"]["SeriesDate"].asString() + " - " + - series["MainDicomTags"]["SeriesDescription"].asString()); - canvas["label"]["en"] = labels; - - canvas["items"].append(annotationPage); - - Json::Value manifest; - manifest["@context"] = "http://iiif.io/api/presentation/3/context.json"; - manifest["id"] = base + "/manifest.json"; - manifest["type"] = "Manifest"; - - labels = Json::arrayValue; - labels.append(study["MainDicomTags"]["StudyDate"].asString() + " - " + - study["MainDicomTags"]["StudyDescription"].asString()); - manifest["label"]["en"] = labels; - - manifest["items"].append(canvas); - - std::string s = manifest.toStyledString(); - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(), s.size(), "application/json"); -} - - extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context) @@ -647,7 +264,7 @@ // hardware threads (e.g. number of CPUs or cores or // hyperthreading units) unsigned int threads = Orthanc::SystemToolbox::GetHardwareConcurrency(); - RawTile::InitializeTranscoderSemaphore(threads); + OrthancWSI::RawTile::InitializeTranscoderSemaphore(threads); char info[1024]; sprintf(info, "The whole-slide imaging plugin will use at most %u threads to transcode the tiles", threads); @@ -655,19 +272,7 @@ OrthancPluginSetDescription(context, "Provides a Web viewer of whole-slide microscopic images within Orthanc."); - orthanc_.reset(new OrthancWSI::OrthancPluginConnection); - cache_.reset(new OrthancWSI::DicomPyramidCache(*orthanc_, 10 /* Number of pyramids to be cached - TODO parameter */)); - - { - // TODO => CONFIG - publicIIIFUrl_ = "http://localhost:8042/wsi/iiif"; - - if (publicIIIFUrl_.empty() || - publicIIIFUrl_[publicIIIFUrl_.size() - 1] != '/') - { - publicIIIFUrl_ += "/"; - } - } + OrthancWSI::DicomPyramidCache::InitializeInstance(10 /* Number of pyramids to be cached - TODO parameter */); OrthancPluginRegisterOnChangeCallback(OrthancPlugins::GetGlobalContext(), OnChangeCallback); @@ -678,9 +283,18 @@ OrthancPlugins::RegisterRestCallback("/wsi/pyramids/([0-9a-f-]+)", true); OrthancPlugins::RegisterRestCallback("/wsi/tiles/([0-9a-f-]+)/([0-9-]+)/([0-9-]+)/([0-9-]+)", true); - OrthancPlugins::RegisterRestCallback("/wsi/iiif/([0-9a-f-]+)/info.json", true); - OrthancPlugins::RegisterRestCallback("/wsi/iiif/([0-9a-f-]+)/([0-9a-z,:]+)/([0-9a-z,!:]+)/([0-9,!]+)/([a-z]+)\\.([a-z]+)", true); - OrthancPlugins::RegisterRestCallback("/wsi/iiif/([0-9a-f-]+)/manifest.json", true); + { + // TODO => CONFIG + std::string url = "http://localhost:8042/wsi/iiif"; + + if (url.empty() || + url[url.size() - 1] != '/') + { + url += "/"; + } + + InitializeIIIF(url); + } // Extend the default Orthanc Explorer with custom JavaScript for WSI std::string explorer; @@ -693,9 +307,8 @@ ORTHANC_PLUGINS_API void OrthancPluginFinalize() { - cache_.reset(NULL); - orthanc_.reset(NULL); - RawTile::FinalizeTranscoderSemaphore(); + OrthancWSI::DicomPyramidCache::FinalizeInstance(); + OrthancWSI::RawTile::FinalizeTranscoderSemaphore(); } diff -r 35c241231af2 -r c72fbdecdc38 ViewerPlugin/RawTile.cpp --- a/ViewerPlugin/RawTile.cpp Mon Jul 10 08:31:50 2023 +0200 +++ b/ViewerPlugin/RawTile.cpp Mon Jul 10 08:59:40 2023 +0200 @@ -37,150 +37,153 @@ static std::unique_ptr transcoderSemaphore_; -Orthanc::ImageAccessor* RawTile::DecodeInternal() +namespace OrthancWSI { - switch (compression_) + Orthanc::ImageAccessor* RawTile::DecodeInternal() { - case OrthancWSI::ImageCompression_Jpeg: + switch (compression_) { - std::unique_ptr decoded(new Orthanc::JpegReader); - decoded->ReadFromMemory(tile_); - return decoded.release(); - } - - case OrthancWSI::ImageCompression_Jpeg2000: - { - std::unique_ptr decoded(new OrthancWSI::Jpeg2000Reader); - decoded->ReadFromMemory(tile_); - - if (photometric_ == Orthanc::PhotometricInterpretation_YBR_ICT) + case ImageCompression_Jpeg: { - OrthancWSI::ImageToolbox::ConvertJpegYCbCrToRgb(*decoded); + std::unique_ptr decoded(new Orthanc::JpegReader); + decoded->ReadFromMemory(tile_); + return decoded.release(); } - return decoded.release(); - } + case ImageCompression_Jpeg2000: + { + std::unique_ptr decoded(new Jpeg2000Reader); + decoded->ReadFromMemory(tile_); - case OrthancWSI::ImageCompression_None: - { - unsigned int bpp = Orthanc::GetBytesPerPixel(format_); - if (bpp * tileWidth_ * tileHeight_ != tile_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + if (photometric_ == Orthanc::PhotometricInterpretation_YBR_ICT) + { + ImageToolbox::ConvertJpegYCbCrToRgb(*decoded); + } + + return decoded.release(); } - std::unique_ptr decoded(new Orthanc::ImageAccessor); - decoded->AssignReadOnly(format_, tileWidth_, tileHeight_, bpp * tileWidth_, tile_.c_str()); + case ImageCompression_None: + { + unsigned int bpp = Orthanc::GetBytesPerPixel(format_); + if (bpp * tileWidth_ * tileHeight_ != tile_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } - return decoded.release(); + std::unique_ptr decoded(new Orthanc::ImageAccessor); + decoded->AssignReadOnly(format_, tileWidth_, tileHeight_, bpp * tileWidth_, tile_.c_str()); + + return decoded.release(); + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } -} -void RawTile::EncodeInternal(std::string& encoded, - const Orthanc::ImageAccessor& decoded, - Orthanc::MimeType transcodingType) -{ - switch (transcodingType) + void RawTile::EncodeInternal(std::string& encoded, + const Orthanc::ImageAccessor& decoded, + Orthanc::MimeType transcodingType) { - case Orthanc::MimeType_Png: + switch (transcodingType) { - Orthanc::PngWriter writer; - Orthanc::IImageWriter::WriteToMemory(writer, encoded, decoded); - break; + case Orthanc::MimeType_Png: + { + Orthanc::PngWriter writer; + Orthanc::IImageWriter::WriteToMemory(writer, encoded, decoded); + break; + } + + case Orthanc::MimeType_Jpeg: + { + Orthanc::JpegWriter writer; + Orthanc::IImageWriter::WriteToMemory(writer, encoded, decoded); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } - - case Orthanc::MimeType_Jpeg: - { - Orthanc::JpegWriter writer; - Orthanc::IImageWriter::WriteToMemory(writer, encoded, decoded); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } -} -RawTile::RawTile(OrthancWSI::ITiledPyramid& pyramid, - unsigned int level, - unsigned int tileX, - unsigned int tileY) : - format_(pyramid.GetPixelFormat()), - tileWidth_(pyramid.GetTileWidth(level)), - tileHeight_(pyramid.GetTileHeight(level)), - photometric_(pyramid.GetPhotometricInterpretation()) -{ - if (!pyramid.ReadRawTile(tile_, compression_, level, tileX, tileY)) + RawTile::RawTile(ITiledPyramid& pyramid, + unsigned int level, + unsigned int tileX, + unsigned int tileY) : + format_(pyramid.GetPixelFormat()), + tileWidth_(pyramid.GetTileWidth(level)), + tileHeight_(pyramid.GetTileHeight(level)), + photometric_(pyramid.GetPhotometricInterpretation()) { - // Handling of missing tile (for sparse tiling): TODO parameter? - // AnswerSparseTile(output, tileWidth, tileHeight); return; - throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + if (!pyramid.ReadRawTile(tile_, compression_, level, tileX, tileY)) + { + // Handling of missing tile (for sparse tiling): TODO parameter? + // AnswerSparseTile(output, tileWidth, tileHeight); return; + throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); + } } -} -void RawTile::Answer(OrthancPluginRestOutput* output, - Orthanc::MimeType transcodingType) -{ - if (compression_ == OrthancWSI::ImageCompression_Jpeg) - { - // The tile is already a JPEG image. In such a case, we can - // serve it as such, because any Web browser can handle JPEG. - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, tile_.c_str(), - tile_.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); - } - else + void RawTile::Answer(OrthancPluginRestOutput* output, + Orthanc::MimeType transcodingType) { - // This is a lossless frame (coming from a JPEG2000 or an - // uncompressed DICOM instance), which is not a DICOM-JPEG - // instance. We need to decompress the raw tile, then transcode - // it to the PNG/JPEG, depending on the "transcodingType". - - std::string transcoded; - + if (compression_ == ImageCompression_Jpeg) + { + // The tile is already a JPEG image. In such a case, we can + // serve it as such, because any Web browser can handle JPEG. + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, tile_.c_str(), + tile_.size(), Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg)); + } + else { - // The semaphore is used to throttle the number of simultaneous computations - Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); + // This is a lossless frame (coming from a JPEG2000 or an + // uncompressed DICOM instance), which is not a DICOM-JPEG + // instance. We need to decompress the raw tile, then transcode + // it to the PNG/JPEG, depending on the "transcodingType". + + std::string transcoded; + + { + // The semaphore is used to throttle the number of simultaneous computations + Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); + + std::unique_ptr decoded(DecodeInternal()); + EncodeInternal(transcoded, *decoded, transcodingType); + } + + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, transcoded.c_str(), + transcoded.size(), Orthanc::EnumerationToString(transcodingType)); + } + } + - std::unique_ptr decoded(DecodeInternal()); - EncodeInternal(transcoded, *decoded, transcodingType); - } + Orthanc::ImageAccessor* RawTile::Decode() + { + Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); + return DecodeInternal(); + } + - OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, transcoded.c_str(), - transcoded.size(), Orthanc::EnumerationToString(transcodingType)); + void RawTile::Encode(std::string& encoded, + const Orthanc::ImageAccessor& decoded, + Orthanc::MimeType transcodingType) + { + Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); + EncodeInternal(encoded, decoded, transcodingType); + } + + + void RawTile::InitializeTranscoderSemaphore(unsigned int maxThreads) + { + transcoderSemaphore_.reset(new Orthanc::Semaphore(maxThreads)); + } + + + void RawTile::FinalizeTranscoderSemaphore() + { + transcoderSemaphore_.reset(NULL); } } - - -Orthanc::ImageAccessor* RawTile::Decode() -{ - Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); - return DecodeInternal(); -} - - -void RawTile::Encode(std::string& encoded, - const Orthanc::ImageAccessor& decoded, - Orthanc::MimeType transcodingType) -{ - Orthanc::Semaphore::Locker locker(*transcoderSemaphore_); - EncodeInternal(encoded, decoded, transcodingType); -} - - -void RawTile::InitializeTranscoderSemaphore(unsigned int maxThreads) -{ - transcoderSemaphore_.reset(new Orthanc::Semaphore(maxThreads)); -} - - -void RawTile::FinalizeTranscoderSemaphore() -{ - transcoderSemaphore_.reset(NULL); -} diff -r 35c241231af2 -r c72fbdecdc38 ViewerPlugin/RawTile.h --- a/ViewerPlugin/RawTile.h Mon Jul 10 08:31:50 2023 +0200 +++ b/ViewerPlugin/RawTile.h Mon Jul 10 08:59:40 2023 +0200 @@ -28,40 +28,43 @@ #include -class RawTile : public boost::noncopyable +namespace OrthancWSI { -private: - Orthanc::PixelFormat format_; - unsigned int tileWidth_; - unsigned int tileHeight_; - Orthanc::PhotometricInterpretation photometric_; - std::string tile_; - OrthancWSI::ImageCompression compression_; + class RawTile : public boost::noncopyable + { + private: + Orthanc::PixelFormat format_; + unsigned int tileWidth_; + unsigned int tileHeight_; + Orthanc::PhotometricInterpretation photometric_; + std::string tile_; + ImageCompression compression_; - Orthanc::ImageAccessor* DecodeInternal(); + Orthanc::ImageAccessor* DecodeInternal(); - static void EncodeInternal(std::string& encoded, - const Orthanc::ImageAccessor& decoded, - Orthanc::MimeType transcodingType); + static void EncodeInternal(std::string& encoded, + const Orthanc::ImageAccessor& decoded, + Orthanc::MimeType transcodingType); -public: - RawTile(OrthancWSI::ITiledPyramid& pyramid, - unsigned int level, - unsigned int tileX, - unsigned int tileY); + public: + RawTile(ITiledPyramid& pyramid, + unsigned int level, + unsigned int tileX, + unsigned int tileY); - void Answer(OrthancPluginRestOutput* output, - Orthanc::MimeType transcodingType); + void Answer(OrthancPluginRestOutput* output, + Orthanc::MimeType transcodingType); - Orthanc::ImageAccessor* Decode(); + Orthanc::ImageAccessor* Decode(); - static void Encode(std::string& encoded, - const Orthanc::ImageAccessor& decoded, - Orthanc::MimeType transcodingType); + static void Encode(std::string& encoded, + const Orthanc::ImageAccessor& decoded, + Orthanc::MimeType transcodingType); - // This semaphore is used to implement throttling for the - // decoding/encoding of tiles - static void InitializeTranscoderSemaphore(unsigned int maxThreads); + // This semaphore is used to implement throttling for the + // decoding/encoding of tiles + static void InitializeTranscoderSemaphore(unsigned int maxThreads); - static void FinalizeTranscoderSemaphore(); -}; + static void FinalizeTranscoderSemaphore(); + }; +}