From 225b9b917103f79dc8d27ff7ce4c2390c60db3ae Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Wed, 1 Jul 2026 12:46:06 -0400 Subject: [PATCH 1/6] feat(omezarr)!: migrate to z5 3.0.1 ArrayView API, drop xtensor Upgrade the OME-Zarr readers from z5 2.0.20 (xtensor-based) to the header-only z5 3.0.1 ArrayView API, removing the xtensor and xsimd dependencies entirely. - omezarr.h / raw_omezarr.h: replace xt::xarray + xtensor_access with z5::multiarray::makeView + array_access.hxx; read into plain std::vector buffers. No change to on-disk read behavior. - CMakeLists.txt: drop the GCC 7.5 xtensor guard and the Boost find_package from the Z5 detection block (z5 3.0.1 needs neither). - conda_cpp.txt / conda_cpp_mac.txt / environment.yml: pin z5py >=3.0.1 (the conda package that ships the z5 C++ headers) and remove xtensor / xsimd. BREAKING CHANGE: OME-Zarr support now requires z5 3.0.1 and C++20; xtensor/xsimd are no longer used. --- CMakeLists.txt | 18 +---------- ci-utils/envs/conda_cpp.txt | 4 +-- ci-utils/envs/conda_cpp_mac.txt | 4 +-- environment.yml | 4 +-- src/nyx/omezarr.h | 49 +++++++++++++++++++----------- src/nyx/raw_omezarr.h | 53 ++++++++++++++++++--------------- 6 files changed, 64 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9679d11c..d685f07a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,28 +83,16 @@ if(USEGPU) endif() if(USE_Z5) - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - #since xtensor does not built with GCC 7.5 and lower - if(NOT(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.5)) - set(USE_Z5 OFF) - message(WARNING "OMEZarr support is not available with GCC 7.5 and older compiler.") - endif() - endif() find_package(ZLIB) if (NOT ZLIB_FOUND) set(USE_Z5 OFF) - message(WARNING "zlib not found. Blosc and hereby OMEZarr support will not be available.") + message(WARNING "zlib not found. OMEZarr support will not be available.") endif() find_package(BLOSC) if(NOT BLOSC_FOUND) set(USE_Z5 OFF) message(WARNING "Blosc not found. OMEZarr support will not be available.") endif() - find_package(Boost) - if(NOT Boost_FOUND) - set(USE_Z5 OFF) - message(WARNING "Boost not found. OMEZarr support will not be available.") - endif() find_package(nlohmann_json) if(NOT nlohmann_json_FOUND) @@ -282,10 +270,6 @@ if(USE_Z5) list(APPEND Nyxus_LIBRARIES ${BLOSC_LIBRARIES}) endif() - if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIR}) - endif() - if (ZLIB_FOUND) list(APPEND Nyxus_LIBRARIES ${ZLIB_LIBRARIES} ) include_directories (${ZLIB_INCLUDE_DIR}) diff --git a/ci-utils/envs/conda_cpp.txt b/ci-utils/envs/conda_cpp.txt index 8a963274..4772fbaa 100644 --- a/ci-utils/envs/conda_cpp.txt +++ b/ci-utils/envs/conda_cpp.txt @@ -1,11 +1,9 @@ -z5py +z5py >=3.0.1 libtiff <=4.7.0 boost >=1.63 nlohmann_json blosc pybind11 >= 2.12.0 -xtensor >=0.26,<0.27 -xsimd >=13,<14 cmake dcmtk >=3.6.9 fmjpeg2koj >=1.0.3 diff --git a/ci-utils/envs/conda_cpp_mac.txt b/ci-utils/envs/conda_cpp_mac.txt index 8a074ebe..b041fd16 100644 --- a/ci-utils/envs/conda_cpp_mac.txt +++ b/ci-utils/envs/conda_cpp_mac.txt @@ -1,13 +1,11 @@ # Mac CI does not exercise DICOM support, and conda-forge does not provide a # satisfiable combination of dcmtk/fmjpeg2koj with the pinned libtiff stack. -z5py +z5py >=3.0.1 libtiff <=4.7.0 boost >=1.63 nlohmann_json blosc pybind11 >= 2.12.0 -xtensor >=0.26,<0.27 -xsimd >=13,<14 cmake libarrow libparquet diff --git a/environment.yml b/environment.yml index 717e7bf4..85937eba 100644 --- a/environment.yml +++ b/environment.yml @@ -6,11 +6,9 @@ dependencies: - python>=3.8 - compilers - boost[version='>=1.63'] - - z5py + - z5py[version='>=3.0.1'] - nlohmann_json - - xsimd[version='>=8,<9'] - cmake - - xtensor[version='>=0.24,<0.25'] - libtiff - pybind11[version='>=2.10.0'] - dcmtk[version='>=3.6.9'] diff --git a/src/nyx/omezarr.h b/src/nyx/omezarr.h index 5c02f737..d3a72553 100644 --- a/src/nyx/omezarr.h +++ b/src/nyx/omezarr.h @@ -5,16 +5,19 @@ #include #include "abs_tile_loader.h" #include "nlohmann/json.hpp" -#include "xtensor/containers/xarray.hpp" // factory functions to create files, groups and datasets #include "z5/factory.hxx" // handles for z5 filesystem objects #include "z5/filesystem/handle.hxx" -// io for xtensor multi-arrays -#include "z5/multiarray/xtensor_access.hxx" +// z5 multiarray API (ArrayView-based, no xtensor) +#include "z5/multiarray/array_view.hxx" +#include "z5/multiarray/array_access.hxx" +// z5 types +#include "z5/types/types.hxx" // attribute functionality #include "z5/attributes.hxx" + /// @brief Tile Loader for OMEZarr /// @tparam DataType AbstractView's internal type template @@ -125,25 +128,35 @@ class NyxusOmeZarrLoader : public AbstractTileLoader } template - void loadTile(std::shared_ptr> &dest, size_t pixel_row_index, size_t pixel_col_index, size_t pixel_layer_index){ - std::vector datasets; + void loadTile(std::shared_ptr> &dest, size_t pixel_row_index, + size_t pixel_col_index, size_t pixel_layer_index) { auto ds = z5::openDataset(*zarr_ptr_, ds_name_); + size_t data_height = tile_height_, data_width = tile_width_; - if (pixel_row_index + data_height > full_height_) {data_height = full_height_ - pixel_row_index;} - if (pixel_col_index + data_width > full_width_) {data_width = full_width_ - pixel_col_index;} - - typename xt::xarray::shape_type shape = {1,1,1,data_height,data_width }; - z5::types::ShapeType offset = { 0,0,pixel_layer_index, pixel_row_index, pixel_col_index }; - xt::xarray array(shape); - z5::multiarray::readSubarray(ds, array, offset.begin()); - std::vector tmp = std::vector (array.begin(), array.end()); + if (pixel_row_index + data_height > full_height_) { + data_height = full_height_ - pixel_row_index; + } + if (pixel_col_index + data_width > full_width_) { + data_width = full_width_ - pixel_col_index; + } + // Create a buffer to hold the read data + std::vector buffer(data_height * data_width); - for (size_t k=0;kbegin()+k*tile_width_); + // Create an ArrayView into the buffer (z5 3.0.1 uses ArrayView instead of xtensor) + z5::types::ShapeType shape = {1, 1, 1, data_height, data_width}; + auto view = z5::multiarray::makeView(buffer.data(), shape); + z5::types::ShapeType offset = {0, 0, pixel_layer_index, pixel_row_index, pixel_col_index}; + + // Read subarray from z5 dataset + z5::multiarray::readSubarray(*ds, view, offset.begin()); + + // Copy from buffer to destination tile, handling partial tiles + for (size_t k = 0; k < data_height; ++k) { + std::copy(buffer.begin() + k * data_width, + buffer.begin() + (k + 1) * data_width, + dest->begin() + k * tile_width_); } - //*dest = std::vector (array.begin(), array.end()); } /// @brief Tiff file height @@ -193,4 +206,4 @@ class NyxusOmeZarrLoader : public AbstractTileLoader std::unique_ptr zarr_ptr_; std::string ds_name_; }; -#endif //OMEZARR_SUPPORT \ No newline at end of file +#endif //OMEZARR_SUPPORT diff --git a/src/nyx/raw_omezarr.h b/src/nyx/raw_omezarr.h index 9430a9de..4cb36b22 100644 --- a/src/nyx/raw_omezarr.h +++ b/src/nyx/raw_omezarr.h @@ -4,14 +4,16 @@ #include #include "nlohmann/json.hpp" -#include "xtensor/containers/xarray.hpp" // factory functions to create files, groups and datasets #include "z5/factory.hxx" // handles for z5 filesystem objects #include "z5/filesystem/handle.hxx" -// io for xtensor multi-arrays -#include "z5/multiarray/xtensor_access.hxx" +// z5 multiarray API (ArrayView-based, no xtensor) +#include "z5/multiarray/array_view.hxx" +#include "z5/multiarray/array_access.hxx" +// z5 types +#include "z5/types/types.hxx" // attribute functionality #include "z5/attributes.hxx" @@ -129,34 +131,37 @@ class RawOmezarrLoader: public RawFormatLoader } template - void loadTile (size_t pixel_row_index, size_t pixel_col_index, size_t pixel_layer_index) + void loadTile(size_t pixel_row_index, size_t pixel_col_index, size_t pixel_layer_index) { - std::vector datasets; auto ds = z5::openDataset(*zarr_ptr_, ds_name_); + size_t data_height = tile_height_, data_width = tile_width_; - - if (pixel_row_index + data_height > full_height_) - { - data_height = full_height_ - pixel_row_index; + if (pixel_row_index + data_height > full_height_) { + data_height = full_height_ - pixel_row_index; } - if (pixel_col_index + data_width > full_width_) - { - data_width = full_width_ - pixel_col_index; + if (pixel_col_index + data_width > full_width_) { + data_width = full_width_ - pixel_col_index; } - typename xt::xarray::shape_type shape = { 1,1,1,data_height,data_width }; - z5::types::ShapeType offset = { 0,0,pixel_layer_index, pixel_row_index, pixel_col_index }; - xt::xarray array(shape); - z5::multiarray::readSubarray(ds, array, offset.begin()); - std::vector tmp = std::vector (array.begin(), array.end()); - + // Create a buffer to hold the read data + std::vector buffer(data_height * data_width); + + // Create an ArrayView into the buffer (z5 3.0.1 uses ArrayView instead of xtensor) + z5::types::ShapeType shape = {1, 1, 1, data_height, data_width}; + auto view = z5::multiarray::makeView(buffer.data(), shape); + z5::types::ShapeType offset = {0, 0, pixel_layer_index, pixel_row_index, pixel_col_index}; + + // Read subarray from z5 dataset + z5::multiarray::readSubarray(*ds, view, offset.begin()); + // zero-fill the buffer foreseeing its partial filling at incomplete (tail) tiles - std::fill (dest.begin(), dest.end(), 0); + std::fill(dest.begin(), dest.end(), 0); - // save this chunk of z5 tile data in the buffer - for (size_t k = 0; k < data_height; ++k) - { - std::copy(tmp.begin() + k * data_width, tmp.begin() + (k + 1) * data_width, dest.begin() + k * tile_width_); + // Copy from buffer to destination tile, handling partial tiles and type conversion + for (size_t k = 0; k < data_height; ++k) { + for (size_t j = 0; j < data_width; ++j) { + dest[k * tile_width_ + j] = static_cast(buffer[k * data_width + j]); + } } } @@ -209,4 +214,4 @@ class RawOmezarrLoader: public RawFormatLoader std::vector dest; }; -#endif //OMEZARR_SUPPORT \ No newline at end of file +#endif //OMEZARR_SUPPORT From f1788478b3ec72fe2fed8af54a67834b1ed9f60d Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Wed, 1 Jul 2026 12:46:55 -0400 Subject: [PATCH 2/6] ci(prereqs): build z5 3.0.1 from source, drop xtensor stack Update the wheel-build prereq scripts for the z5 3.0.1 upgrade: - Remove the xtl / xtensor / xsimd source builds (no longer needed). - Bump z5 from 2.0.20 to 3.0.1. - Install libdeflate before the z5 build so z5 3.0.1's find_package(libdeflate CONFIG REQUIRED) resolves (libdeflate was already built later in the script; it is moved up and kept unconditional since libtiff also depends on it). - validate_prereqs.sh: drop the xtensor header check, add checks for z5 multiarray/array_access.hxx and the libdeflate CMake config. --- ci-utils/install_prereq_linux.sh | 68 ++++++++++---------------------- ci-utils/install_prereq_win.bat | 67 ++++++++++--------------------- ci-utils/validate_prereqs.sh | 7 +++- 3 files changed, 45 insertions(+), 97 deletions(-) diff --git a/ci-utils/install_prereq_linux.sh b/ci-utils/install_prereq_linux.sh index 74a41817..9b1782a4 100755 --- a/ci-utils/install_prereq_linux.sh +++ b/ci-utils/install_prereq_linux.sh @@ -61,9 +61,21 @@ unzip zlib131.zip cd zlib-1.3.1 mkdir build_man cd build_man -cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ .. -cmake --build . -cmake --build . --target install +cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ .. +cmake --build . +cmake --build . --target install +cd ../../ + +# libdeflate is a base dependency (used by libtiff) and is also the gzip/zlib +# backend for z5 3.0.1 (find_package(libdeflate CONFIG REQUIRED)), so install it +# before the z5 build below. +curl -L https://github.com/ebiggers/libdeflate/archive/refs/tags/v1.19.zip -o v1.19.zip +unzip v1.19.zip +cd libdeflate-1.19 +mkdir build_man +cd build_man +cmake -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ -DCMAKE_PREFIX_PATH=../../"$LOCAL_INSTALL_DIR"/ .. +make install -j4 cd ../../ if [[ $BUILD_BOOST_DEP -eq 1 ]]; then @@ -93,48 +105,18 @@ if [[ $BUILD_Z5_DEP -eq 1 ]]; then cmake --build . --target install cd ../../ - curl -L https://github.com/xtensor-stack/xtl/archive/refs/tags/0.8.0.zip -o 0.8.0.zip - unzip 0.8.0.zip - cd xtl-0.8.0 - mkdir build_man - cd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ .. - cmake --build . - cmake --build . --target install - cd ../../ - - curl -L https://github.com/xtensor-stack/xtensor/archive/refs/tags/0.26.0.zip -o 0.26.0.zip - unzip 0.26.0.zip - cd xtensor-0.26.0 - mkdir build_man - cd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ .. - cmake --build . - cmake --build . --target install - cd ../../ - - curl -L https://github.com/xtensor-stack/xsimd/archive/refs/tags/13.2.0.zip -o 13.2.0.zip - unzip 13.2.0.zip - cd xsimd-13.2.0 - mkdir build_man - cd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ .. - cmake --build . - cmake --build . --target install - cd ../../ - curl -L https://github.com/nlohmann/json/archive/refs/tags/v3.11.2.zip -o v3.11.2.zip - unzip v3.11.2.zip + unzip v3.11.2.zip cd json-3.11.2 mkdir build_man cd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ .. + cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ .. make install/fast cd ../../ - curl -L https://github.com/constantinpape/z5/archive/refs/tags/2.0.20.zip -o 2.0.20.zip - unzip 2.0.20.zip - cd z5-2.0.20 + curl -L https://github.com/constantinpape/z5/archive/refs/tags/3.0.1.zip -o 3.0.1.zip + unzip 3.0.1.zip + cd z5-3.0.1 mkdir build_man cd build_man cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ -DCMAKE_PREFIX_PATH=../../"$LOCAL_INSTALL_DIR"/ -DWITH_BLOSC=ON -DBUILD_Z5PY=OFF .. @@ -172,16 +154,6 @@ if [[ $BULD_DCMTK_DEP -eq 1 ]]; then cd ../../ fi -curl -L https://github.com/ebiggers/libdeflate/archive/refs/tags/v1.19.zip -o v1.19.zip -unzip v1.19.zip -cd libdeflate-1.19 -mkdir build_man -cd build_man -cmake -DCMAKE_INSTALL_PREFIX=../../"$LOCAL_INSTALL_DIR"/ -DCMAKE_PREFIX_PATH=../../"$LOCAL_INSTALL_DIR"/ .. -make install -j4 -cd ../../ - - for i in {1..5} do curl -L https://download.osgeo.org/libtiff/tiff-4.7.0.zip -o tiff-4.7.0.zip diff --git a/ci-utils/install_prereq_win.bat b/ci-utils/install_prereq_win.bat index aefed80a..d17b8b43 100644 --- a/ci-utils/install_prereq_win.bat +++ b/ci-utils/install_prereq_win.bat @@ -40,8 +40,21 @@ tar -xvf zlib131.zip pushd zlib-1.3.1 mkdir build_man pushd build_man -cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../local_install/ .. -cmake --build . --config Release --target install --parallel 4 +cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../local_install/ .. +cmake --build . --config Release --target install --parallel 4 +popd +popd + +rem libdeflate is a base dependency (used by libtiff) and is also the gzip/zlib +rem backend for z5 3.0.1 (find_package(libdeflate CONFIG REQUIRED)), so install it +rem before the z5 build below. +curl -L https://github.com/ebiggers/libdeflate/archive/refs/tags/v1.19.zip -o v1.19.zip +tar -xf v1.19.zip +pushd libdeflate-1.19 +mkdir build_man +pushd build_man +cmake -DCMAKE_INSTALL_PREFIX=../../local_install/ -DCMAKE_PREFIX_PATH=%ABS_INSTALL% .. +cmake --build . --config Release --target install --parallel 4 popd popd @@ -71,49 +84,19 @@ if "%BUILD_Z5_DEP%" == "1" ( popd popd - curl -L https://github.com/xtensor-stack/xtl/archive/refs/tags/0.8.0.zip -o 0.8.0.zip - tar -xf 0.8.0.zip - pushd xtl-0.8.0 - mkdir build_man - pushd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../local_install/ .. - cmake --build . --config Release --target install - popd - popd - - curl -L https://github.com/xtensor-stack/xtensor/archive/refs/tags/0.26.0.zip -o 0.26.0.zip - tar -xf 0.26.0.zip - pushd xtensor-0.26.0 - mkdir build_man - pushd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../local_install/ .. - cmake --build . --config Release --target install - popd - popd - - curl -L https://github.com/xtensor-stack/xsimd/archive/refs/tags/13.2.0.zip -o 13.2.0.zip - tar -xf 13.2.0.zip - pushd xsimd-13.2.0 - mkdir build_man - pushd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../local_install/ .. - cmake --build . --config Release --target install - popd - popd - curl -L https://github.com/nlohmann/json/archive/refs/tags/v3.11.2.zip -o v3.11.2.zip - tar -xf v3.11.2.zip + tar -xf v3.11.2.zip pushd json-3.11.2 mkdir build_man pushd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../local_install/ -DJSON_BuildTests=OFF .. + cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../local_install/ -DJSON_BuildTests=OFF .. cmake --build . --config Release --target install --parallel 4 popd popd - curl -L https://github.com/constantinpape/z5/archive/refs/tags/2.0.20.zip -o 2.0.20.zip - tar -xf 2.0.20.zip - pushd z5-2.0.20 + curl -L https://github.com/constantinpape/z5/archive/refs/tags/3.0.1.zip -o 3.0.1.zip + tar -xf 3.0.1.zip + pushd z5-3.0.1 mkdir build_man pushd build_man cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=../../local_install/ -DCMAKE_PREFIX_PATH=%ABS_INSTALL% -DWITH_BLOSC=ON -DBUILD_Z5PY=OFF .. @@ -163,16 +146,6 @@ if "%BUILD_DCMTK_DEP%" == "1" ( popd ) -curl -L https://github.com/ebiggers/libdeflate/archive/refs/tags/v1.19.zip -o v1.19.zip -tar -xf v1.19.zip -pushd libdeflate-1.19 -mkdir build_man -pushd build_man -cmake -DCMAKE_INSTALL_PREFIX=../../local_install/ -DCMAKE_PREFIX_PATH=%ABS_INSTALL% .. -cmake --build . --config Release --target install --parallel 4 -popd -popd - curl -L https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.0.zip -o 3.1.0.zip tar -xf 3.1.0.zip pushd libjpeg-turbo-3.1.0 diff --git a/ci-utils/validate_prereqs.sh b/ci-utils/validate_prereqs.sh index dc7a7fbb..3bd9b5f9 100755 --- a/ci-utils/validate_prereqs.sh +++ b/ci-utils/validate_prereqs.sh @@ -25,11 +25,14 @@ check() { echo "=== Validating prereq installation in $LOCAL_INSTALL ===" echo "" -echo "-- Z5 support (CMakeLists.txt: find_package ZLIB, BLOSC, Boost, nlohmann_json; find_file z5/z5.hxx) --" +echo "-- Z5 support (CMakeLists.txt: find_package ZLIB, BLOSC, nlohmann_json; find_file z5/z5.hxx) --" check "Z5 header" "$LOCAL_INSTALL/include/z5/z5.hxx" +check "Z5 array_access header" "$LOCAL_INSTALL/include/z5/multiarray/array_access.hxx" check "nlohmann_json headers" "$LOCAL_INSTALL/include/nlohmann/json.hpp" -check "xtensor headers" "$LOCAL_INSTALL/include/xtensor/containers/xtensor.hpp" check "blosc header" "$LOCAL_INSTALL/include/blosc.h" +# z5 3.0.1 uses libdeflate as its gzip/zlib backend (find_package(libdeflate CONFIG REQUIRED)) +check "libdeflate CMake config" "$LOCAL_INSTALL/lib/cmake/libdeflate/libdeflate-config.cmake" \ + "$LOCAL_INSTALL/lib64/cmake/libdeflate/libdeflate-config.cmake" echo "" echo "-- Arrow support (CMakeLists.txt: find_package Arrow, Parquet) --" From 922425b2f3e7fceefa6ec7314c3feb01690a5684 Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Wed, 1 Jul 2026 12:47:08 -0400 Subject: [PATCH 3/6] ci: bump test jobs to Python 3.11 for z5py 3.0.1 z5py 3.0.x ships no Python 3.10 build (only 3.11-3.14 on all platforms), so the 3.10-pinned conda environment could not solve z5py >=3.0.1. Bump the ubuntu, mac and windows test jobs to 3.11. --- .github/workflows/build_and_test_mac.yml | 4 ++-- .github/workflows/build_and_test_ubuntu.yml | 4 ++-- .github/workflows/build_and_test_windows.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_and_test_mac.yml b/.github/workflows/build_and_test_mac.yml index 2720bba5..a5a6bb60 100644 --- a/.github/workflows/build_and_test_mac.yml +++ b/.github/workflows/build_and_test_mac.yml @@ -30,7 +30,7 @@ jobs: with: activate-environment: anaconda-client-env miniconda-version: "latest" - python-version: "3.10" + python-version: "3.11" channels: conda-forge channel-priority: strict auto-activate-base: false @@ -73,7 +73,7 @@ jobs: with: activate-environment: anaconda-client-env miniconda-version: "latest" - python-version: "3.10" + python-version: "3.11" channels: conda-forge channel-priority: strict auto-activate-base: false diff --git a/.github/workflows/build_and_test_ubuntu.yml b/.github/workflows/build_and_test_ubuntu.yml index cc9b0ff9..e54db4cb 100644 --- a/.github/workflows/build_and_test_ubuntu.yml +++ b/.github/workflows/build_and_test_ubuntu.yml @@ -31,7 +31,7 @@ jobs: with: activate-environment: anaconda-client-env miniconda-version: "latest" - python-version: "3.10" + python-version: "3.11" channels: conda-forge auto-activate-base: false @@ -67,7 +67,7 @@ jobs: with: activate-environment: anaconda-client-env miniconda-version: "latest" - python-version: "3.10" + python-version: "3.11" channels: conda-forge auto-activate-base: false diff --git a/.github/workflows/build_and_test_windows.yml b/.github/workflows/build_and_test_windows.yml index 48f51872..39c67228 100644 --- a/.github/workflows/build_and_test_windows.yml +++ b/.github/workflows/build_and_test_windows.yml @@ -31,7 +31,7 @@ jobs: with: activate-environment: anaconda-client-env miniconda-version: "latest" - python-version: "3.10" + python-version: "3.11" channels: conda-forge auto-activate-base: false @@ -71,7 +71,7 @@ jobs: with: activate-environment: anaconda-client-env miniconda-version: "latest" - python-version: "3.10" + python-version: "3.11" channels: conda-forge auto-activate-base: false From 43a584f6ac4cadb963c8fcc02f6197b5aae6724e Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Wed, 1 Jul 2026 12:48:03 -0400 Subject: [PATCH 4/6] build(arrow): require Boost for Arrow instead of Z5 z5 3.0.1 no longer needs Boost, but Apache Arrow does. Move the Boost lookup from the Z5 block to the Arrow block. Keep it best-effort: find_package(Boost QUIET) adds the include dirs when available, and otherwise the build falls back to Boost headers under $NYXUS_DEP_DIR/include (some prereq layouts ship Boost headers-only with no CMake config). Arrow is not disabled on a Boost config-mode miss, so headers-only Boost still builds. --- CMakeLists.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d685f07a..71318bc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,15 +382,30 @@ if(USE_ARROW) endif() find_package(Parquet) - if (NOT Parquet_FOUND) + if (NOT Parquet_FOUND) message(WARNING "The Parquet library was not found. The build will continue without Arrow support.") set(USE_ARROW OFF) endif() + # Building Arrow support pulls in Boost headers. Try to locate Boost so we can + # add its include dirs, but do NOT gate Arrow on find_package(Boost) succeeding: + # some prereq layouts ship Boost as headers-only under the common include prefix + # (no CMake config / no Boost_INCLUDE_DIR), which is enough to build against. + find_package(Boost QUIET) + if(NOT Boost_FOUND) + message(STATUS "Boost was not found via find_package; relying on Boost headers on the include path for Arrow support.") + endif() + endif() if(USE_ARROW) add_definitions(-DUSE_ARROW) + if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + elseif(DEFINED ENV{NYXUS_DEP_DIR}) + # headers-only Boost shipped under the dependency prefix (no CMake config) + include_directories("$ENV{NYXUS_DEP_DIR}/include") + endif() list(APPEND Nyxus_LIBRARIES arrow_shared) list(APPEND Nyxus_LIBRARIES parquet_shared) endif() From 61e7709a440fd7e7192debe4908dd6b6e94fc1ab Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Wed, 1 Jul 2026 12:49:05 -0400 Subject: [PATCH 5/6] test(omezarr): add OME-Zarr reader unit tests Add GTest coverage for both z5-based OME-Zarr readers (NyxusOmeZarrLoader and RawOmezarrLoader): geometry, exact pixel values, full-image checksums, and multi-tile / partial-tile reads. Ships two small bfio-generated zarr-v2 datasets under tests/data/omezarr (512x512 single-tile and 1500x1200 2x2 tile grid with partial edges); see the datasets' README for regeneration. Tests are guarded by OMEZARR_SUPPORT so non-Z5 builds are unaffected. --- tests/CMakeLists.txt | 1 + tests/data/omezarr/README.md | 54 +++++ tests/data/omezarr/multi.ome.zarr/.zattrs | 16 ++ tests/data/omezarr/multi.ome.zarr/.zgroup | 3 + tests/data/omezarr/multi.ome.zarr/.zmetadata | 1 + tests/data/omezarr/multi.ome.zarr/0/.zarray | 29 +++ tests/data/omezarr/multi.ome.zarr/0/.zattrs | 1 + tests/data/omezarr/multi.ome.zarr/0/0.0.0.0.0 | Bin 0 -> 43520 bytes tests/data/omezarr/multi.ome.zarr/0/0.0.0.0.1 | Bin 0 -> 33200 bytes tests/data/omezarr/multi.ome.zarr/0/0.0.0.1.0 | Bin 0 -> 21213 bytes tests/data/omezarr/multi.ome.zarr/0/0.0.0.1.1 | Bin 0 -> 16419 bytes .../multi.ome.zarr/OME/METADATA.ome.xml | 9 + tests/data/omezarr/test.ome.zarr/.zattrs | 16 ++ tests/data/omezarr/test.ome.zarr/.zgroup | 3 + tests/data/omezarr/test.ome.zarr/.zmetadata | 1 + tests/data/omezarr/test.ome.zarr/0/.zarray | 29 +++ tests/data/omezarr/test.ome.zarr/0/.zattrs | 1 + tests/data/omezarr/test.ome.zarr/0/0.0.0.0.0 | Bin 0 -> 16224 bytes .../test.ome.zarr/OME/METADATA.ome.xml | 9 + tests/test_all.cc | 32 +++ tests/test_omezarr.h | 219 ++++++++++++++++++ 21 files changed, 424 insertions(+) create mode 100644 tests/data/omezarr/README.md create mode 100644 tests/data/omezarr/multi.ome.zarr/.zattrs create mode 100644 tests/data/omezarr/multi.ome.zarr/.zgroup create mode 100644 tests/data/omezarr/multi.ome.zarr/.zmetadata create mode 100644 tests/data/omezarr/multi.ome.zarr/0/.zarray create mode 100644 tests/data/omezarr/multi.ome.zarr/0/.zattrs create mode 100644 tests/data/omezarr/multi.ome.zarr/0/0.0.0.0.0 create mode 100644 tests/data/omezarr/multi.ome.zarr/0/0.0.0.0.1 create mode 100644 tests/data/omezarr/multi.ome.zarr/0/0.0.0.1.0 create mode 100644 tests/data/omezarr/multi.ome.zarr/0/0.0.0.1.1 create mode 100644 tests/data/omezarr/multi.ome.zarr/OME/METADATA.ome.xml create mode 100644 tests/data/omezarr/test.ome.zarr/.zattrs create mode 100644 tests/data/omezarr/test.ome.zarr/.zgroup create mode 100644 tests/data/omezarr/test.ome.zarr/.zmetadata create mode 100644 tests/data/omezarr/test.ome.zarr/0/.zarray create mode 100644 tests/data/omezarr/test.ome.zarr/0/.zattrs create mode 100644 tests/data/omezarr/test.ome.zarr/0/0.0.0.0.0 create mode 100644 tests/data/omezarr/test.ome.zarr/OME/METADATA.ome.xml create mode 100644 tests/test_omezarr.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2dbb3e79..65e3eaa5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,7 @@ set(TEST_SRC test_gabor.h test_initialization.h test_3d_nifti.h + test_omezarr.h ${TEST_SOURCE_FILES} ) add_executable(runAllTests ${TEST_SRC}) diff --git a/tests/data/omezarr/README.md b/tests/data/omezarr/README.md new file mode 100644 index 00000000..c3bcac8d --- /dev/null +++ b/tests/data/omezarr/README.md @@ -0,0 +1,54 @@ +# OME-Zarr test datasets + +These datasets are consumed by `tests/test_omezarr.h`, which exercises the two +z5-based OME-Zarr readers: + +- `NyxusOmeZarrLoader` (`src/nyx/omezarr.h`) — the Hedgehog tile-loader +- `RawOmezarrLoader` (`src/nyx/raw_omezarr.h`) — the raw-format loader + +Both readers expect a **zarr v2** store laid out as OME-NGFF: a root group whose +`.zattrs` carries `multiscales[0].datasets[0].path`, pointing at a 5D +`(T, C, Z, Y, X)` array. The loaders read `shape[2..4]` as depth/height/width +and `chunks[2..4]` as the tile sizes. + +## Datasets + +| Store | Size (HxW) | dtype | Chunk | Tile grid | Pixel value | +|------------------|-----------:|--------|-----------|-----------|------------------------| +| `test.ome.zarr` | 512x512 | uint16 | 1024x1024 | 1x1 | `(row + col) % 65536` | +| `multi.ome.zarr` | 1500x1200 | uint16 | 1024x1024 | 2x2* | `(row*7 + col*3) % 65536` | + +\* `multi.ome.zarr` has partial edge tiles (1500 = 1024 + 476, 1200 = 1024 + 176), +so it also exercises the loaders' partial-tile clipping path. + +Deterministic checksums asserted by the tests: + +- `test.ome.zarr` sum of all pixels = `133955584` +- `multi.ome.zarr` sum of all pixels = `12681000000` + +## Regeneration + +The datasets were generated with [bfio](https://pypi.org/project/bfio/) (writes +zarr v2 OME-Zarr by default): + +```python +import numpy as np +from bfio import BioWriter + +# test.ome.zarr +H, W = 512, 512 +Y, X = np.meshgrid(np.arange(H), np.arange(W), indexing='ij') +img = ((Y.astype(np.uint32) + X.astype(np.uint32)) % 65536).astype(np.uint16) +with BioWriter("test.ome.zarr", X=W, Y=H, Z=1, C=1, T=1, dtype=np.uint16) as bw: + bw[:] = img[..., np.newaxis, np.newaxis, np.newaxis] + +# multi.ome.zarr +H, W = 1500, 1200 +Y, X = np.meshgrid(np.arange(H), np.arange(W), indexing='ij') +img = ((Y.astype(np.uint32) * 7 + X.astype(np.uint32) * 3) % 65536).astype(np.uint16) +with BioWriter("multi.ome.zarr", X=W, Y=H, Z=1, C=1, T=1, dtype=np.uint16) as bw: + bw[:] = img[..., np.newaxis, np.newaxis, np.newaxis] +``` + +bfio's default tile size (1024) determines the chunk size; the image dimensions +above were chosen so `multi.ome.zarr` produces a 2x2 grid with partial edges. diff --git a/tests/data/omezarr/multi.ome.zarr/.zattrs b/tests/data/omezarr/multi.ome.zarr/.zattrs new file mode 100644 index 00000000..f00e24a2 --- /dev/null +++ b/tests/data/omezarr/multi.ome.zarr/.zattrs @@ -0,0 +1,16 @@ +{ + "multiscales": [ + { + "version": "0.1", + "name": "multi.ome.zarr", + "datasets": [ + { + "path": "0" + } + ], + "metadata": { + "method": "mean" + } + } + ] +} \ No newline at end of file diff --git a/tests/data/omezarr/multi.ome.zarr/.zgroup b/tests/data/omezarr/multi.ome.zarr/.zgroup new file mode 100644 index 00000000..cab13da6 --- /dev/null +++ b/tests/data/omezarr/multi.ome.zarr/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format": 2 +} \ No newline at end of file diff --git a/tests/data/omezarr/multi.ome.zarr/.zmetadata b/tests/data/omezarr/multi.ome.zarr/.zmetadata new file mode 100644 index 00000000..3b3434d2 --- /dev/null +++ b/tests/data/omezarr/multi.ome.zarr/.zmetadata @@ -0,0 +1 @@ +{"metadata": {".zgroup": {"zarr_format": 2}, ".zattrs": {"multiscales": [{"version": "0.1", "name": "multi.ome.zarr", "datasets": [{"path": "0"}], "metadata": {"method": "mean"}}]}, "0/.zattrs": {}, "0/.zarray": {"shape": [1, 1, 1, 1500, 1200], "chunks": [1, 1, 1, 1024, 1024], "dtype": "wB#P)i43iVXl=?cuZVpRyeR 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 gSzn*)87j+@bsEc; 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 X;5q>r)(P|!1tMaww6v6QoxuMAL%NK_ literal 0 HcmV?d00001 diff --git a/tests/data/omezarr/multi.ome.zarr/0/0.0.0.1.0 b/tests/data/omezarr/multi.ome.zarr/0/0.0.0.1.0 new file mode 100644 index 0000000000000000000000000000000000000000..dff49e0ef2b063dedb98ce26a6ac63efa606f3c3 GIT binary patch literal 21213 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 literal 0 HcmV?d00001 diff --git a/tests/data/omezarr/multi.ome.zarr/0/0.0.0.1.1 b/tests/data/omezarr/multi.ome.zarr/0/0.0.0.1.1 new file mode 100644 index 0000000000000000000000000000000000000000..738b9aab839a8e6511524cbc226cd2be5563dfb8 GIT binary patch literal 16419 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 hxWD=R2CrFgOE3T^(All={c!W!jnTvJ + + + + + + + + diff --git a/tests/data/omezarr/test.ome.zarr/.zattrs b/tests/data/omezarr/test.ome.zarr/.zattrs new file mode 100644 index 00000000..6b0836a2 --- /dev/null +++ b/tests/data/omezarr/test.ome.zarr/.zattrs @@ -0,0 +1,16 @@ +{ + "multiscales": [ + { + "version": "0.1", + "name": "test.ome.zarr", + "datasets": [ + { + "path": "0" + } + ], + "metadata": { + "method": "mean" + } + } + ] +} \ No newline at end of file diff --git a/tests/data/omezarr/test.ome.zarr/.zgroup b/tests/data/omezarr/test.ome.zarr/.zgroup new file mode 100644 index 00000000..cab13da6 --- /dev/null +++ b/tests/data/omezarr/test.ome.zarr/.zgroup @@ -0,0 +1,3 @@ +{ + "zarr_format": 2 +} \ No newline at end of file diff --git a/tests/data/omezarr/test.ome.zarr/.zmetadata b/tests/data/omezarr/test.ome.zarr/.zmetadata new file mode 100644 index 00000000..b6b71b39 --- /dev/null +++ b/tests/data/omezarr/test.ome.zarr/.zmetadata @@ -0,0 +1 @@ +{"metadata": {".zgroup": {"zarr_format": 2}, ".zattrs": {"multiscales": [{"version": "0.1", "name": "test.ome.zarr", "datasets": [{"path": "0"}], "metadata": {"method": "mean"}}]}, "0/.zattrs": {}, "0/.zarray": {"shape": [1, 1, 1, 512, 512], "chunks": [1, 1, 1, 1024, 1024], "dtype": "FJ!;`}6&LzMuE^r?Oe7 z0uP9Q;eJrf22#auqRK=63gYKVaHKLs+ruCSaC3x{)nK_3409H*tN~|TA*Lpbb_efT zP@@i1sS7GkFw}=j4ItfHyiN^=8$(JH*wGZW_`-%3u(Fl-`)y!OJMnL{hovGmiP$IN zl!z-L>^eYQ5p6^Sh!`zms)%S22_lk3oDh*IqGCs=Euy7}z9K?JOct?F#0C*bB94i; zD5AW0&+a08Mf4W2UA%wqZBN`BPWUJX_BvssDp%gNLd8mz?d+>KRCTQOh*NcEmm02a zH6L|fEJkx9&Z9 z_Ui55r*FUh0RsjG1_f&dX$KE^YUtC?3=0`PVr1yEqehQ;?)ewSj(hQ?@e?Ladij-C zUwi$HHz&U}WolS>#I)%%-j1C4&aByU-i?|&Z~lUXi{6V~ykzOJ_dob>`HGdRRZTn}RfAQs4J9h3$+Px?F>%IF@_8&Ny`pviBeShfi z4@Z7Hns)4`<0nr3eCn59e>;8VtS%(45iE&s#!9WyfbG>`(L!(-_ip+L}q~?7SsI zpAZ$|5_Zj?NlCOznw+iHnU*f2ZmEJ(4jpB;U#EjUyY7%%=fG8`{~oErN~Ic);6!zt zaiIpTxKWcwapy5=QCnuGYVL75az(Sd;Dx8@6WwzB4f*iGU9Kc<44vYC%K0{{xI)M> z^g%{)g^>9bAg7HC=@6#YnU=M_N%U?RwmnsKwW4=hTbW?l&#l75WX0k^UFzXUeY|Ku zL%i`pO(PofI87`gIAV(b4&@1!z1`3}F~s(}%wFqrYVYopeEG*G7o#aIroiIUsk*1GJ*qB zV)u5pF?A%`$W_M1#8lUeJQHjCRdIqPud7#&!V8Aq`>!9^d&JUKDtXLMX zkVU*lG>cinQkL;PAMhc|S;0!n2zGAP(W{BA(mAO7tfOQRlNHNq*6}C(ie9d0=k-~lsaFA5K;ak4rd&>y+6a<$jejdF432^BqCM%Z1 z{J;@@>~(zDn8l1+hr-+#AL;iiJr?`;VReon}0Bn#dU6w%}s7uMzEhC*u2;Y zX54X?Y#m9j{?He^w=XC@8(|rm`|e~io9?pv?lGdycm4it#+IegInL1w7b)K~%$soM z54!VS9(>z7%pcJe9J#JhXWMRwNQxuEIDxx-hjw>IoSkK7MxOJ_Q)77sDNnTI`G|bl zmd|eTNkHx^9w)+kol)?6L~z!ZbVxGFppf zCOu;4TF77y*F=Uy#Hjp+?&Ny8=;bPz`hShGta9`IqqV7zHB#@RJoU1k%Q9tI<>oz0 zYg6|eIi+jm20rPg>mUWT^ngOwLGCUL!gYxg9?Tu{MdK1?v&b%c+%?sC#{rY<dXHV)lx3Bh_h79}J=90%?%%Xm-8%(3uQe~!p^J-z z@|jSRQn>Ud#%GGV6Q{={xSH-x$hlt9PEtx!mQ`-vx3xBPoss&4^@`W^2Yp_)Hf34m z=Dl8PQx6>wHQsyAvU*DAy0xbw51O*9a`XPMwW;e&)R!yX%_%+mDruBum7DjJtxY|2 zpfT6GEmXRPFYXCvNvABU+`JEMZR$EB_0Il^*L6$h*rkoKta9_-wY8~-1`6sno{G=r z?$@m-?UZGeoA + + + + + + + + diff --git a/tests/test_all.cc b/tests/test_all.cc index f099b67e..41c52864 100644 --- a/tests/test_all.cc +++ b/tests/test_all.cc @@ -27,6 +27,7 @@ #include "test_roi_blacklist.h" #include "test_image_quality.h" #include "test_3d_nifti.h" +#include "test_omezarr.h" #include "test_3d_shape.h" #include "test_3d_gldzm.h" #include "test_3d_ngldm.h" @@ -2315,6 +2316,37 @@ TEST(TEST_NYXUS, TEST_3D_NIFTY_DACC_CONSISTENCY) { } +//***** OME-Zarr i/o ***** + +#ifdef OMEZARR_SUPPORT + +TEST(TEST_NYXUS, TEST_OMEZARR_TILELOADER_GEOMETRY) { + ASSERT_NO_THROW (test_omezarr_tileloader_geometry()); +} + +TEST(TEST_NYXUS, TEST_OMEZARR_TILELOADER_CONTENT) { + ASSERT_NO_THROW (test_omezarr_tileloader_content()); +} + +TEST(TEST_NYXUS, TEST_OMEZARR_TILELOADER_MULTITILE) { + ASSERT_NO_THROW (test_omezarr_tileloader_multitile()); +} + +TEST(TEST_NYXUS, TEST_RAW_OMEZARR_GEOMETRY) { + ASSERT_NO_THROW (test_raw_omezarr_geometry()); +} + +TEST(TEST_NYXUS, TEST_RAW_OMEZARR_CONTENT) { + ASSERT_NO_THROW (test_raw_omezarr_content()); +} + +TEST(TEST_NYXUS, TEST_RAW_OMEZARR_MULTITILE) { + ASSERT_NO_THROW (test_raw_omezarr_multitile()); +} + +#endif // OMEZARR_SUPPORT + + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/tests/test_omezarr.h b/tests/test_omezarr.h new file mode 100644 index 00000000..cdd2eb12 --- /dev/null +++ b/tests/test_omezarr.h @@ -0,0 +1,219 @@ +#pragma once + +#include + +#ifdef OMEZARR_SUPPORT + +#include +#include +#include "../src/nyx/omezarr.h" +#include "../src/nyx/raw_omezarr.h" +#include "../src/nyx/helpers/fsystem.h" + +// The OME-Zarr test datasets under tests/data/omezarr are generated with bfio +// (see tests/data/omezarr/README.md). They are zarr-v2 stores with the 5D +// (T, C, Z, Y, X) layout that the nyxus z5-based loaders expect. +// +// test.ome.zarr : 512 x 512 uint16, single 1024x1024 chunk (one tile) +// pixel value = (row + col) % 65536 +// multi.ome.zarr : 1500 x 1200 uint16, 1024x1024 chunks (2x2 tile grid, +// partial edge tiles) ; value = (row*7 + col*3) % 65536 + +static inline fs::path omezarr_data_path(const char* name) +{ + fs::path p(__FILE__); + fs::path pp = p.parent_path(); + fs::path rel(std::string("/data/omezarr/") + name); + return fs::path(pp.string() + rel.make_preferred().string()); +} + +// --------------------------------------------------------------------------- +// NyxusOmeZarrLoader (AbstractTileLoader) tests +// --------------------------------------------------------------------------- + +// Geometry: dimensions and chunk/tile sizes are read correctly from metadata. +void test_omezarr_tileloader_geometry() +{ + fs::path ds = omezarr_data_path("test.ome.zarr"); + ASSERT_TRUE(fs::exists(ds)); + + auto ldr = NyxusOmeZarrLoader(1, ds.string()); + + ASSERT_EQ(ldr.fullHeight(0), 512u); + ASSERT_EQ(ldr.fullWidth(0), 512u); + ASSERT_EQ(ldr.fullDepth(0), 1u); + ASSERT_EQ(ldr.tileHeight(0), 1024u); + ASSERT_EQ(ldr.tileWidth(0), 1024u); + ASSERT_EQ(ldr.tileDepth(0), 1u); + ASSERT_EQ(ldr.numberPyramidLevels(), 1u); +} + +// Content: read the single tile and verify exact pixel values and the checksum. +void test_omezarr_tileloader_content() +{ + fs::path ds = omezarr_data_path("test.ome.zarr"); + ASSERT_TRUE(fs::exists(ds)); + + auto ldr = NyxusOmeZarrLoader(1, ds.string()); + + size_t th = ldr.tileHeight(0); + size_t tw = ldr.tileWidth(0); + auto tile = std::make_shared>(th * tw, 0u); + + ASSERT_NO_THROW(ldr.loadTileFromFile(tile, 0, 0, 0, 0)); + + const std::vector& buf = *tile; + + // pixel value = (row + col) % 65536, laid out row-major with stride tileWidth + ASSERT_EQ(buf[0 * tw + 0], 0u); // (0,0) + ASSERT_EQ(buf[0 * tw + 511], 511u); // (0,511) + ASSERT_EQ(buf[100 * tw + 50], 150u); // (100,50) + ASSERT_EQ(buf[511 * tw + 511], 1022u); // (511,511) + + // full-image checksum over the valid 512x512 region + unsigned long long total = 0; + for (size_t r = 0; r < ldr.fullHeight(0); ++r) + for (size_t c = 0; c < ldr.fullWidth(0); ++c) + total += buf[r * tw + c]; + ASSERT_EQ(total, 133955584ull); +} + +// Multi-tile: 2x2 tile grid with partial edge tiles. +void test_omezarr_tileloader_multitile() +{ + fs::path ds = omezarr_data_path("multi.ome.zarr"); + ASSERT_TRUE(fs::exists(ds)); + + auto ldr = NyxusOmeZarrLoader(1, ds.string()); + + const size_t H = ldr.fullHeight(0); + const size_t W = ldr.fullWidth(0); + const size_t th = ldr.tileHeight(0); + const size_t tw = ldr.tileWidth(0); + ASSERT_EQ(H, 1500u); + ASSERT_EQ(W, 1200u); + + const size_t nRows = (H + th - 1) / th; // 2 + const size_t nCols = (W + tw - 1) / tw; // 2 + ASSERT_EQ(nRows, 2u); + ASSERT_EQ(nCols, 2u); + + auto tile = std::make_shared>(th * tw, 0u); + + unsigned long long total = 0; + for (size_t tr = 0; tr < nRows; ++tr) + { + for (size_t tc = 0; tc < nCols; ++tc) + { + std::fill(tile->begin(), tile->end(), 0u); + ASSERT_NO_THROW(ldr.loadTileFromFile(tile, tr, tc, 0, 0)); + + const std::vector& buf = *tile; + const size_t row0 = tr * th; + const size_t col0 = tc * tw; + const size_t validH = std::min(th, H - row0); + const size_t validW = std::min(tw, W - col0); + + for (size_t r = 0; r < validH; ++r) + { + for (size_t c = 0; c < validW; ++c) + { + size_t gr = row0 + r; + size_t gc = col0 + c; + uint32_t expected = static_cast((gr * 7 + gc * 3) % 65536); + ASSERT_EQ(buf[r * tw + c], expected) + << "mismatch at global (" << gr << "," << gc << ")"; + total += buf[r * tw + c]; + } + } + } + } + ASSERT_EQ(total, 12681000000ull); +} + +// --------------------------------------------------------------------------- +// RawOmezarrLoader (RawFormatLoader) tests +// --------------------------------------------------------------------------- + +void test_raw_omezarr_geometry() +{ + fs::path ds = omezarr_data_path("test.ome.zarr"); + ASSERT_TRUE(fs::exists(ds)); + + auto ldr = RawOmezarrLoader(ds.string()); + + ASSERT_EQ(ldr.fullHeight(0), 512u); + ASSERT_EQ(ldr.fullWidth(0), 512u); + ASSERT_EQ(ldr.fullDepth(0), 1u); + ASSERT_EQ(ldr.tileHeight(0), 1024u); + ASSERT_EQ(ldr.tileWidth(0), 1024u); +} + +void test_raw_omezarr_content() +{ + fs::path ds = omezarr_data_path("test.ome.zarr"); + ASSERT_TRUE(fs::exists(ds)); + + auto ldr = RawOmezarrLoader(ds.string()); + const size_t tw = ldr.tileWidth(0); + + ASSERT_NO_THROW(ldr.loadTileFromFile(0, 0, 0, 0)); + + // get_uint32_pixel indexes into the internal tile buffer (stride tileWidth) + ASSERT_EQ(ldr.get_uint32_pixel(0 * tw + 0), 0u); + ASSERT_EQ(ldr.get_uint32_pixel(0 * tw + 511), 511u); + ASSERT_EQ(ldr.get_uint32_pixel(100 * tw + 50), 150u); + ASSERT_EQ(ldr.get_uint32_pixel(511 * tw + 511), 1022u); + + // get_dpequiv_pixel returns the same values as double + ASSERT_DOUBLE_EQ(ldr.get_dpequiv_pixel(100 * tw + 50), 150.0); + + unsigned long long total = 0; + for (size_t r = 0; r < ldr.fullHeight(0); ++r) + for (size_t c = 0; c < ldr.fullWidth(0); ++c) + total += ldr.get_uint32_pixel(r * tw + c); + ASSERT_EQ(total, 133955584ull); +} + +// Multi-tile read through the RawFormatLoader path, including partial tiles. +void test_raw_omezarr_multitile() +{ + fs::path ds = omezarr_data_path("multi.ome.zarr"); + ASSERT_TRUE(fs::exists(ds)); + + auto ldr = RawOmezarrLoader(ds.string()); + const size_t H = ldr.fullHeight(0); + const size_t W = ldr.fullWidth(0); + const size_t th = ldr.tileHeight(0); + const size_t tw = ldr.tileWidth(0); + + const size_t nRows = (H + th - 1) / th; + const size_t nCols = (W + tw - 1) / tw; + + unsigned long long total = 0; + for (size_t tr = 0; tr < nRows; ++tr) + { + for (size_t tc = 0; tc < nCols; ++tc) + { + ASSERT_NO_THROW(ldr.loadTileFromFile(tr, tc, 0, 0)); + const size_t row0 = tr * th; + const size_t col0 = tc * tw; + const size_t validH = std::min(th, H - row0); + const size_t validW = std::min(tw, W - col0); + for (size_t r = 0; r < validH; ++r) + { + for (size_t c = 0; c < validW; ++c) + { + size_t gr = row0 + r, gc = col0 + c; + uint32_t expected = static_cast((gr * 7 + gc * 3) % 65536); + ASSERT_EQ(ldr.get_uint32_pixel(r * tw + c), expected) + << "mismatch at global (" << gr << "," << gc << ")"; + total += ldr.get_uint32_pixel(r * tw + c); + } + } + } + } + ASSERT_EQ(total, 12681000000ull); +} + +#endif // OMEZARR_SUPPORT From 7ec899db7f0fc628b5d7ca21cf4fd2bc6c9ef006 Mon Sep 17 00:00:00 2001 From: Sameeul B Samee Date: Tue, 30 Jun 2026 16:16:17 -0400 Subject: [PATCH 6/6] fix: upgrade Docker build to CUDA 12.6 and Python 3.11 - Replace cudatoolkit=11.5 conda packages with cuda-version=12.6, cuda-nvcc=12.6, cuda-cudart-dev=12.6, libcufft-dev (conda-forge CUDA 12 package names that include nvcc) - Add -DCMAKE_CUDA_COMPILER=$CONDA_PREFIX/bin/nvcc so CMake finds the conda nvcc without relying on check_language(CUDA) discovery - Bump PYTHON=3.9 -> 3.11 in build_conda.sh (3.9 is EOL) - Update Dockerfile base image from nvidia/cuda:11.5.2-runtime-ubuntu20.04 to nvidia/cuda:12.6.0-runtime-ubuntu22.04 - Update hardcoded nyxus-3.9 env path in publish_docker.yml to nyxus-3.11 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish_docker.yml | 2 +- Dockerfile | 2 +- ci-utils/build_conda.sh | 3 ++- ci-utils/envs/conda_linux_gpu.txt | 6 ++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml index 44a6ad2a..9010fc04 100644 --- a/.github/workflows/publish_docker.yml +++ b/.github/workflows/publish_docker.yml @@ -39,7 +39,7 @@ jobs: - name: Copy 3rd-party libraries to staging directory working-directory: ${{github.workspace}} run: | - bash ci-utils/docker_copy_3rd_party_libs.sh ${{github.workspace}}/miniconda-for-nyxus/envs/nyxus-3.9 + bash ci-utils/docker_copy_3rd_party_libs.sh ${{github.workspace}}/miniconda-for-nyxus/envs/nyxus-3.11 - name: Set version from .version file run: | diff --git a/Dockerfile b/Dockerfile index 16a995d2..612c26ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nvidia/cuda:11.5.2-runtime-ubuntu20.04 +FROM nvidia/cuda:12.6.0-runtime-ubuntu22.04 COPY VERSION / diff --git a/ci-utils/build_conda.sh b/ci-utils/build_conda.sh index eb3e6c80..bd3466e5 100755 --- a/ci-utils/build_conda.sh +++ b/ci-utils/build_conda.sh @@ -17,7 +17,7 @@ MINICONDA=$PWD/miniconda-for-nyxus # Modify this to your preferred location for CPP_BUILD_DIR=$PWD SRC_ROOT=$1 #source dir location NYXUS_ROOT=$SRC_ROOT -PYTHON=3.9 +PYTHON=3.11 git config --global --add safe.directory $NYXUS_ROOT @@ -66,6 +66,7 @@ mkdir -p $CPP_BUILD_DIR pushd $CPP_BUILD_DIR cmake -DCMAKE_PREFIX_PATH=$CONDA_PREFIX \ -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX \ + -DCMAKE_CUDA_COMPILER=$CONDA_PREFIX/bin/nvcc \ -DBUILD_CLI=ON \ -DUSEGPU=ON \ -DALLEXTRAS=ON \ diff --git a/ci-utils/envs/conda_linux_gpu.txt b/ci-utils/envs/conda_linux_gpu.txt index 35545b83..da5841d9 100644 --- a/ci-utils/envs/conda_linux_gpu.txt +++ b/ci-utils/envs/conda_linux_gpu.txt @@ -1,2 +1,4 @@ -cudatoolkit=11.5.0 -cudatoolkit-dev=11.5.0 \ No newline at end of file +cuda-version=12.6 +cuda-nvcc=12.6 +cuda-cudart-dev=12.6 +libcufft-dev