From 5234b5509d960e899c0fbcc9d5fb3267b7cf5ece Mon Sep 17 00:00:00 2001 From: David Northover Date: Fri, 17 Oct 2025 13:37:56 -0400 Subject: [PATCH 1/2] Efficient Per-Z Intermediates - Switched to raw byte saving rather than TiffFiles (smaller, faster). - Appending to single-Z as each entry can be written (and read) sequentially, with file locking. - Temp Files now cleaned up as they are no longer needed. - Indicies now written as squeeze type, and types are passed in the intermediate. - Intermediate folders now PID-tagged and cleaned up when done, to avoid issues with consecutive runs. --- .coverage | Bin 126976 -> 122880 bytes python/ouroboros/helpers/files.py | 69 ++++++++-------- python/ouroboros/helpers/parse.py | 8 +- python/ouroboros/helpers/shapes.py | 5 -- python/ouroboros/helpers/slice.py | 2 +- .../pipeline/backproject_pipeline.py | 78 +++++++++--------- python/poetry.lock | 30 ++++--- python/pyproject.toml | 1 + python/test/helpers/test_files.py | 44 ++++++---- 9 files changed, 122 insertions(+), 115 deletions(-) diff --git a/.coverage b/.coverage index c6fe2e99bc99b90e8286f58b9e493d7b82d6c40e..6515a9c6a7a00f373c795e14f8b57490d7d8199f 100644 GIT binary patch literal 122880 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 literal 126976 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 diff --git a/python/ouroboros/helpers/files.py b/python/ouroboros/helpers/files.py index a8cb17e..2ae0de5 100644 --- a/python/ouroboros/helpers/files.py +++ b/python/ouroboros/helpers/files.py @@ -1,4 +1,5 @@ from functools import partial +from io import BytesIO from multiprocessing.pool import ThreadPool import os @@ -6,7 +7,6 @@ from numpy.typing import ArrayLike from pathlib import Path import cv2 -from tifffile import TiffWriter, TiffFile import time from .shapes import DataShape @@ -171,60 +171,60 @@ def generate_tiff_write(write_func: callable, compression: str | None, micron_re **kwargs) -def write_small_intermediate(file_path: os.PathLike, *series): - with TiffWriter(file_path, append=True) as tif: - for entry in series: - tif.write(entry, dtype=entry.dtype) +def write_raw_intermediate(target: BytesIO, *series): + for entry in series: + target.write(entry) + return target.tell() def ravel_map_2d(index, source_rows, target_rows, offset): return np.add.reduce(np.add(np.divmod(index, source_rows), offset) * ((target_rows, ), (np.uint32(1), ))) -def load_z_intermediate(path: Path, offset: int = 0): - with TiffFile(path) as tif: - meta = tif.series[offset].asarray() - source_rows, target_rows, offset_rows, offset_columns = meta - return (ravel_map_2d(tif.series[offset + 1].asarray(), - source_rows, target_rows, - ((offset_rows, ), (offset_columns, ))), - tif.series[offset + 2].asarray(), - tif.series[offset + 3].asarray()) +def load_raw_file_intermediate(handle: BytesIO): + meta = np.fromfile(handle, np.uint32, 6) + source_rows, target_rows, offset_rows, offset_columns, channel_count, data_length = meta + t_index, t_value, t_weight = [np.dtype(code.decode()).type for code in np.fromfile(handle, 'S8', 3)] + return (ravel_map_2d(np.fromfile(handle, t_index, data_length), + source_rows, target_rows, + ((offset_rows, ), (offset_columns, ))), + np.fromfile(handle, t_value, data_length * channel_count).reshape(-1, data_length), + np.fromfile(handle, t_weight, data_length)) def increment_volume(path: Path, vol: np.ndarray, offset: int = 0, cleanup=False): - indicies, values, weights = load_z_intermediate(path, offset) - for i in range(0, vol.shape[0] - 1): - np.add.at(vol[i], indicies, np.atleast_2d(values)[i]) - np.add.at(vol[-1], indicies, weights) + if isinstance(path, Path): + with open(path, "rb") as handle: + end = os.fstat(handle.fileno()).st_size + handle.seek(offset) + while handle.tell() < end: + indicies, values, weights = load_raw_file_intermediate(handle) + for i in range(0, vol.shape[0] - 1): + np.add.at(vol[i], indicies, np.atleast_2d(values)[i]) + np.add.at(vol[-1], indicies, weights) if cleanup: path.unlink() def volume_from_intermediates(path: Path, shape: DataShape, thread_count: int = 4): - vol = np.zeros((1 + shape.C, np.prod((shape.Y, shape.X))), dtype=np.float32) - with ThreadPool(thread_count) as pool: - if not path.exists(): - # We don't have any intermediate(s) for this value, so return empty. - return vol[0] - elif path.is_dir(): - pool.starmap(increment_volume, [(i, vol, 0, False) for i in path.glob("**/*.tif*")]) - else: - with TiffFile(path) as tif: - offset_set = range(0, len(tif.series), 4) - pool.starmap(increment_volume, [(path, vol, i, False) for i in offset_set]) + vol = np.zeros((2, np.prod((shape.Y, shape.X))), dtype=np.float32) + if path.is_dir(): + with ThreadPool(thread_count) as pool: + pool.starmap(increment_volume, [(i, vol, 0, True) for i in path.glob("**/*.tif*")]) + elif path.exists(): + increment_volume(path, vol, 0, True) - nz = np.flatnonzero(vol[-1]) - vol[:-1, nz] /= vol[-1, nz] - return vol[:-1] + nz = np.flatnonzero(vol[0]) + vol[0, nz] /= vol[1, nz] + return vol[0] def write_conv_vol(writer: callable, source_path, shape, dtype, scaling, target_folder, index, interpolation): perf = {} - start = time.perf_counter() + vol_start = time.perf_counter() vol = volume_from_intermediates(source_path, shape) - perf["Merge Volume"] = time.perf_counter() - start + perf["Merge Volume"] = time.perf_counter() - vol_start if scaling is not None: start = time.perf_counter() # CV2 is only 2D but we're resizing from the 1D image anyway at the moment. @@ -241,4 +241,5 @@ def write_conv_vol(writer: callable, source_path, shape, dtype, scaling, target_ writer(target_folder.joinpath(f"{index}.tif"), data=np_convert(dtype, vol.T.reshape(shape.Y, shape.X, shape.C), normalize=False, safe_bool=True)) perf["Write Merged"] = time.perf_counter() - start + perf["Total Chunk Merge"] = time.perf_counter() - vol_start return perf diff --git a/python/ouroboros/helpers/parse.py b/python/ouroboros/helpers/parse.py index c05b210..01b2173 100644 --- a/python/ouroboros/helpers/parse.py +++ b/python/ouroboros/helpers/parse.py @@ -11,10 +11,10 @@ class CV_FORMAT(Enum): PRECOMPUTED = ["neuroglancer-precomputed"] ZARR = ["zarr", "zarr2", "zarr3"] N5 = ["n5"] - + def __str__(self): return f"{self.name.lower()}://" - + @classmethod def get(cls, suffix): for e in cls: @@ -56,8 +56,8 @@ def parse_source(cls, value, handler: ValidatorFunctionWrapHandler) -> str: split_source = base_source.split("|") if len(split_source) > 1: kv_store = split_source[1].split(":") - base_source = f"{CV_FORMAT.get(kv_store[0])}{split_source[0]}{kv_store[1]}" - + base_source = f"{CV_FORMAT.get(kv_store[0])}{split_source[0]}{kv_store[1]}" + return SourceModel(url=base_source) if isinstance(source, SourceModel) else base_source diff --git a/python/ouroboros/helpers/shapes.py b/python/ouroboros/helpers/shapes.py index eacd73b..3f72280 100644 --- a/python/ouroboros/helpers/shapes.py +++ b/python/ouroboros/helpers/shapes.py @@ -2,7 +2,6 @@ Module containing shapes of data. """ from abc import ABC, abstractmethod -from collections import namedtuple from dataclasses import dataclass, asdict, replace, fields, astuple, make_dataclass, Field, InitVar from functools import cached_property, reduce import operator @@ -233,10 +232,6 @@ class Z(DataShape): Z: int # noqa: E701,E702 class GenericOrder(DataShape): A: int; B: int; C: int # noqa: E701,E702 -# ???? -NPString = namedtuple("NPString", 'T') - - @dataclass class DataRange(object): start: DataShape; stop: DataShape; step: DataShape # noqa: E701,E702 diff --git a/python/ouroboros/helpers/slice.py b/python/ouroboros/helpers/slice.py index 1f9845e..e2a9400 100644 --- a/python/ouroboros/helpers/slice.py +++ b/python/ouroboros/helpers/slice.py @@ -219,7 +219,7 @@ def backproject_box(bounding_box: BoundingBox, slice_rects: np.ndarray, slices: np.add.at(volume[-1], points + point_inc, c_weights) # Get indicies of the flattened Z-Y-X backprojected domain that have values. - nz_vol = np.flatnonzero(volume[-1]) + nz_vol = np.flatnonzero(volume[-1]).astype(squish_type) # Return indicies and only the volume region with values. return nz_vol, volume[:-1, nz_vol].squeeze(), volume[-1, nz_vol].squeeze() diff --git a/python/ouroboros/pipeline/backproject_pipeline.py b/python/ouroboros/pipeline/backproject_pipeline.py index 2c7283b..5cef5be 100644 --- a/python/ouroboros/pipeline/backproject_pipeline.py +++ b/python/ouroboros/pipeline/backproject_pipeline.py @@ -11,6 +11,7 @@ import time import traceback +from filelock import FileLock import numpy as np from ouroboros.helpers.memory_usage import ( @@ -32,7 +33,7 @@ join_path, generate_tiff_write, write_conv_vol, - write_small_intermediate + write_raw_intermediate ) from ouroboros.helpers.shapes import DataRange, ImgSliceC @@ -127,19 +128,16 @@ def _process(self, input_data: any) -> tuple[any, None] | tuple[None, any]: straightened_volume_path = new_straightened_volume_path - # Write huge temp files (need to address) full_bounding_box = BoundingBox.bound_boxes(volume_cache.bounding_boxes) write_shape = np.flip(full_bounding_box.get_shape()).tolist() - print(f"\nFront Projection Shape: {FPShape}") - print(f"\nBack Projection Shape (Z/Y/X):{write_shape}") pipeline_input.output_file_path = (f"{config.output_file_name}_" f"{'_'.join(map(str, full_bounding_box.get_min(np.uint32)))}") folder_path = Path(config.output_file_folder, pipeline_input.output_file_path) folder_path.mkdir(exist_ok=True, parents=True) - i_path = Path(config.output_file_folder, - f"{config.output_file_name}_t_{'_'.join(map(str, full_bounding_box.get_min(np.uint32)))}") + # Intermediate Path + i_path = Path(config.output_file_folder, f"{os.getpid()}_{config.output_file_name}") if config.make_single_file: is_big_tiff = calculate_gigabytes_from_dimensions( @@ -189,12 +187,12 @@ def _process(self, input_data: any) -> tuple[any, None] | tuple[None, any]: for chunk, _, chunk_rects, _, index in chunk_range.get_iter(chunk_iter): bp_futures.append(executor.submit( process_chunk, - config, - straightened_volume_path, - chunk_rects, - chunk, - index, - full_bounding_box + config=config, + straightened_volume_path=straightened_volume_path, + chunk_rects=chunk_rects, + chunk=chunk, + index=index, + full_bounding_box=full_bounding_box )) # Track what's written. @@ -206,8 +204,8 @@ def _process(self, input_data: any) -> tuple[any, None] | tuple[None, any]: def note_written(write_future): nonlocal pages_written pages_written += 1 - self.update_progress((np.sum(processed) / len(chunk_range)) * (2 / 3) - + (pages_written / num_pages) * (1 / 3)) + self.update_progress((np.sum(processed) / len(chunk_range)) * (exec_procs / self.num_processes) + + (pages_written / num_pages) * (write_procs / self.num_processes)) for key, value in write_future.result().items(): self.add_timing(key, value) @@ -222,8 +220,8 @@ def note_written(write_future): # Update the progress bar processed[index] = 1 - self.update_progress((np.sum(processed) / len(chunk_range)) * (2 / 3) - + (pages_written / num_pages) * (1 / 3)) + self.update_progress((np.sum(processed) / len(chunk_range)) * (exec_procs / self.num_processes) + + (pages_written / num_pages) * (write_procs / self.num_processes)) update_writable_rects(processed, slice_rects, min_dim, writeable, DEFAULT_CHUNK_SIZE) @@ -233,14 +231,14 @@ def note_written(write_future): for index in write: write_futures.append(write_executor.submit( write_conv_vol, - tif_write(tifffile.imwrite), - i_path.joinpath(f"i_{index:05}"), - ImgSliceC(*write_shape[1:], channels), - bool if config.make_backprojection_binary else np.uint16, - scaling_factors, - folder_path, - index, - config.upsample_order + writer=tif_write(tifffile.imwrite), + source_path=i_path.joinpath(f"i_{index:05}.dat"), + shape=ImgSliceC(*write_shape[1:], channels), + dtype=bool if config.make_backprojection_binary else np.uint16, + scaling=scaling_factors, + target_folder=folder_path, + index=index, + interpolation=config.upsample_order )) write_futures[-1].add_done_callback(note_written) @@ -271,8 +269,7 @@ def note_written(write_future): if config.make_single_file: shutil.rmtree(folder_path) - shutil.rmtree(Path(config.output_file_folder, - f"{config.output_file_name}_t_{'_'.join(map(str, full_bounding_box.get_min(np.uint32)))}")) + shutil.rmtree(i_path) return None @@ -320,7 +317,7 @@ def process_chunk( if values.nbytes == 0: # No data to write from this chunk, so return as such. - durations["total_process"] = [time.perf_counter() - start_total] + durations["total_chunk_process"] = [time.perf_counter() - start_total] return durations, index, [] # Save the data @@ -336,7 +333,9 @@ def process_chunk( "target_rows": full_bounding_box.get_shape()[0], "offset_columns": offset[1], "offset_rows": offset[2], + "channel_count": np.uint32(1 if len(slices.shape) < 4 else slices.shape[-1]), } + type_ar = np.array([yx_vals.dtype.str, values.dtype.str, weights.dtype.str], dtype='S8') durations["split"] = [time.perf_counter() - start] # Gets slices off full array corresponding to each Z value. @@ -347,19 +346,24 @@ def process_chunk( durations["stack"] = [time.perf_counter() - start] start = time.perf_counter() - file_path = Path(config.output_file_folder, - f"{config.output_file_name}_t_{'_'.join(map(str, full_bounding_box.get_min(np.uint32)))}") - file_path.mkdir(exist_ok=True, parents=True) + i_path = Path(config.output_file_folder, f"{os.getppid()}_{config.output_file_name}") + i_path.mkdir(exist_ok=True, parents=True) - def write_z(i, z_slice): + def write_z(target, z_slice): + write_raw_intermediate(target, + np.fromiter(offset_dict.values(), dtype=np.uint32, count=5).tobytes(), + np.uint32(len(yx_vals[z_slice])).tobytes(), + type_ar.tobytes(), + yx_vals[z_slice].tobytes(), values[z_slice].tobytes(), weights[z_slice].tobytes()) + + def make_z(i, z_slice): offset_z = z_stack[i] + offset[0] - file_path.joinpath(f"i_{offset_z:05}").mkdir(exist_ok=True, parents=True) - write_small_intermediate(file_path.joinpath(f"i_{offset_z:05}", f"{index}.tif"), - np.fromiter(offset_dict.values(), dtype=np.uint32, count=4), - yx_vals[z_slice], np.atleast_2d(values)[:, z_slice], weights[z_slice]) + z_path = i_path.joinpath(f"i_{offset_z:05}.dat") + with FileLock(z_path.with_suffix(".lock")): + write_z(open(z_path, "ab"), z_slice) with ThreadPool(12) as pool: - pool.starmap(write_z, enumerate(z_slices)) + pool.starmap(make_z, enumerate(z_slices)) durations["write_intermediate"] = [time.perf_counter() - start] except BaseException as be: @@ -367,7 +371,7 @@ def write_z(i, z_slice): traceback.print_tb(be.__traceback__, file=sys.stderr) raise be - durations["total_process"] = [time.perf_counter() - start_total] + durations["total_chunk_process"] = [time.perf_counter() - start_total] return durations, index, z_stack + offset[0] diff --git a/python/poetry.lock b/python/poetry.lock index e4329e9..489bcc2 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "altgraph" @@ -1518,6 +1518,18 @@ files = [ [package.dependencies] numpy = "*" +[[package]] +name = "filelock" +version = "3.20.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2"}, + {file = "filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4"}, +] + [[package]] name = "fonttools" version = "4.60.1" @@ -4572,43 +4584,29 @@ groups = ["main"] files = [ {file = "zfpy-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1417ec8595c2ccb467d228410f7570b1e804bf042ffe84664d7851a267e03239"}, {file = "zfpy-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:893aa8239a9cbae34f8277c6633c145f2dc11230f0f51a39c010a594a4581976"}, - {file = "zfpy-1.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55981215b0e4c447c3e715190d5c27e1f67af137088aff35fccee8bdb52a0eed"}, - {file = "zfpy-1.0.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4feea60d3bca2ae48456e2b38831d8715694072266bd4642ff738fb7e5512edc"}, {file = "zfpy-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910cac6c191f7bf9d100bd348486d2e53a35a2ba4aa7091020f7154b9f26454a"}, {file = "zfpy-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b15012ed906b89b43474d5b1400a955348c6bc9cc4c216c2049c16c249a9b51f"}, {file = "zfpy-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:a71c0f8c4bfba2433ad11332d6b8493ae661a1c01048292f5e539103a605ec2d"}, {file = "zfpy-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:13146293a5132627feb2c9f7adc6d95c528dd19dc3260f000e4b6284b2696b1f"}, {file = "zfpy-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:06d6d6a46fbd198c6fec7f31b111e11f5be3a254859f5ac3ab9488d05b240337"}, - {file = "zfpy-1.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96c8d3702ec6fdb40cd40c8c64c9e47fe7ba07ca63a0392d051fb109e44a85b0"}, - {file = "zfpy-1.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0ad9b75ef5135bcd8ba8d0851dda828c6d183c5cd73577782a794387c001ec1"}, {file = "zfpy-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d204b17ee8c1eef6a2e650c123d627c0f64b3d8957df4c788dd2aaf2a72eb4ae"}, {file = "zfpy-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0ea9d0bcf191ec345145592d2e7586413bcfd385034f85d00238e1eaa857185"}, {file = "zfpy-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:78ff5cc656da9414e264bbdfb316a0c30bf930a426f9c2a1f1a5131620135439"}, {file = "zfpy-1.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bb83fa31c943475c4ed8182c0bd1db518beca319809612c9851d78545e7d31c9"}, {file = "zfpy-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d401da303fe926b18df1e93483a2099762d61db87597ccdaf89e25fb71b107cc"}, - {file = "zfpy-1.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c5289c100e9820723b1f78616a0e37ab32416c69b0c934e96f2b8d9138f54d5"}, - {file = "zfpy-1.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33e9505c7457937912ed4a2356d802ba1898fe32d636e24f21c7072d03dc68fb"}, {file = "zfpy-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b5e7c1f0ebdecc6850e65004e7fbba8d5e78bf4b626654e463c3e0aa5b3fe54"}, {file = "zfpy-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58180c0917656ab0eb2dadad7e033e10271a3c615dc8972657137dba16e2bd42"}, {file = "zfpy-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:f15c91425cebca0ab5c88b3b2ba0eb800ec5310e3ea2a6b5f3e4b198a97288d9"}, {file = "zfpy-1.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:791fa42b651a26be67e52043b619e3a039862900e7080711d07a5de956158b8c"}, {file = "zfpy-1.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f811480ebff2e028c50bf3491d1d2e3869eb4388883543d0698f5fdc917e4386"}, - {file = "zfpy-1.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:665d3f55665a5b53642842f751f2dc1e7ec5a1307030f88b8137e63bb93c9e24"}, - {file = "zfpy-1.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7192425d69887a0759aa3f494f942fe5da7fd32debc1ec5ed44bbd754d8b486a"}, {file = "zfpy-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4892bc441ce59de98c4b3b29f04e44657aa6ccfe3ae5ca23660efb010b829d6"}, {file = "zfpy-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a64590ef3c182988f4ac5745ec6e1623b06c348583cbdb96d296e4899f4469"}, {file = "zfpy-1.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9a71cfce2fb7a82497e90e7152f847250664f30ca4dfedcca500c35d4c63e44"}, - {file = "zfpy-1.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ba62488da318a1e2a7497164823ac0899993560e41fbdfb0381b0f011b317f26"}, - {file = "zfpy-1.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:838926f46c6601bce70068b711d630f74cde89ab33d92851bfaa9ed22ae06009"}, - {file = "zfpy-1.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78dfd1ce01fc79e068be69a7e883203cf47f13806e6f65d66cb30e2b2a4bcb13"}, - {file = "zfpy-1.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:802633376e86ed19bb07cd8518dd445a81b81f89ef9ac2147f4429a95cd0a69f"}, - {file = "zfpy-1.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:5448056a32da0f5db4dd3a80b008d6d31a06ba1a6d44bf5cf3a112a238ca30ac"}, {file = "zfpy-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46ec529b729b553604a3509cd0d6d0c28169f0be0cd7744f69f16324912d463b"}, {file = "zfpy-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b717a14b3ae18fc0130d440706dc08cde9c8149423bb0df9f478f52a07a5ed6"}, {file = "zfpy-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf82232d4f6e0092f54a7bf2ec52ae70024f0a2839ce54b77fb2a29fb417791e"}, {file = "zfpy-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c86a576d65557f83e2c758664ee96710c06352f8b79dca5867199e94ccfb2ce"}, {file = "zfpy-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:2f0dea9281b6ce1623eb45316f000a0fdaad01459a01bc50dd866a5fff37ec7b"}, - {file = "zfpy-1.0.1.tar.gz", hash = "sha256:75c7014bdb2ad497a08846aaadca6d13de7c154a541c42557e52ec42030ca926"}, ] [[package]] @@ -4812,4 +4810,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "6befa102e0ee25da5d46ca77f34981dd8cd12e5bfb86c61d35377a30b4d314fa" +content-hash = "1bf3f1761d13853aa420300e55e88f9778a98fd0b0c65a334c074770513ea13c" diff --git a/python/pyproject.toml b/python/pyproject.toml index 48a21f0..f609da6 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -29,6 +29,7 @@ sse-starlette = "^2.3.2" typing-extensions = "^4.14.0" uvicorn = "^0.29.0" opencv-python = "^4.0.0" +filelock = "^3.20.0" [tool.poetry.group.dev.dependencies] pytest = "^8.2.2" diff --git a/python/test/helpers/test_files.py b/python/test/helpers/test_files.py index 340c7ad..872381c 100644 --- a/python/test/helpers/test_files.py +++ b/python/test/helpers/test_files.py @@ -20,9 +20,9 @@ np_convert, generate_tiff_write, ravel_map_2d, - load_z_intermediate, increment_volume, - write_small_intermediate + load_raw_file_intermediate, + write_raw_intermediate ) @@ -223,24 +223,28 @@ def test_write_intermediate(tmp_path): "target_rows": np.uint32(60), "offset_columns": np.uint32(60), "offset_rows": np.uint32(40), + "channels": np.uint32(1) } + type_ar = np.array([raveled_source.dtype.str, source_values.dtype.str, source_weights.dtype.str], dtype='S8') - write_small_intermediate(sample_path, - np.fromiter(offset_dict.values(), dtype=np.uint32, count=4), - raveled_source, - source_values, - source_weights) + write_raw_intermediate(open(sample_path, "wb"), + np.fromiter(offset_dict.values(), dtype=np.uint32, count=5).tobytes(), + np.uint32(len(raveled_source)).tobytes(), + type_ar.tobytes(), + raveled_source.tobytes(), + source_values.tobytes(), + source_weights.tobytes()) - indicies, values, weights = load_z_intermediate(sample_path) + indicies, values, weights = load_raw_file_intermediate(open(sample_path, "rb")) assert len(indicies) == 100 - assert np.all(indicies == raveled_mapped) - assert len(values) == 100 + assert values.shape == (1, 100) assert values.dtype == np.float32 - assert np.all(values == source_values) assert len(weights) == 100 - assert np.all(weights == source_weights) assert weights.dtype == np.float32 + assert np.all(values[0] == source_values) + assert np.all(weights == source_weights) + assert np.all(indicies == raveled_mapped) def test_increment_volume(tmp_path): @@ -258,13 +262,17 @@ def test_increment_volume(tmp_path): "target_rows": np.uint32(60), "offset_columns": np.uint32(60), "offset_rows": np.uint32(40), + "channels": np.uint32(1) } - - write_small_intermediate(sample_path, - np.fromiter(offset_dict.values(), dtype=np.uint32, count=4), - raveled_source, - source_values, - source_weights) + type_ar = np.array([raveled_source.dtype.str, source_values.dtype.str, source_weights.dtype.str], dtype='S8') + + write_raw_intermediate(open(sample_path, "wb"), + np.fromiter(offset_dict.values(), dtype=np.uint32, count=5).tobytes(), + np.uint32(len(raveled_source)).tobytes(), + type_ar.tobytes(), + raveled_source.tobytes(), + source_values.tobytes(), + source_weights.tobytes()) volume = np.zeros((2, 80 * 60)) increment_volume(sample_path, volume[:], cleanup=True) From 43364e31c8fc52b129e8101d1c06b5d387801c41 Mon Sep 17 00:00:00 2001 From: David Northover Date: Fri, 17 Oct 2025 13:39:35 -0400 Subject: [PATCH 2/2] Adding cargo to docker install --- python/Dockerfile | 3 ++- python/Dockerfile-prod | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/python/Dockerfile b/python/Dockerfile index fac80f8..c409c5a 100644 --- a/python/Dockerfile +++ b/python/Dockerfile @@ -8,7 +8,8 @@ ENV POETRY_NO_INTERACTION=1 \ RUN apt-get update -y RUN apt-get install \ gcc \ - libpq-dev + libpq-dev \ + cargo WORKDIR /app diff --git a/python/Dockerfile-prod b/python/Dockerfile-prod index f76ee15..0e55fd7 100644 --- a/python/Dockerfile-prod +++ b/python/Dockerfile-prod @@ -7,7 +7,8 @@ FROM thehale/python-poetry:2.1.3-py3.11-slim as python-base RUN apt-get update -y RUN apt-get install -y \ gcc \ - libpq-dev + libpq-dev \ + cargo COPY ./dist/*.whl ./