diff --git a/.gitmodules b/.gitmodules index 2869a11..8640183 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,7 @@ [submodule "libs/libCZIrw"] path = libs/libCZIrw - url = https://github.com/ZEISS/libczi + url = https://github.com/ZEISS/libczi.git + branch = main [submodule "libs/pybind11"] path = libs/pybind11 url = https://github.com/pybind/pybind11 diff --git a/API.md b/API.md index fa0d6ab..ed8b457 100644 --- a/API.md +++ b/API.md @@ -55,13 +55,35 @@ A `CacheOptions` object allows defining a cache type and upper limits for memory ```python cache_options = CacheOptions( type = CacheType.Standard, - max_memory_usge = 500 * 1024**2 # 500 Megabytes + max_memory_usage = 500 * 1024**2 # 500 Megabytes max_sub_block_count = 100, ) with czi.open_czi(file_path, cache_options=cache_options) as czi: ... ``` +### Specifying additional reader-options +The `open_czi` method accepts a `ReaderOptions` structure where additional configurations +can be given controlling the operations. There are two options currently available: +- `enable_mask_awareness` - whether the tile-composition uses mask-information. This is by default `false`. +- `enable_visibility_check_optimization` - in the tile-composition, do a visibility-check before reading sub-blocks, potentially reducing the amount of data that must be loaded. This is by default `true`. + +The `ReaderOptions` can be passed to `open_czi` like in this example: + +```python +cache_options = CacheOptions( + type = CacheType.Standard, + max_memory_usage = 500 * 1024**2 # 500 Megabytes + max_sub_block_count = 100, +) +reader_options = ReaderOptions( + enable_mask_awareness = True +) +with czi.open_czi(file_path, cache_options=cache_options, reader_options=reader_options) as czi: + ... +``` + + ## Reading a CZI The following calls all relate to reading information from the CZI. And, whenever they're called, the file's last write date will be evaluated and cached. **If the file was changed while opened, all file caches will be invalidated.** diff --git a/_pylibCZIrw/src/api/CMakeLists.txt b/_pylibCZIrw/src/api/CMakeLists.txt index 9934d82..bdcccb0 100644 --- a/_pylibCZIrw/src/api/CMakeLists.txt +++ b/_pylibCZIrw/src/api/CMakeLists.txt @@ -13,7 +13,8 @@ add_library( site.cpp StaticContext.cpp StaticContext.h - SubBlockCache.h) + SubBlockCache.h + ReaderOptions.h) target_include_directories(_pylibCZIrw_API PRIVATE ${libCZI_SOURCE_DIR}) target_link_libraries(_pylibCZIrw_API INTERFACE libCZIStatic JxrDecodeStatic) diff --git a/_pylibCZIrw/src/api/CZIreadAPI.cpp b/_pylibCZIrw/src/api/CZIreadAPI.cpp index 572c717..9e8d684 100644 --- a/_pylibCZIrw/src/api/CZIreadAPI.cpp +++ b/_pylibCZIrw/src/api/CZIreadAPI.cpp @@ -21,7 +21,14 @@ CZIreadAPI::CZIreadAPI(const std::string &stream_class_name, CZIreadAPI::CZIreadAPI(const std::string &stream_class_name, const std::wstring &fileName, - const SubBlockCacheOptions &subBlockCacheOptions) { + const SubBlockCacheOptions &subBlockCacheOptions) + : CZIreadAPI(stream_class_name, fileName, subBlockCacheOptions, + ReaderOptions()) {} + +CZIreadAPI::CZIreadAPI(const std::string &stream_class_name, + const std::wstring &fileName, + const SubBlockCacheOptions &subBlockCacheOptions, + const ReaderOptions &readerOptions) { shared_ptr stream; if (stream_class_name.empty() || stream_class_name == "standard") { stream = StreamsFactory::CreateDefaultStreamForFile(fileName.c_str()); @@ -49,6 +56,7 @@ CZIreadAPI::CZIreadAPI(const std::string &stream_class_name, this->spAccessor = reader->CreateSingleChannelScalingTileAccessor(); this->spReader = reader; this->subBlockCacheOptions = subBlockCacheOptions; + this->readerOptions = readerOptions; if (subBlockCacheOptions.cacheType == CacheType::Standard) { this->spSubBlockCache = libCZI::CreateSubBlockCache(); } else if (subBlockCacheOptions.cacheType != CacheType::None) { @@ -117,8 +125,11 @@ std::unique_ptr CZIreadAPI::GetSingleChannelScalingTileAccessorData( libCZI::ISingleChannelScalingTileAccessor::Options scstaOptions; scstaOptions.Clear(); + // set the flag "visibility check optimization" as configured by the user scstaOptions.useVisibilityCheckOptimization = - true; // enable the "visibility check optimization" + this->readerOptions.enableVisibilityCheckOptimization; + // set the flag "mask awareness" as configured by the user + scstaOptions.maskAware = this->readerOptions.enableMaskAwareness; scstaOptions.backGroundColor = bgColor; if (this->spSubBlockCache) { scstaOptions.subBlockCache = this->spSubBlockCache; diff --git a/_pylibCZIrw/src/api/CZIreadAPI.h b/_pylibCZIrw/src/api/CZIreadAPI.h index 80dec66..b5345b8 100644 --- a/_pylibCZIrw/src/api/CZIreadAPI.h +++ b/_pylibCZIrw/src/api/CZIreadAPI.h @@ -1,13 +1,15 @@ #pragma once #include "PImage.h" +#include "ReaderOptions.h" #include "SubBlockCache.h" #include "inc_libCzi.h" #include #include /// Class used to represent a CZI reader object in pylibCZIrw. -/// It gathers the libCZI features needed for reading in the pylibCZIrw project. +/// It gathers the libCZI features needed for reading in the pylibCZIrw +/// project. /// CZIrwAPI will be exposed to python via pybind11 as a czi class. class CZIreadAPI { @@ -21,6 +23,9 @@ class CZIreadAPI { ///< null (in which case no caching is done) SubBlockCacheOptions subBlockCacheOptions; ///< Options for using the subblock cache + ReaderOptions + readerOptions; ///< Options for the reader, such as enabling mask + ///< awareness and visibility check optimizations public: /// Constructor which constructs a CZIrwAPI object from the given wstring. @@ -47,9 +52,10 @@ class CZIreadAPI { /// Creates a spReader and spAccessor (SingleChannelTilingScalingAccessor) for /// the czi document pointed by the given filepath. /// This constructor allows defining a subblock cache to be used for - /// performance optimization. \param fileName Filename of the - /// file. \param subBlockCacheOptions Options for initializing the - /// subblock cache. + /// performance optimization. + /// \param fileName Filename of the file. + /// \param subBlockCacheOptions Options for initializing the + /// subblock cache. CZIreadAPI(const std::wstring &fileName, const SubBlockCacheOptions &subBlockCacheOptions); @@ -61,16 +67,44 @@ class CZIreadAPI { /// optimization. /// /// \param stream_class_name A string identifying the stream class to - /// be used (note that this string is *not* the same string the libCZI-streams - /// factory uses. There is a mapping between - /// the two strings done in the CZIrwAPI - /// constructor. + /// be used (note that this string is *not* + /// the same string the libCZI-streams + /// factory uses. There is a mapping between + /// the two strings done in the CZIrwAPI + /// constructor. /// \param fileName Filename (or URI) of the file (the - /// interpretation of the string is stream class specific). \param - /// subBlockCacheOptions Options for initializing the subblock cache. + /// interpretation of the string is stream + /// class specific). + /// \param subBlockCacheOptions Options for initializing the subblock + /// cache. CZIreadAPI(const std::string &stream_class_name, const std::wstring &fileName, const SubBlockCacheOptions &subBlockCacheOptions); + /// Constructor which constructs a CZIrwAPI object, allowing to specify a + /// stream class name. Possible stream class names are: "standard" for reading + /// files in the file system, and "curl" for reading files from a web server. + /// "curl" is mapped to the libCZI-streams class "curl_http_inputstream". This + /// constructor allows defining a subblock cache to be used for performance + /// optimization. In addition, is allows to specify reader options. + /// + /// \param stream_class_name A string identifying the stream class to + /// be used (note that this string is *not* + /// the same string the libCZI-streams + /// factory uses. There is a mapping between + /// the two strings done in the CZIrwAPI + /// constructor. + /// \param fileName Filename (or URI) of the file (the + /// interpretation of the string is stream + /// class specific). + /// \param subBlockCacheOptions Options for initializing the + /// subblock cache. + /// \param readerOptions Options controlling the reader + /// operation, such as enabling mask + /// aware tile-composition. + CZIreadAPI(const std::string &stream_class_name, const std::wstring &fileName, + const SubBlockCacheOptions &subBlockCacheOptions, + const ReaderOptions &readerOptions); + /// Close the Opened czi document void close() { this->spReader->Close(); } diff --git a/_pylibCZIrw/src/api/ReaderOptions.h b/_pylibCZIrw/src/api/ReaderOptions.h new file mode 100644 index 0000000..79a2755 --- /dev/null +++ b/_pylibCZIrw/src/api/ReaderOptions.h @@ -0,0 +1,18 @@ +#pragma once +#include "inc_libCzi.h" + +/// This POD ("plain-old-data") structure gathers open-time options for the +/// CZI reader. It is used to configure the reader's behavior, such as enabling +/// mask awareness and visibility check optimizations. +struct ReaderOptions { + /// whether to enable mask awareness + bool enableMaskAwareness = false; + + /// whether to enable visibility check optimization + bool enableVisibilityCheckOptimization = true; + + void Clear() { + this->enableMaskAwareness = false; + this->enableVisibilityCheckOptimization = true; + } +}; diff --git a/_pylibCZIrw/src/bindings/CZIrw.cpp b/_pylibCZIrw/src/bindings/CZIrw.cpp index fd4dc2d..437bb45 100644 --- a/_pylibCZIrw/src/bindings/CZIrw.cpp +++ b/_pylibCZIrw/src/bindings/CZIrw.cpp @@ -1,6 +1,7 @@ #include "../api/CZIreadAPI.h" #include "../api/CZIwriteAPI.h" #include "../api/PImage.h" +#include "../api/ReaderOptions.h" #include "../api/SubBlockCache.h" #include "../api/site.h" #include "PbHelper.h" @@ -33,6 +34,13 @@ PYBIND11_MODULE(_pylibCZIrw, m) { return new CZIreadAPI(stream_class_name, fileName, subBlockCacheOptions); })) + .def(py::init([](const std::string &stream_class_name, + const std::wstring &fileName, + const SubBlockCacheOptions &subBlockCacheOptions, + const ReaderOptions &readerOptions) { + return new CZIreadAPI(stream_class_name, fileName, subBlockCacheOptions, + readerOptions); + })) .def("close", &CZIreadAPI::close) .def("GetXmlMetadata", &CZIreadAPI::GetXmlMetadata) .def("GetSubBlockStats", &CZIreadAPI::GetSubBlockStats) @@ -219,6 +227,13 @@ PYBIND11_MODULE(_pylibCZIrw, m) { .def_readwrite("elements_count", &SubBlockCacheInfo::elementsCount) .def_readwrite("memory_usage", &SubBlockCacheInfo::memoryUsage); + py::class_(m, "ReaderOptions", py::module_local()) + .def(py::init<>()) + .def_readwrite("enableMaskAwareness", &ReaderOptions::enableMaskAwareness) + .def_readwrite("enableVisibilityCheckOptimization", + &ReaderOptions::enableVisibilityCheckOptimization) + .def("Clear", &ReaderOptions::Clear); + // perform one-time-initialization of libCZI OneTimeSiteInitialization(); } diff --git a/_pylibCZIrw/src/bindings/PbHelper.h b/_pylibCZIrw/src/bindings/PbHelper.h index 6089f8b..d5498f1 100644 --- a/_pylibCZIrw/src/bindings/PbHelper.h +++ b/_pylibCZIrw/src/bindings/PbHelper.h @@ -1,5 +1,6 @@ #include "../api/CZIreadAPI.h" #include "include_python.h" +#include #include #include #include @@ -19,6 +20,7 @@ class CMemBitmapWrapper : public libCZI::IBitmapData { std::uint32_t width; std::uint32_t height; std::uint32_t stride; + std::atomic lock_count = ATOMIC_VAR_INIT(0); public: CMemBitmapWrapper(libCZI::PixelType pixeltype, std::uint32_t width, @@ -42,10 +44,15 @@ class CMemBitmapWrapper : public libCZI::IBitmapData { bitmapLockInfo.ptrDataRoi = this->ptrData; bitmapLockInfo.stride = this->stride; bitmapLockInfo.size = this->stride * static_cast(this->height); + std::atomic_fetch_add(&this->lock_count, 1); return bitmapLockInfo; } - virtual void Unlock() {} + virtual void Unlock() { std::atomic_fetch_sub(&this->lock_count, 1); } + + virtual int GetLockCount() const { + return std::atomic_load(&this->lock_count); + } }; /// Returns format descriptor corresponding to each libCZI::PixelType. diff --git a/_pylibCZIrw/src/pylibCZIrw_Config.h b/_pylibCZIrw/src/pylibCZIrw_Config.h new file mode 100644 index 0000000..a07ecd6 --- /dev/null +++ b/_pylibCZIrw/src/pylibCZIrw_Config.h @@ -0,0 +1,4 @@ +#pragma once + +// Make the project version available on the C++ side +#define PROJECT_VERSION "" diff --git a/libs/libCZIrw b/libs/libCZIrw index 87c06cb..a996184 160000 --- a/libs/libCZIrw +++ b/libs/libCZIrw @@ -1 +1 @@ -Subproject commit 87c06cbf5eee5b91ca2d3560ad0a247d6ed9c810 +Subproject commit a9961845f465412abf1e468851afc5e8c764202b diff --git a/pylibCZIrw/czi.py b/pylibCZIrw/czi.py index 761936c..ee821d5 100644 --- a/pylibCZIrw/czi.py +++ b/pylibCZIrw/czi.py @@ -1,7 +1,7 @@ """Module implementing the czi interface The open method will create a czi document. -This czi document can be use to read and write czi. +This czi document can be used to read and write czi. """ import contextlib @@ -69,6 +69,20 @@ class CacheOptions: max_sub_block_count: Optional[int] = None +@dataclass +class ReaderOptions: + """Reader options data structure. + + Configuration options for CZI reader. + """ + + enable_mask_awareness: bool = False + """Whether the accessor will use the valid-pixel-mask for the tile-composition.""" + + enable_visibility_check_optimization: bool = True + """Whether the accessor will use the visibility-check-optimization for the tile-composition.""" + + @dataclass class Rgb8Color: """Rgb8Color class. @@ -151,6 +165,7 @@ def __init__( filepath: str, file_input_type: ReaderFileInputTypes = ReaderFileInputTypes.Standard, cache_options: Optional[CacheOptions] = None, + reader_options: Optional[ReaderOptions] = None, ) -> None: """Creates a czi reader object, should only be called through the open_czi() function. @@ -164,20 +179,25 @@ def __init__( The configuration of a subblock cache to be used. """ libczi_cache_options = self._create_default_cache_options(cache_options=cache_options) + libczi_reader_options = self._create_reader_options(reader_options=reader_options) + if file_input_type is ReaderFileInputTypes.Curl: if validators.url(filepath): # When reading from CURL stream we assume that the connection is slow # And therefore also cache uncompressed subblocks. libczi_cache_options.cacheOnlyCompressed = False self._czi_reader = _pylibCZIrw.czi_reader( - ReaderFileInputTypes.Curl.value, filepath, libczi_cache_options + ReaderFileInputTypes.Curl.value, filepath, libczi_cache_options, libczi_reader_options ) else: raise FileNotFoundError(f"{filepath} is not a valid URL.") else: # When reading from disk we only cache compressed subblocks. libczi_cache_options.cacheOnlyCompressed = True - self._czi_reader = _pylibCZIrw.czi_reader(filepath, libczi_cache_options) + # use the "standard reader class name" for local files + self._czi_reader = _pylibCZIrw.czi_reader( + ReaderFileInputTypes.Standard.value, filepath, libczi_cache_options, libczi_reader_options + ) self._stats = self._czi_reader.GetSubBlockStats() @classmethod @@ -196,6 +216,29 @@ def close(self) -> None: """Close the document and finalize the reading""" self._czi_reader.close() + @staticmethod + def _create_reader_options(reader_options: Optional[ReaderOptions]) -> _pylibCZIrw.ReaderOptions: + """Creates a ReaderOptions object from the ReaderOptions dataclass. + + Parameters + ---------- + reader_options : ReaderOptions + Reader options dataclass + + Returns + ------- + : _pylibCZIrw.ReaderOptions + Reader options object. + """ + libczi_reader_options = _pylibCZIrw.ReaderOptions() + libczi_reader_options.Clear() + if reader_options: + libczi_reader_options.enableMaskAwareness = reader_options.enable_mask_awareness + libczi_reader_options.enableVisibilityCheckOptimization = ( + reader_options.enable_visibility_check_optimization + ) + return libczi_reader_options + @staticmethod def _compute_index_ranges( rectangle: _pylibCZIrw.IntRect, @@ -1270,6 +1313,8 @@ def open_czi( filepath: str, file_input_type: ReaderFileInputTypes = ReaderFileInputTypes.Standard, cache_options: Optional[CacheOptions] = None, + *, + reader_options: Optional[ReaderOptions] = None, ) -> Generator: """Initialize a czi reader object and returns it. Opens the filepath and hands it over to the low-level function. @@ -1282,13 +1327,22 @@ def open_czi( The type of file input, default is local file. cache_options : CacheOptions, optional The configuration of a subblock cache to be used. Per default no cache is used. + reader_options: ReaderOptions, optional + Additional configuration options for the reader. Note that there is a keyword-only argument + boundary before the reader_options argument. + Note: This parameter uses a keyword-only argument separator (*) to prevent + accidental positional argument passing. This design choice ensures that + reader_options must be explicitly named when called, improving code clarity + and preventing errors when the function signature evolves. It also maintains + backward compatibility if new optional parameters are added between + cache_options and reader_options in the future. Returns ---------- : czi CziReader document as a czi object """ - reader = CziReader(filepath, file_input_type, cache_options=cache_options) + reader = CziReader(filepath, file_input_type, cache_options=cache_options, reader_options=reader_options) try: yield reader finally: