Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add check pyramid needed (ie: if czi doesnt already have a pyramid, and is greater than 4096*4096) #52

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ We will treat scenes differently at this level for the sake of consistency. The

*Returns:* The raw metadata of the czi parsed into a dictionary.

### Checking if CZI needs a pyramid

**`needs_pyramid`**

*Returns:* True if a pyramid is needed. False if pyramid is already present, or image is smaller than threshold (default of 4096x4096).

### Reading custom attributes

**`custom_attributes_metadata`**
Expand Down
99 changes: 99 additions & 0 deletions _pylibCZIrw/src/api/CZIreadAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,102 @@ SubBlockCacheInfo CZIreadAPI::GetCacheInfo() {

return cacheInfo;
}

bool CZIreadAPI::NeedsPyramid(uint32_t max_extent_of_image) {
ThresholdParameters parameters{ max_extent_of_image };

const auto statistics = this->spReader->GetStatistics();

// First, check the overall bounding box
if (!CheckOverallBoundingBoxForNecessityOfPyramid(statistics, parameters)) {
return false;
}

// Check per-scene bounding boxes
const auto per_scene_result = CheckPerSceneBoundingBoxesForNecessityOfPyramid(statistics, parameters);
if (per_scene_result.value_or(true) == false) {
return false;
}

// Check if pyramid is already present
const auto pyramid_statistics = this->spReader->GetPyramidStatistics();

if (CheckIfPyramidIsPresent(statistics, pyramid_statistics, parameters)) {
return false;
}

return true;
}

bool CZIreadAPI::CheckOverallBoundingBoxForNecessityOfPyramid(const libCZI::SubBlockStatistics& statistics, const ThresholdParameters& threshold_parameters) {
if (IsRectangleAboveThreshold(statistics.boundingBoxLayer0Only, threshold_parameters)) {
return true;
}
return false;
}

std::optional<bool> CZIreadAPI::CheckPerSceneBoundingBoxesForNecessityOfPyramid(const libCZI::SubBlockStatistics& statistics, const ThresholdParameters& threshold_parameters) {
if (statistics.sceneBoundingBoxes.empty()) {
return std::nullopt;
}

for (const auto& sceneBoundingBox : statistics.sceneBoundingBoxes) {
if (IsRectangleAboveThreshold(sceneBoundingBox.second.boundingBoxLayer0, threshold_parameters)) {
return true;
}
}

return false;
}

bool CZIreadAPI::IsRectangleAboveThreshold(const libCZI::IntRect& rectangle, const ThresholdParameters& threshold_parameters) {
if (static_cast<uint32_t>(rectangle.w) > threshold_parameters.max_extent_of_image || static_cast<uint32_t>(rectangle.h) > threshold_parameters.max_extent_of_image) {
return true;
}
return false;
}

bool CZIreadAPI::CheckIfPyramidIsPresent(const libCZI::SubBlockStatistics& statistics, const libCZI::PyramidStatistics& pyramid_statistics, const ThresholdParameters& threshold_parameters) {
if (statistics.sceneBoundingBoxes.empty()) {
// No S-index used; check overall bounding box
if (CheckOverallBoundingBoxForNecessityOfPyramid(statistics, threshold_parameters)) {
const auto& pyramid_layer_statistics_iterator = pyramid_statistics.scenePyramidStatistics.find(std::numeric_limits<int>::max());
if (pyramid_layer_statistics_iterator == pyramid_statistics.scenePyramidStatistics.end()) {
// Unexpected; there should always be a pyramid-layer-0
return false;
}

if (!DoesContainPyramidLayer(pyramid_layer_statistics_iterator->second)) {
return false;
}
}
} else {
// Document contains scenes; check per-scene bounding boxes
if (CheckPerSceneBoundingBoxesForNecessityOfPyramid(statistics, threshold_parameters)) {
for (const auto& sceneBoundingBox : statistics.sceneBoundingBoxes) {
if (IsRectangleAboveThreshold(sceneBoundingBox.second.boundingBoxLayer0, threshold_parameters)) {
const auto& pyramid_layer_statistics_iterator = pyramid_statistics.scenePyramidStatistics.find(sceneBoundingBox.first);
if (pyramid_layer_statistics_iterator == pyramid_statistics.scenePyramidStatistics.end()) {
// Unexpected; there should always be a pyramid-layer-0
return false;
}

if (!DoesContainPyramidLayer(pyramid_layer_statistics_iterator->second)) {
return false;
}
}
}
}
}

return true;
}

