diff --git a/.gitignore b/.gitignore index 9b22bec..cce0baa 100644 --- a/.gitignore +++ b/.gitignore @@ -567,4 +567,7 @@ scratch/ build/ build_man/ -venv/ \ No newline at end of file +venv/ +CmakeFiles +CMakeCache.txt +.devcontainer/ \ No newline at end of file diff --git a/README.md b/README.md index 45e7d73..bddf940 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ Here is an example of building and installing `Argolid` in a Python virtual envi ``` python -m virtualenv venv source venv/bin/activate -pip install cmake -git clone https://github.com/sameeul/argolid.git +pip install cmake setuptools looseversion +git clone https://github.com/polusai/argolid.git cd argolid mkdir build_deps cd build_deps -sh ../ci_utils/install_prereq_linux.sh +sh ../ci-utils/install_prereq_linux.sh # on linux prefer bash over sh cd ../ -export ARGOLID_DER_DIR=./build_deps/local_install +export ARGOLID_DEP_DIR=./build_deps/local_install python setup.py install ``` diff --git a/ci-utils/install_prereq_linux.sh b/ci-utils/install_prereq_linux.sh index a055d74..2bc3afb 100755 --- a/ci-utils/install_prereq_linux.sh +++ b/ci-utils/install_prereq_linux.sh @@ -38,11 +38,11 @@ if [[ "$OSTYPE" == "darwin"* ]]; then cd zlib-1.3.1 mkdir build_man cd build_man - cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=/usr/local .. - cmake --build . - cmake --build . --target install + cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_INSTALL_PREFIX=/usr/local .. + cmake --build . + cmake --build . --target install cd ../../ - + curl -L https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.0.zip -o 3.1.0.zip unzip 3.1.0.zip cd libjpeg-turbo-3.1.0 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2e73259 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel", "looseversion", "versioneer", "cmake"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.py b/setup.py index 78fad08..b06beee 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,14 @@ import os import re import sys -import versioneer import platform import subprocess -from distutils.version import LooseVersion +from looseversion import LooseVersion from setuptools import setup, find_packages, Extension from setuptools.command.build_ext import build_ext +import versioneer class CMakeExtension(Extension): def __init__(self, name, sourcedir=""): diff --git a/src/cpp/core/chunked_pyramid_assembler.cpp b/src/cpp/core/chunked_pyramid_assembler.cpp index 0c59f82..64f305d 100644 --- a/src/cpp/core/chunked_pyramid_assembler.cpp +++ b/src/cpp/core/chunked_pyramid_assembler.cpp @@ -66,7 +66,7 @@ ImageInfo OmeTiffCollToChunked::Assemble(const std::string& input_dir, int grid_x_max = 0, grid_y_max = 0, grid_c_max = 0; int grid_x_min = INT_MAX, grid_y_min = INT_MAX, grid_c_min = INT_MAX; std::vector image_vec; - ImageInfo whole_image; + ImageInfo whole_image {}; auto fp = std::make_unique (input_dir, pattern); auto files = fp->getFiles(); @@ -163,22 +163,34 @@ ImageInfo OmeTiffCollToChunked::Assemble(const std::string& input_dir, array).value(); tensorstore::IndexTransform<> transform = tensorstore::IdentityTransform(dest.domain()); - if(v == VisType::PCNG){ - transform = (std::move(transform) | tensorstore::Dims("z", "channel").IndexSlice({z, i._c_grid-grid_c_min}) - | tensorstore::Dims(y_dim).SizedInterval((i._y_grid-grid_y_min)*whole_image._chunk_size_y, image_height) - | tensorstore::Dims(x_dim).SizedInterval((i._x_grid-grid_x_min)*whole_image._chunk_size_x, image_width) - | tensorstore::Dims(x_dim, y_dim).Transpose({y_dim, x_dim})).value(); - - } else if (v == VisType::NG_Zarr){ - transform = (std::move(transform) | tensorstore::Dims(c_dim).SizedInterval(i._c_grid-grid_c_min, 1) - | tensorstore::Dims(z_dim).SizedInterval(z, 1) - | tensorstore::Dims(y_dim).SizedInterval((i._y_grid-grid_y_min)*whole_image._chunk_size_y, image_height) - | tensorstore::Dims(x_dim).SizedInterval((i._x_grid-grid_x_min)*whole_image._chunk_size_x, image_width)).value(); - } else if (v == VisType::Viv){ - transform = (std::move(transform) | tensorstore::Dims(c_dim).SizedInterval(i._c_grid-grid_c_min, 1) - | tensorstore::Dims(z_dim).SizedInterval(z, 1) - | tensorstore::Dims(y_dim).SizedInterval((i._y_grid-grid_y_min)*whole_image._chunk_size_y, image_height) - | tensorstore::Dims(x_dim).SizedInterval((i._x_grid-grid_x_min)*whole_image._chunk_size_x, image_width)).value(); + auto yy = (i._y_grid - grid_y_min) * whole_image._chunk_size_y; + auto xx = (i._x_grid - grid_x_min) * whole_image._chunk_size_x; + auto cc = (i._c_grid - grid_c_min); + + switch (v) { + case VisType::PCNG: + transform = + (std::move(transform) + | tensorstore::Dims("z", "channel").IndexSlice({z, cc}) + | tensorstore::Dims(y_dim).SizedInterval(yy, image_height) + | tensorstore::Dims(x_dim).SizedInterval(xx, image_width) + | tensorstore::Dims(x_dim, y_dim).Transpose({y_dim, x_dim})) + .value(); + break; + + case VisType::NG_Zarr: // same as Viv + case VisType::Viv: + transform = + (std::move(transform) + | tensorstore::Dims(c_dim).SizedInterval(cc, 1) + | tensorstore::Dims(z_dim).SizedInterval(z, 1) + | tensorstore::Dims(y_dim).SizedInterval(yy, image_height) + | tensorstore::Dims(x_dim).SizedInterval(xx, image_width)) + .value(); + break; + + default: + throw std::invalid_argument("Unsupported VisType"); } tensorstore::Write(array, dest | transform).value(); }); diff --git a/src/cpp/core/ome_tiff_to_chunked_pyramid.cpp b/src/cpp/core/ome_tiff_to_chunked_pyramid.cpp index 25eb99f..a2564cb 100644 --- a/src/cpp/core/ome_tiff_to_chunked_pyramid.cpp +++ b/src/cpp/core/ome_tiff_to_chunked_pyramid.cpp @@ -1,5 +1,6 @@ #include "ome_tiff_to_chunked_pyramid.h" #include +#include namespace fs = std::filesystem; @@ -47,7 +48,16 @@ void OmeTiffToChunkedPyramid::GenerateFromCollection( int base_level_key = 0; PLOG_INFO << "Assembling base image..."; auto whole_image =_tiff_coll_to_chunk.Assemble(collection_path, stitch_vector_file, chunked_file_dir, std::to_string(base_level_key), v, _th_pool); - int max_level = static_cast(ceil(log2(std::max({whole_image._full_image_width, whole_image._full_image_width})))); + + if (whole_image._full_image_height <= 0 || + whole_image._full_image_width <= 0) { + PLOG_WARNING << "Assembled image height and width must be positive " + << "(height=" + std::to_string(whole_image._full_image_height) + ", width=" + std::to_string(whole_image._full_image_width) + ")\n" + << "No (tiff) images images found in the collection_path : " + collection_path + " please check the path" + << "\n"; + return; + } + int max_level = static_cast(ceil(log2(std::max({whole_image._full_image_width, whole_image._full_image_height})))); int min_level = static_cast(ceil(log2(min_dim))); auto max_level_key = max_level-min_level+1+base_level_key; PLOG_INFO << "Generating image pyramids..."; diff --git a/src/cpp/utilities/utilities.cpp b/src/cpp/utilities/utilities.cpp index 64f08ea..d2e0691 100644 --- a/src/cpp/utilities/utilities.cpp +++ b/src/cpp/utilities/utilities.cpp @@ -248,12 +248,12 @@ void WriteTSZattrFilePlateImage( } void WriteVivZattrFile(const std::string& tiff_file_name, const std::string& zattr_file_loc, int min_level, int max_level){ - json scale_metadata_list = json::array(); - for(int i=min_level; i<=max_level; ++i){ - json scale_metadata; - scale_metadata["path"] = std::to_string(i); - scale_metadata_list.push_back(scale_metadata); + + for (int i = min_level; i <= max_level; ++i) { + scale_metadata_list.emplace_back(json{ + {"path", std::to_string(i)} + }); } json combined_metadata; diff --git a/src/python/argolid/pyramid_generator.py b/src/python/argolid/pyramid_generator.py index cd52e7f..17e42de 100644 --- a/src/python/argolid/pyramid_generator.py +++ b/src/python/argolid/pyramid_generator.py @@ -1,7 +1,18 @@ from pydantic import BaseModel, Field, field_validator from typing import Dict, Optional, List +from enum import IntEnum from .libargolid import OmeTiffToChunkedPyramidCPP, VisType, DSType, PyramidViewCPP +class LogLevel(IntEnum): + NONE = 0 + FATAL = 1 + ERROR = 2 + WARNING = 3 + INFO = 4 + DEBUG = 5 + VERBOSE = 6 + + class Downsample(BaseModel): channel_name: str method: str @@ -10,7 +21,7 @@ class Downsample(BaseModel): def check_method_config(cls, v): if v not in {"mean", "mode_max", "mode_min"}: raise ValueError(f'Value must be "mean", mode_max or "mode_min".') - return v + return v class PlateVisualizationMetadata(BaseModel): output_type: str @@ -32,16 +43,17 @@ def check_output_type_config(cls, v): return v class PyramidGenerartor: - def __init__(self, log_level = None) -> None: + def __init__(self, log_level = LogLevel.NONE) -> None: self._pyr_generator = OmeTiffToChunkedPyramidCPP() self.vis_types_dict ={ "NG_Zarr" : VisType.NG_Zarr, "PCNG" : VisType.PCNG, "Viv" : VisType.Viv} self.ds_types_dict = {"mean" : DSType.Mean, "mode_max" : DSType.Mode_Max, "mode_min" : DSType.Mode_Min} + self._pyr_generator.SetLogLevel(log_level) def generate_from_single_image(self, input_file, output_dir, min_dim, vis_type, ds_dict = {}): channel_ds_dict = {} - for c, ds in ds_dict: - channel_ds_dict[c] = self.ds_types_dict[ds] + for c in ds_dict: + channel_ds_dict[c] = self.ds_types_dict[ds_dict[c]] self._pyr_generator.GenerateFromSingleFile(input_file, output_dir, min_dim, self.vis_types_dict[vis_type], channel_ds_dict) def generate_from_image_collection(self, collection_path, pattern , image_name, output_dir, min_dim, vis_type, ds_dict = {}):