Mercurial > hg > orthanc-wsi
changeset 337:82d976848b34
created OnTheFlyPyramidsCache class
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 06 Dec 2024 11:15:46 +0100 |
parents | dfd9ecf38091 |
children | a76e0cf77264 |
files | Applications/CMakeLists.txt Framework/Inputs/CytomineImage.h Framework/Inputs/DecodedTiledPyramid.h Framework/Inputs/OnTheFlyPyramid.cpp Framework/Inputs/OnTheFlyPyramid.h Framework/Inputs/OnTheFlyPyramidsCache.cpp Framework/Inputs/OnTheFlyPyramidsCache.h Framework/Inputs/OpenSlidePyramid.cpp Framework/Inputs/OpenSlidePyramid.h Framework/Inputs/SingleLevelDecodedPyramid.h ViewerPlugin/CMakeLists.txt ViewerPlugin/Plugin.cpp |
diffstat | 12 files changed, 424 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/Applications/CMakeLists.txt Wed Dec 04 22:41:47 2024 +0100 +++ b/Applications/CMakeLists.txt Fri Dec 06 11:15:46 2024 +0100 @@ -120,6 +120,7 @@ ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidLevel.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/HierarchicalTiff.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/OnTheFlyPyramid.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/OnTheFlyPyramidsCache.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlideLibrary.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/OpenSlidePyramid.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/PlainTiff.cpp
--- a/Framework/Inputs/CytomineImage.h Wed Dec 04 22:41:47 2024 +0100 +++ b/Framework/Inputs/CytomineImage.h Fri Dec 06 11:15:46 2024 +0100 @@ -91,5 +91,10 @@ } void SetImageCompression(ImageCompression compression); + + virtual size_t GetMemoryUsage() const ORTHANC_OVERRIDE + { + return 0; // Image is stored on the remote Cytomine server + } }; }
--- a/Framework/Inputs/DecodedTiledPyramid.h Wed Dec 04 22:41:47 2024 +0100 +++ b/Framework/Inputs/DecodedTiledPyramid.h Fri Dec 06 11:15:46 2024 +0100 @@ -72,5 +72,7 @@ { return false; // No access to the raw tiles } + + virtual size_t GetMemoryUsage() const = 0; }; }
--- a/Framework/Inputs/OnTheFlyPyramid.cpp Wed Dec 04 22:41:47 2024 +0100 +++ b/Framework/Inputs/OnTheFlyPyramid.cpp Fri Dec 06 11:15:46 2024 +0100 @@ -118,4 +118,18 @@ return *higherLevels_[level - 1]; } } + + + size_t OnTheFlyPyramid::GetMemoryUsage() const + { + size_t memory = baseLevel_->GetSize(); + + for (size_t i= 0; i < higherLevels_.size(); i++) + { + assert(higherLevels_[i] != NULL); + memory += higherLevels_[i]->GetSize(); + } + + return memory; + } }
--- a/Framework/Inputs/OnTheFlyPyramid.h Wed Dec 04 22:41:47 2024 +0100 +++ b/Framework/Inputs/OnTheFlyPyramid.h Fri Dec 06 11:15:46 2024 +0100 @@ -91,5 +91,7 @@ { return Orthanc::PhotometricInterpretation_RGB; } + + size_t GetMemoryUsage() const ORTHANC_OVERRIDE; }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/OnTheFlyPyramidsCache.cpp Fri Dec 06 11:15:46 2024 +0100 @@ -0,0 +1,224 @@ +/** + * 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) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 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 <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeadersWSI.h" +#include "OnTheFlyPyramidsCache.h" + + +static std::unique_ptr<OrthancWSI::OnTheFlyPyramidsCache> singleton_; + +namespace OrthancWSI +{ + class OnTheFlyPyramidsCache::CachedPyramid : public boost::noncopyable + { + private: + std::unique_ptr<DecodedTiledPyramid> pyramid_; + size_t memory_; + + public: + explicit CachedPyramid(DecodedTiledPyramid* pyramid) : + pyramid_(pyramid) + { + if (pyramid == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + memory_ = pyramid->GetMemoryUsage(); + } + } + + const DecodedTiledPyramid& GetPyramid() const + { + assert(pyramid_ != NULL); + return *pyramid_; + } + + size_t GetMemoryUsage() const + { + return memory_; + } + }; + + + bool OnTheFlyPyramidsCache::SanityCheck() + { + return (cache_.GetSize() < maxCount_ && + (cache_.IsEmpty() || maxMemory_ == 0 || memoryUsage_ <= maxMemory_)); + } + + + void OnTheFlyPyramidsCache::MakeRoom(size_t memory) + { + // Mutex must be locked + + while (cache_.GetSize() >= maxCount_ || + (!cache_.IsEmpty() && + maxMemory_ != 0 && + memoryUsage_ + memory > maxMemory_)) + { + CachedPyramid* oldest = NULL; + cache_.RemoveOldest(oldest); + + if (oldest == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + memoryUsage_ -= oldest->GetMemoryUsage(); + delete oldest; + } + } + + assert(SanityCheck()); + } + + + OnTheFlyPyramidsCache::CachedPyramid * OnTheFlyPyramidsCache::Store(FrameIdentifier identifier, + DecodedTiledPyramid *pyramid) + { + // Mutex must be locked + + std::unique_ptr<CachedPyramid> payload(new CachedPyramid(pyramid)); + CachedPyramid* result = payload.get(); + + MakeRoom(payload->GetMemoryUsage()); + + // Add a new element to the cache and make it the most + // recently used entry + cache_.Add(identifier, payload.release()); + memoryUsage_ += payload->GetMemoryUsage(); + + assert(SanityCheck()); + return result; + } + + + OnTheFlyPyramidsCache::OnTheFlyPyramidsCache(IPyramidFetcher *fetcher, + size_t maxCount, + size_t maxMemory): + fetcher_(fetcher), + maxCount_(maxCount), + maxMemory_(maxMemory), // 256 MB + memoryUsage_(0) + { + if (fetcher == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + if (maxCount == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(SanityCheck()); + } + + + void OnTheFlyPyramidsCache::InitializeInstance(IPyramidFetcher *fetcher, + size_t maxSize, + size_t maxMemory) + { + if (singleton_.get() == NULL) + { + singleton_.reset(new OnTheFlyPyramidsCache(fetcher, maxSize, maxMemory)); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void OnTheFlyPyramidsCache::FinalizeInstance() + { + if (singleton_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + singleton_.reset(NULL); + } + } + + + OnTheFlyPyramidsCache & OnTheFlyPyramidsCache::GetInstance() + { + if (singleton_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *singleton_; + } + } + + + OnTheFlyPyramidsCache::Accessor::Accessor(OnTheFlyPyramidsCache that, + const std::string &instanceId, + unsigned int frameNumber): + lock_(that.mutex_), + identifier_(instanceId, frameNumber), + pyramid_(NULL) + { + if (that.cache_.Contains(identifier_, pyramid_)) + { + if (pyramid_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + // Tag the series as the most recently used + that.cache_.MakeMostRecent(identifier_); + } + else + { + // Unlock the mutex as creating the pyramid is a time-consuming operation + lock_.unlock(); + + std::unique_ptr<DecodedTiledPyramid> payload(that.fetcher_->Fetch(instanceId, frameNumber)); + + // Re-lock, as we now modify the cache + lock_.lock(); + pyramid_ = that.Store(identifier_, payload.release()); + } + } + + + const DecodedTiledPyramid & OnTheFlyPyramidsCache::Accessor::GetPyramid() const + { + if (IsValid()) + { + return pyramid_->GetPyramid(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Inputs/OnTheFlyPyramidsCache.h Fri Dec 06 11:15:46 2024 +0100 @@ -0,0 +1,114 @@ +/** + * 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) 2024-2024 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2024 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 <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "DecodedTiledPyramid.h" + +#include <Cache/LeastRecentlyUsedIndex.h> + +#include <boost/thread/mutex.hpp> + + +namespace OrthancWSI +{ + class OnTheFlyPyramidsCache : public boost::noncopyable + { + public: + class IPyramidFetcher : public boost::noncopyable + { + public: + virtual ~IPyramidFetcher() + { + } + + virtual DecodedTiledPyramid* Fetch(const std::string& instanceId, + unsigned int frameNumber) = 0; + }; + + private: + class CachedPyramid; + + typedef std::pair<std::string, unsigned int> FrameIdentifier; // Associates an instance ID with a frame number + + typedef Orthanc::LeastRecentlyUsedIndex<FrameIdentifier, CachedPyramid*> Cache; + + std::unique_ptr<IPyramidFetcher> fetcher_; + + boost::mutex mutex_; + size_t maxCount_; + size_t maxMemory_; + size_t memoryUsage_; + Cache cache_; + + bool SanityCheck(); + + void MakeRoom(size_t memory); + + CachedPyramid* Store(FrameIdentifier identifier, + DecodedTiledPyramid* pyramid); + + OnTheFlyPyramidsCache(IPyramidFetcher* fetcher /* takes ownership */, + size_t maxCount, + size_t maxMemory); + + public: + static void InitializeInstance(IPyramidFetcher* fetcher, + size_t maxSize, + size_t maxMemory); + + static void FinalizeInstance(); + + static OnTheFlyPyramidsCache& GetInstance(); + + class Accessor : public boost::noncopyable + { + private: + boost::mutex::scoped_lock lock_; + FrameIdentifier identifier_; + CachedPyramid* pyramid_; + + public: + Accessor(OnTheFlyPyramidsCache that, + const std::string& instanceId, + unsigned int frameNumber); + + bool IsValid() const + { + return pyramid_ != NULL; + } + + const std::string& GetInstanceId() const + { + return identifier_.first; + } + + unsigned int GetFrameNumber() const + { + return identifier_.second; + } + + const DecodedTiledPyramid& GetPyramid() const; + }; + }; +}
--- a/Framework/Inputs/OpenSlidePyramid.cpp Wed Dec 04 22:41:47 2024 +0100 +++ b/Framework/Inputs/OpenSlidePyramid.cpp Fri Dec 06 11:15:46 2024 +0100 @@ -160,4 +160,17 @@ return false; } } + + + size_t OpenSlidePyramid::GetMemoryUsage() const + { + size_t countPixels = 0; + + for (unsigned int i = 0; i < image_.GetLevelCount(); i++) + { + countPixels += image_.GetLevelWidth(i) * image_.GetLevelHeight(i); + } + + return countPixels * Orthanc::GetBytesPerPixel(Orthanc::PixelFormat_RGBA32); + } }
--- a/Framework/Inputs/OpenSlidePyramid.h Wed Dec 04 22:41:47 2024 +0100 +++ b/Framework/Inputs/OpenSlidePyramid.h Fri Dec 06 11:15:46 2024 +0100 @@ -84,5 +84,7 @@ bool LookupImagedVolumeSize(float& width, float& height) const; + + size_t GetMemoryUsage() const ORTHANC_OVERRIDE; }; }
--- a/Framework/Inputs/SingleLevelDecodedPyramid.h Wed Dec 04 22:41:47 2024 +0100 +++ b/Framework/Inputs/SingleLevelDecodedPyramid.h Fri Dec 06 11:15:46 2024 +0100 @@ -84,5 +84,10 @@ uint8_t backgroundRed, uint8_t backgroundGreen, uint8_t backgroundBlue); + + size_t GetMemoryUsage() const ORTHANC_OVERRIDE + { + return image_.GetSize(); + } }; }
--- a/ViewerPlugin/CMakeLists.txt Wed Dec 04 22:41:47 2024 +0100 +++ b/ViewerPlugin/CMakeLists.txt Fri Dec 06 11:15:46 2024 +0100 @@ -200,6 +200,7 @@ ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidInstance.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/DicomPyramidLevel.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/OnTheFlyPyramid.cpp + ${ORTHANC_WSI_DIR}/Framework/Inputs/OnTheFlyPyramidsCache.cpp ${ORTHANC_WSI_DIR}/Framework/Inputs/PyramidWithRawTiles.cpp ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Reader.cpp ${ORTHANC_WSI_DIR}/Framework/Jpeg2000Writer.cpp
--- a/ViewerPlugin/Plugin.cpp Wed Dec 04 22:41:47 2024 +0100 +++ b/ViewerPlugin/Plugin.cpp Fri Dec 06 11:15:46 2024 +0100 @@ -26,6 +26,9 @@ #include "DicomPyramidCache.h" #include "IIIF.h" #include "RawTile.h" +#include "../Framework/Inputs/DecodedTiledPyramid.h" +#include "../Framework/Inputs/OnTheFlyPyramid.h" +#include "../Framework/Inputs/OnTheFlyPyramidsCache.h" #include <Compatibility.h> // For std::unique_ptr #include <Images/Image.h> @@ -39,10 +42,48 @@ #include <EmbeddedResources.h> #include <cassert> +#include <Images/PngReader.h> +#include <boost/atomic/detail/lock_pool.hpp> + #define ORTHANC_PLUGIN_NAME "wsi" +namespace OrthancWSI +{ + class OrthancPyramidFrameFetcher : public OnTheFlyPyramidsCache::IPyramidFetcher + { + private: + std::unique_ptr<OrthancStone::IOrthancConnection> orthanc_; + bool smooth_; + + public: + explicit OrthancPyramidFrameFetcher(OrthancStone::IOrthancConnection* orthanc, + bool smooth) : + orthanc_(orthanc), + smooth_(smooth) + { + if (orthanc == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + DecodedTiledPyramid * Fetch(const std::string &instanceId, + unsigned frameNumber) ORTHANC_OVERRIDE + { + std::string png; + orthanc_->RestApiGet(png, "/instances/" + instanceId + "/frames/" + boost::lexical_cast<std::string>(frameNumber) + "/preview"); + + std::unique_ptr<Orthanc::PngReader> reader(new Orthanc::PngReader()); + reader->ReadFromMemory(png); + + return new OnTheFlyPyramid(reader.release(), 512, 512, smooth_); + } + }; +} + + static bool DisplayPerformanceWarning() { (void) DisplayPerformanceWarning; // Disable warning about unused function