bool CZIreadAPI::DoesContainPyramidLayer(const std::vector<libCZI::PyramidStatistics::PyramidLayerStatistics>& pyramid_layer_statistics) {
for (const auto& layer_statistics : pyramid_layer_statistics) {
if (!layer_statistics.layerInfo.IsLayer0() && layer_statistics.count > 0) {
return true;
}
}
return false;
}
18 changes: 18 additions & 0 deletions _pylibCZIrw/src/api/CZIreadAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ class CZIreadAPI {
SubBlockCacheOptions
subBlockCacheOptions; ///< Options for using the subblock cache

struct ThresholdParameters {
uint32_t max_extent_of_image; ///< Helper struct for threshold parameters
};

/// Helper methods for pyramid checking
bool CheckOverallBoundingBoxForNecessityOfPyramid(const libCZI::SubBlockStatistics& statistics, const ThresholdParameters& threshold_parameters);
std::optional<bool> CheckPerSceneBoundingBoxesForNecessityOfPyramid(const libCZI::SubBlockStatistics& statistics, const ThresholdParameters& threshold_parameters);
bool IsRectangleAboveThreshold(const libCZI::IntRect& rectangle, const ThresholdParameters& threshold_parameters);
bool CheckIfPyramidIsPresent(const libCZI::SubBlockStatistics& statistics, const libCZI::PyramidStatistics& pyramid_statistics, const ThresholdParameters& threshold_parameters);
bool DoesContainPyramidLayer(const std::vector<libCZI::PyramidStatistics::PyramidLayerStatistics>& pyramid_layer_statistics);

public:
/// Constructor which constructs a CZIrwAPI object from the given wstring.
/// Creates a spReader and spAccessor (SingleChannelTilingScalingAccessor) for
Expand Down Expand Up @@ -105,4 +116,11 @@ class CZIreadAPI {
/// <returns>A SubBlockCacheInfo struct containing the cache
/// information.</returns>
SubBlockCacheInfo GetCacheInfo();

/// <summary>
/// Determines whether the CZI document needs a pyramid representation based on the specified threshold.
/// </summary>
/// <param name="max_extent_of_image>The maximum extent (width or height) of the image before a pyramid is considered necessary</param>
/// <returns>True if a pyramid is needed, false otherwise</returns>
bool NeedsPyramid(uint32_t max_extent_of_image);
};
4 changes: 3 additions & 1 deletion _pylibCZIrw/src/bindings/CZIrw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ PYBIND11_MODULE(_pylibCZIrw, m) {
pixeltype, roi, bgColor, zoom, coordinateString, SceneIndexes);
return result;
})
.def("GetCacheInfo", &CZIreadAPI::GetCacheInfo);
.def("GetCacheInfo", &CZIreadAPI::GetCacheInfo)
.def("NeedsPyramid", &CZIreadAPI::NeedsPyramid, py::arg("max_extent_of_image"),
"Determines whether the CZI document needs a pyramid representation based on the specified threshold.");

py::class_<CZIwriteAPI>(m, "czi_writer", py::module_local())
.def(py::init<const std::wstring &, const std::string &>())
Expand Down
15 changes: 15 additions & 0 deletions pylibCZIrw/czi.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,21 @@ def close(self) -> None:
"""Close the document and finalize the reading"""
self._czi_reader.close()

def needs_pyramid(self, max_extent_of_image: int = 4096) -> bool:
"""Determine whether the CZI document requires a pyramid representation.

Parameters
----------
max_extent_of_image : int, optional
The maximum extent (width or height) before a pyramid is required. Defaults to 4096.

Returns
----------
: bool
True if a pyramid is needed, False otherwise.
"""
return self._czi_reader.NeedsPyramid(max_extent_of_image)

@staticmethod
def _compute_index_ranges(
rectangle: _pylibCZIrw.IntRect,
Expand Down