From 2e6db025fcbb5cd1a76fd50078d70d2438b7cbb6 Mon Sep 17 00:00:00 2001 From: thewtex <25432+thewtex@users.noreply.github.com> Date: Tue, 1 Sep 2026 09:11:59 +0000 Subject: [PATCH 1/2] chore(ci): update prek hooks --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54ae4d41..4552634b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ default_install_hook_types: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v4.6.0" + rev: "v6.0.0" hooks: - id: check-added-large-files # Example notebooks are stored with their outputs, figures included. @@ -41,19 +41,19 @@ repos: exclude: '^\.github/(agents/.*\.agent\.md|workflows/(.*\.lock\.yml|agentics-maintenance\.yml))$' - repo: https://github.com/codespell-project/codespell - rev: "v2.2.5" + rev: "v2.4.3" hooks: - id: codespell # Executed example notebooks embed base64 figures. exclude: ^(ts/deno\.lock|py/examples/.*\.ipynb)$ - repo: https://github.com/shellcheck-py/shellcheck-py - rev: "v0.9.0.5" + rev: "v0.11.0.1-1" hooks: - id: shellcheck - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.2 + rev: v0.16.5 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -61,7 +61,7 @@ repos: - id: ruff-format - repo: https://github.com/commitizen-tools/commitizen - rev: "v4.13.0" + rev: "v4.18.0" hooks: - id: commitizen stages: [commit-msg] From 80513c14a12d4a5ba39a6788d40c6fa38b09c123 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Tue, 1 Sep 2026 11:55:51 -0400 Subject: [PATCH 2/2] chore(ci): satisfy updated prek hooks The hook autoupdate in the previous commit pulled in ruff 0.16, whose defaults changed in two ways that broke the prek job: - Files that resolve no ruff configuration now get a much broader default rule set. `.commitizen/*.py` and `docs/conf.py` sit above `py/` and `mcp/`, so they were suddenly linted with rules the project does not select. Add a root `ruff.toml` that extends `py/pyproject.toml` so they follow the same configuration as the rest of the Python code, and apply the resulting RET505 fixes. - `ruff-format` now also formats Python blocks in Markdown, which reformats the documentation. Keep that, but exclude `AGENTS.md`: its formatting guidance is written as deliberately misformatted "WRONG" examples that formatting would collapse into their "RIGHT" counterparts. Also switch the `ruff` hook to its new `ruff-check` id, apply the RUF036 and duplicate-`__all__`-entry fixes, and fix a spelling that codespell 2.4 flags in docs/mcp.md. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016uY6F9EC1Wo5QuyvaFUaqx --- .commitizen/cz_ngff_zarr.py | 8 +-- .pre-commit-config.yaml | 7 ++- docs/faq.md | 1 + docs/hcs.md | 98 ++++++++++++++++++++-------------- docs/installation.md | 3 +- docs/lif.md | 5 +- docs/mcp.md | 2 +- docs/python.md | 85 +++++++++++++++-------------- docs/quick_start.md | 4 +- docs/rfc4.md | 34 ++++-------- docs/tiff.md | 4 +- py/ngff_zarr/__init__.py | 1 - py/ngff_zarr/multiscales.py | 2 +- py/ngff_zarr/to_multiscales.py | 2 +- ruff.toml | 5 ++ 15 files changed, 134 insertions(+), 127 deletions(-) create mode 100644 ruff.toml diff --git a/.commitizen/cz_ngff_zarr.py b/.commitizen/cz_ngff_zarr.py index 3baba0b7..fb0433ed 100644 --- a/.commitizen/cz_ngff_zarr.py +++ b/.commitizen/cz_ngff_zarr.py @@ -24,9 +24,9 @@ def _detect_package(self, config): tag_format = config.settings.get("tag_format", "") if "py-v" in tag_format: return "py" - elif "mcp-v" in tag_format: + if "mcp-v" in tag_format: return "mcp" - elif "ts-v" in tag_format: + if "ts-v" in tag_format: return "ts" return None @@ -81,9 +81,9 @@ def _should_include_commit(self, scope: str | None, files: list[str]) -> bool: # Handle package-specific scope filtering if self.package == "py": return self._should_include_for_py(scope, files) - elif self.package == "mcp": + if self.package == "mcp": return self._should_include_for_mcp(scope, files) - elif self.package == "ts": + if self.package == "ts": return self._should_include_for_ts(scope, files) # If package unknown, include by default diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4552634b..1fa5eb9e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,10 +55,15 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.16.5 hooks: - - id: ruff + - id: ruff-check args: [--fix, --exit-non-zero-on-fix] exclude: py/test/conftest.py - id: ruff-format + # ruff-format also formats Python blocks in Markdown as of + # ruff-pre-commit v0.16. AGENTS.md documents formatting mistakes with + # deliberately misformatted "WRONG" examples, so formatting it would + # erase the distinction it is trying to draw. + exclude: ^AGENTS\.md$ - repo: https://github.com/commitizen-tools/commitizen rev: "v4.18.0" diff --git a/docs/faq.md b/docs/faq.md index 7224bb62..03b168ba 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -145,6 +145,7 @@ store wraps instead: ```python # Before from zarr.storage import LocalStore + multiscales = from_ngff_zarr(LocalStore("image.ome.zarr")) # After diff --git a/docs/hcs.md b/docs/hcs.md index 461978bb..e2ce6567 100644 --- a/docs/hcs.md +++ b/docs/hcs.md @@ -187,9 +187,7 @@ The expected workflow for writing HCS data is to: The plate metadata structure is the same for both v0.4 and v0.5. The version is specified when creating the `Plate` object: ```python -from ngff_zarr import ( - Plate, PlateColumn, PlateRow, PlateWell, PlateAcquisition -) +from ngff_zarr import Plate, PlateColumn, PlateRow, PlateWell, PlateAcquisition # Define plate structure columns = [PlateColumn(name="1"), PlateColumn(name="2"), PlateColumn(name="3")] @@ -308,6 +306,7 @@ with HCSPlateWriter("my_screen.ozx", plate_metadata) as writer: ```python from concurrent.futures import ThreadPoolExecutor + def write_well(writer, well_data): writer.write_well_image( multiscales=well_data.image, @@ -316,6 +315,7 @@ def write_well(writer, well_data): field_index=well_data.field, ) + with HCSPlateWriter("plate.ozx", plate_metadata) as writer: with ThreadPoolExecutor(max_workers=4) as executor: executor.map(lambda wd: write_well(writer, wd), well_data_list) @@ -361,8 +361,8 @@ from ngff_zarr import write_store_to_zip # Convert an existing plate to .ozx write_store_to_zip( "existing_plate.ome.zarr", # Source plate directory - "plate.ozx", # Output .ozx file - version="0.5" # Required for .ozx + "plate.ozx", # Output .ozx file + version="0.5", # Required for .ozx ) ``` @@ -449,16 +449,16 @@ ome_zarr_version = "0.4" # Create plate layout for a 96-well plate (subset) columns = [PlateColumn(name=str(i)) for i in range(1, 13)] # 12 columns -rows = [PlateRow(name=chr(65 + i)) for i in range(8)] # 8 rows (A-H) +rows = [PlateRow(name=chr(65 + i)) for i in range(8)] # 8 rows (A-H) wells = [] for row_idx, row in enumerate(rows): for col_idx, col in enumerate(columns): - wells.append(PlateWell( - path=f"{row.name}/{col.name}", - rowIndex=row_idx, - columnIndex=col_idx - )) + wells.append( + PlateWell( + path=f"{row.name}/{col.name}", rowIndex=row_idx, columnIndex=col_idx + ) + ) plate_metadata = Plate( name="Compound Screen - Plate 1", @@ -466,16 +466,16 @@ plate_metadata = Plate( rows=rows, wells=wells, field_count=4, # 4 fields per well - version=ome_zarr_version # Use consistent version + version=ome_zarr_version, # Use consistent version ) + # Function to create synthetic field data def create_field_image(treatment_effect=1.0): # Create synthetic microscopy data: T, C, Z, Y, X base_intensity = 100 data = np.random.poisson( - base_intensity * treatment_effect, - size=(1, 2, 10, 512, 512) + base_intensity * treatment_effect, size=(1, 2, 10, 512, 512) ).astype(np.uint16) ngff_image = NgffImage( @@ -488,6 +488,7 @@ def create_field_image(treatment_effect=1.0): return to_multiscales(ngff_image, scale_factors=[2, 4]) + # Simulate acquisition workflow plate_store = "drug_screen_plate1.ome.zarr" @@ -498,7 +499,6 @@ to_hcs_zarr(hcs_plate, plate_store) # Iterate through wells as they are imaged for row in ["A", "B", "C"]: # First 3 rows for demo for col in ["1", "2", "3", "4"]: # First 4 columns for demo - # Simulate different treatment effects if row == "A": effect = 0.5 # Inhibitor @@ -529,7 +529,9 @@ print(f"Screening complete! Plate saved to: {plate_store}") # Verify the written data final_plate = nz.from_hcs_zarr(plate_store) print(f"Plate: {final_plate.name}") -print(f"Wells written: {len([w for w in final_plate.wells if final_plate.get_well(final_plate.rows[w.rowIndex].name, final_plate.columns[w.columnIndex].name) is not None])}") +print( + f"Wells written: {len([w for w in final_plate.wells if final_plate.get_well(final_plate.rows[w.rowIndex].name, final_plate.columns[w.columnIndex].name) is not None])}" +) ``` ### Complete Example: v0.5 Drug Screening Workflow @@ -546,16 +548,16 @@ ome_zarr_version = "0.5" # Create plate layout for v0.5 columns = [PlateColumn(name=str(i)) for i in range(1, 4)] # 3 columns -rows = [PlateRow(name=chr(65 + i)) for i in range(2)] # 2 rows (A-B) +rows = [PlateRow(name=chr(65 + i)) for i in range(2)] # 2 rows (A-B) wells = [] for row_idx, row in enumerate(rows): for col_idx, col in enumerate(columns): - wells.append(PlateWell( - path=f"{row.name}/{col.name}", - rowIndex=row_idx, - columnIndex=col_idx - )) + wells.append( + PlateWell( + path=f"{row.name}/{col.name}", rowIndex=row_idx, columnIndex=col_idx + ) + ) plate_metadata = Plate( name="v0.5 Screening Plate", @@ -566,12 +568,12 @@ plate_metadata = Plate( version=ome_zarr_version, # Use consistent version ) + # Function to create synthetic field data def create_field_image(treatment_effect=1.0): base_intensity = 100 data = np.random.poisson( - base_intensity * treatment_effect, - size=(1, 2, 10, 512, 512) + base_intensity * treatment_effect, size=(1, 2, 10, 512, 512) ).astype(np.uint16) ngff_image = NgffImage( @@ -584,6 +586,7 @@ def create_field_image(treatment_effect=1.0): return to_multiscales(ngff_image, scale_factors=[2, 4]) + # Create plate structure plate_store = "screening_v05.ome.zarr" hcs_plate = HCSPlate(store=plate_store, plate_metadata=plate_metadata) @@ -682,17 +685,21 @@ wells = [ ] plate_metadata = Plate( - columns=columns, rows=rows, wells=wells, - name="Example Plate v0.4", field_count=1, - version=ome_zarr_version + columns=columns, + rows=rows, + wells=wells, + name="Example Plate v0.4", + field_count=1, + version=ome_zarr_version, ) # Create synthetic image data data = np.random.randint(0, 255, size=(1, 1, 10, 256, 256), dtype=np.uint8) ngff_image = nz.NgffImage( - data=data, dims=["t", "c", "z", "y", "x"], + data=data, + dims=["t", "c", "z", "y", "x"], scale={"t": 1.0, "c": 1.0, "z": 0.5, "y": 0.325, "x": 0.325}, - translation={"t": 0.0, "c": 0.0, "z": 0.0, "y": 0.0, "x": 0.0} + translation={"t": 0.0, "c": 0.0, "z": 0.0, "y": 0.0, "x": 0.0}, ) multiscales = nz.to_multiscales(ngff_image) @@ -744,17 +751,21 @@ wells = [ ] plate_metadata = Plate( - columns=columns, rows=rows, wells=wells, - name="Example Plate v0.5", field_count=1, - version=ome_zarr_version + columns=columns, + rows=rows, + wells=wells, + name="Example Plate v0.5", + field_count=1, + version=ome_zarr_version, ) # Create synthetic image data data = np.random.randint(0, 255, size=(1, 1, 10, 256, 256), dtype=np.uint8) ngff_image = nz.NgffImage( - data=data, dims=["t", "c", "z", "y", "x"], + data=data, + dims=["t", "c", "z", "y", "x"], scale={"t": 1.0, "c": 1.0, "z": 0.5, "y": 0.325, "x": 0.325}, - translation={"t": 0.0, "c": 0.0, "z": 0.0, "y": 0.0, "x": 0.0} + translation={"t": 0.0, "c": 0.0, "z": 0.0, "y": 0.0, "x": 0.0}, ) multiscales = nz.to_multiscales(ngff_image) @@ -789,8 +800,13 @@ These approaches allow you to write individual well images as they are acquired ```python from ngff_zarr.v04.zarr_metadata import ( - Plate, PlateColumn, PlateRow, PlateWell, - Well, WellImage, PlateAcquisition + Plate, + PlateColumn, + PlateRow, + PlateWell, + Well, + WellImage, + PlateAcquisition, ) # Define plate structure @@ -811,7 +827,7 @@ plate_metadata = Plate( columns=columns, rows=rows, wells=wells, - field_count=2 # Number of fields per well + field_count=2, # Number of fields per well ) ``` @@ -961,14 +977,14 @@ memory usage: import ngff_zarr as nz # Configure cache sizes globally -nz.config.hcs_well_cache_size = 100 # Max wells to cache per plate -nz.config.hcs_image_cache_size = 50 # Max images to cache per well +nz.config.hcs_well_cache_size = 100 # Max wells to cache per plate +nz.config.hcs_image_cache_size = 50 # Max images to cache per well # Or configure per operation plate = nz.from_hcs_zarr( "plate.zarr", - well_cache_size=50, # Custom well cache size - image_cache_size=25 # Custom image cache size + well_cache_size=50, # Custom well cache size + image_cache_size=25, # Custom image cache size ) ``` diff --git a/docs/installation.md b/docs/installation.md index 218649a9..5f446740 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -18,7 +18,8 @@ With, for example, the [Pyodide REPL] or [JupyterLite] ```python import micropip -await micropip.install('ngff-zarr') + +await micropip.install("ngff-zarr") ``` ::: diff --git a/docs/lif.md b/docs/lif.md index 23b2d9b1..5dfba428 100644 --- a/docs/lif.md +++ b/docs/lif.md @@ -160,10 +160,7 @@ with LifFile("tile_scan.lif") as lif: # Check if image has mosaic dimension if has_mosaic_dimension(lif_image): # Convert to HCS plate structure - plate_metadata, well_images = lif_to_hcs_plate( - lif_image, - plate_name="TileScan" - ) + plate_metadata, well_images = lif_to_hcs_plate(lif_image, plate_name="TileScan") # Write as HCS plate with HCSPlateWriter("tile_scan.ome.zarr", plate_metadata) as writer: diff --git a/docs/mcp.md b/docs/mcp.md index dec8f2e6..b7f63432 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -45,7 +45,7 @@ agent: The `ngff-zarr-mcp` Model Context Protocol (MCP) server converts datasets to the OME-Zarr scientific imaging data format, optimizes the compression codec, ensures a limited number of files are generated, and creates a Python script for -re-use. +reuse. diff --git a/docs/python.md b/docs/python.md index 054382de..b2f10cc4 100644 --- a/docs/python.md +++ b/docs/python.md @@ -302,7 +302,7 @@ Metadata validation is supported for OME-Zarr version 0.1 to 0.5. To write the multiscales to OME-Zarr, use [`to_ngff_zarr`]. ```python -nz.to_ngff_zarr('cthead1.ome.zarr', multiscales) +nz.to_ngff_zarr("cthead1.ome.zarr", multiscales) ``` Use the `.ome.zarr` extension for local directory stores by convention. @@ -346,11 +346,8 @@ Format Specification 3. This can be a single integer, ```python -version = '0.5' -nz.to_ngff_zarr('lightsheet.ome.zarr', - multiscales, - chunks_per_shard=2, - version=version) +version = "0.5" +nz.to_ngff_zarr("lightsheet.ome.zarr", multiscales, chunks_per_shard=2, version=version) ``` This will use 2 chunks per shard for all dimensions. @@ -358,19 +355,20 @@ This will use 2 chunks per shard for all dimensions. Or, specify a tuple of integers for each dimension. ```python -nz.to_ngff_zarr('lightsheet.ome.zarr', - multiscales, - chunks_per_shard=(2, 2, 4), - version=version) +nz.to_ngff_zarr( + "lightsheet.ome.zarr", multiscales, chunks_per_shard=(2, 2, 4), version=version +) ``` Or, specify a dictionary of integers for each dimension. ```python -nz.to_ngff_zarr('lightsheet.ome.zarr', - multiscales, - chunks_per_shard={'z':4, 'y':2, 'x':2}, - version=version) +nz.to_ngff_zarr( + "lightsheet.ome.zarr", + multiscales, + chunks_per_shard={"z": 4, "y": 2, "x": 2}, + version=version, +) ``` The resulting shard shape will be the product of the chunk shape and the @@ -390,10 +388,12 @@ already there. The OME keys themselves (`multiscales`, `omero`, `ome`) are derived from the multiscales and cannot be set this way. ```python -multiscales.root_attributes = {'acquisition': {'direction': [0, -1, 0, 1, 0, 0, 0, 0, 1]}} -nz.to_ome_zarr('image.ome.zarr', multiscales, version='0.5') +multiscales.root_attributes = { + "acquisition": {"direction": [0, -1, 0, 1, 0, 0, 0, 0, 1]} +} +nz.to_ome_zarr("image.ome.zarr", multiscales, version="0.5") -nz.from_ome_zarr('image.ome.zarr').root_attributes +nz.from_ome_zarr("image.ome.zarr").root_attributes # {'acquisition': {'direction': [0, -1, 0, 1, 0, 0, 0, 0, 1]}} ``` @@ -402,7 +402,7 @@ filled region by region, goes in afterwards; existing keys are replaced and the others kept: ```python -nz.update_root_attributes('image.ome.zarr', {'acquisition': {'max_displacement': 3.2}}) +nz.update_root_attributes("image.ome.zarr", {"acquisition": {"max_displacement": 3.2}}) ``` ### Create the store before its data @@ -420,14 +420,14 @@ import dask.array as da shape = (1, 4096, 8192, 8192) chunks = (1, 64, 512, 512) -placeholder = da.zeros(shape, dtype='float32', chunks=chunks) -image = nz.to_ngff_image(placeholder, dims=['c', 'z', 'y', 'x']) +placeholder = da.zeros(shape, dtype="float32", chunks=chunks) +image = nz.to_ngff_image(placeholder, dims=["c", "z", "y", "x"]) multiscales = nz.to_multiscales(image, scale_factors=[], chunks=chunks, cache=False) -nz.to_ome_zarr('volume.ome.zarr', multiscales, version='0.5', metadata_only=True) +nz.to_ome_zarr("volume.ome.zarr", multiscales, version="0.5", metadata_only=True) -level0 = nz.open_array('volume.ome.zarr', multiscales.metadata.datasets[0].path) +level0 = nz.open_array("volume.ome.zarr", multiscales.metadata.datasets[0].path) for z in range(0, shape[1], 64): - level0[:, z:z + 64] = compute_slab(z) + level0[:, z : z + 64] = compute_slab(z) ``` Pass `cache=False` to `to_multiscales`: the placeholder has nothing worth @@ -451,10 +451,11 @@ rewritten; root attributes the writer does not own are kept; the multiscales entry is rewritten in full. ```python -base = nz.from_ome_zarr('volume.ome.zarr').images[0] +base = nz.from_ome_zarr("volume.ome.zarr").images[0] multiscales = nz.to_multiscales(base, scale_factors=[2, 4], chunks=chunks) -nz.to_ome_zarr('volume.ome.zarr', multiscales, version='0.5', - overwrite=False, start_level=1) +nz.to_ome_zarr( + "volume.ome.zarr", multiscales, version="0.5", overwrite=False, start_level=1 +) ``` `start_level` requires `overwrite=False`, and the store must hold every level @@ -482,13 +483,13 @@ For a store whose grid is already fixed, read the aligned block that contains the windows and slice it in memory. `open_array` reports the grid: ```python -level0 = nz.open_array('volume.ome.zarr', 'scale0/image') +level0 = nz.open_array("volume.ome.zarr", "scale0/image") depth = level0.chunks[1] for start in range(0, level0.shape[1], depth): - block = level0[:, start:start + depth] # one aligned read + block = level0[:, start : start + depth] # one aligned read for z in range(block.shape[1]): - plane = block[:, z] # a view, no read + plane = block[:, z] # a view, no read ``` This reaches the same speed as reading the volume in aligned blocks, whatever @@ -568,7 +569,7 @@ Use [`from_hcs_zarr`] to load HCS plate data: ```python # Load an HCS plate -plate = nz.from_hcs_zarr('screening_plate.ome.zarr') +plate = nz.from_hcs_zarr("screening_plate.ome.zarr") print(f"Plate: {plate.metadata.name}") print(f"Wells: {len(plate.metadata.wells)}") @@ -618,7 +619,7 @@ Validate HCS metadata during loading: ```python # Validate against HCS schema -plate = nz.from_hcs_zarr('plate.ome.zarr', validate=True) +plate = nz.from_hcs_zarr("plate.ome.zarr", validate=True) ``` For more detailed examples and advanced usage, see the @@ -632,14 +633,14 @@ the desired version when writing. ```python # Convert from 0.4 to 0.5 -multiscales = from_ngff_zarr('cthead1.ome.zarr') -to_ngff_zarr('cthead1_zarr3.ome.zarr', multiscales, version='0.5') +multiscales = from_ngff_zarr("cthead1.ome.zarr") +to_ngff_zarr("cthead1_zarr3.ome.zarr", multiscales, version="0.5") ``` ```python # Convert from 0.5 to 0.4 -multiscales = from_ngff_zarr('cthead1.ome.zarr') -to_ngff_zarr('cthead1_zarr2.ome.zarr', multiscales, version='0.4') +multiscales = from_ngff_zarr("cthead1.ome.zarr") +to_ngff_zarr("cthead1_zarr2.ome.zarr", multiscales, version="0.4") ``` ## Upgrade OME-Zarr versions @@ -678,13 +679,12 @@ import numpy as np import ngff_zarr as nz # Synthesize a tiny store written at OME-Zarr 0.5 (Zarr v3). -image = nz.to_ngff_image(np.zeros((4, 32, 32), dtype=np.uint8), - dims=['z', 'y', 'x']) +image = nz.to_ngff_image(np.zeros((4, 32, 32), dtype=np.uint8), dims=["z", "y", "x"]) multiscales = nz.to_multiscales(image, scale_factors=[2]) -nz.to_ome_zarr('image.ome.zarr', multiscales, version='0.5') +nz.to_ome_zarr("image.ome.zarr", multiscales, version="0.5") # Rewrite only the root metadata to 0.6; array chunks are left untouched. -nz.upgrade_ome_zarr('image.ome.zarr', version='0.6') +nz.upgrade_ome_zarr("image.ome.zarr", version="0.6") ``` Write an upgraded 0.6 copy from a 0.4 source, leaving the source intact: @@ -694,13 +694,12 @@ import numpy as np import ngff_zarr as nz # Synthesize a tiny store written at OME-Zarr 0.4 (Zarr v2). -image = nz.to_ngff_image(np.zeros((4, 32, 32), dtype=np.uint8), - dims=['z', 'y', 'x']) +image = nz.to_ngff_image(np.zeros((4, 32, 32), dtype=np.uint8), dims=["z", "y", "x"]) multiscales = nz.to_multiscales(image, scale_factors=[2]) -nz.to_ome_zarr('image_v04.ome.zarr', multiscales, version='0.4') +nz.to_ome_zarr("image_v04.ome.zarr", multiscales, version="0.4") # Write a new 0.6 store; 'image_v04.ome.zarr' is read lazily and never erased. -nz.upgrade_ome_zarr('image_v04.ome.zarr', 'image_v06.ome.zarr', version='0.6') +nz.upgrade_ome_zarr("image_v04.ome.zarr", "image_v06.ome.zarr", version="0.6") ``` Pass `validate=True` to validate the source metadata against the NGFF schema diff --git a/docs/quick_start.md b/docs/quick_start.md index 030a0ea9..39ba2a7c 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -25,7 +25,7 @@ data = np.random.randint(0, 256, int(1e6)).reshape((1000, 1000)) multiscales = nz.to_multiscales(data) -nz.to_ngff_zarr('example.ome.zarr', multiscales) +nz.to_ngff_zarr("example.ome.zarr", multiscales) ``` ## High Content Screening (HCS) @@ -34,7 +34,7 @@ nz.to_ngff_zarr('example.ome.zarr', multiscales) import ngff_zarr as nz # Load HCS plate data -plate = nz.from_hcs_zarr('screening_plate.ome.zarr') +plate = nz.from_hcs_zarr("screening_plate.ome.zarr") # Access a specific well well = plate.get_well("A", "1") diff --git a/docs/rfc4.md b/docs/rfc4.md index 6ec75e8e..a60b8c89 100644 --- a/docs/rfc4.md +++ b/docs/rfc4.md @@ -76,10 +76,7 @@ from itk import imread image = imread("image.nrrd") # Convert to NGFF image with anatomical orientation -ngff_image = ngff_zarr.itk_image_to_ngff_image( - image, - add_anatomical_orientation=True -) +ngff_image = ngff_zarr.itk_image_to_ngff_image(image, add_anatomical_orientation=True) # Convert to multiscales and write to Zarr -- because the image carries # orientation, it is written automatically @@ -102,10 +99,7 @@ from itkwasm_image_io import imread image = imread("image.nii.gz") # Convert with anatomical orientation -ngff_image = ngff_zarr.itk_image_to_ngff_image( - image, - add_anatomical_orientation=True -) +ngff_image = ngff_zarr.itk_image_to_ngff_image(image, add_anatomical_orientation=True) ``` ## Anatomical Orientation Values @@ -214,7 +208,7 @@ ngff_image = nz.NgffImage( dims=("z", "y", "x"), scale={"x": 1.0, "y": 1.0, "z": 1.0}, translation={"x": 0.0, "y": 0.0, "z": 0.0}, - axes_orientations=nz.LPS + axes_orientations=nz.LPS, ) # Convert to multiscales and write -- orientation is written automatically @@ -231,7 +225,7 @@ The `LPS` constant is equivalent to: { "x": AnatomicalOrientation(type="anatomical", value="right-to-left"), "y": AnatomicalOrientation(type="anatomical", value="anterior-to-posterior"), - "z": AnatomicalOrientation(type="anatomical", value="inferior-to-superior") + "z": AnatomicalOrientation(type="anatomical", value="inferior-to-superior"), } ``` @@ -257,7 +251,7 @@ ngff_image = nz.NgffImage( dims=("z", "y", "x"), scale={"x": 1.0, "y": 1.0, "z": 1.0}, translation={"x": 0.0, "y": 0.0, "z": 0.0}, - axes_orientations=nz.RAS + axes_orientations=nz.RAS, ) # Convert to multiscales and write -- orientation is written automatically @@ -274,7 +268,7 @@ The `RAS` constant is equivalent to: { "x": AnatomicalOrientation(type="anatomical", value="left-to-right"), "y": AnatomicalOrientation(type="anatomical", value="posterior-to-anterior"), - "z": AnatomicalOrientation(type="anatomical", value="inferior-to-superior") + "z": AnatomicalOrientation(type="anatomical", value="inferior-to-superior"), } ``` @@ -283,17 +277,11 @@ The `RAS` constant is equivalent to: You can also manually specify orientations for custom coordinate systems: ```python -from ngff_zarr import ( - AnatomicalOrientation, - AnatomicalOrientationValues, - NgffImage -) +from ngff_zarr import AnatomicalOrientation, AnatomicalOrientationValues, NgffImage import dask.array as da # Create orientation objects -x_orientation = AnatomicalOrientation( - value=AnatomicalOrientationValues.right_to_left -) +x_orientation = AnatomicalOrientation(value=AnatomicalOrientationValues.right_to_left) y_orientation = AnatomicalOrientation( value=AnatomicalOrientationValues.anterior_to_posterior ) @@ -308,11 +296,7 @@ ngff_image = NgffImage( dims=("z", "y", "x"), scale={"x": 1.0, "y": 1.0, "z": 1.0}, translation={"x": 0.0, "y": 0.0, "z": 0.0}, - axes_orientations={ - "x": x_orientation, - "y": y_orientation, - "z": z_orientation - } + axes_orientations={"x": x_orientation, "y": y_orientation, "z": z_orientation}, ) ``` diff --git a/docs/tiff.md b/docs/tiff.md index 17b4218f..b8fcad62 100644 --- a/docs/tiff.md +++ b/docs/tiff.md @@ -215,8 +215,8 @@ For an OME-TIFF with this metadata: The resulting NgffImage will have: ```python -ngff_image.scale = {'x': 0.5, 'y': 0.5, 'z': 2.0} -ngff_image.axes_units = {'x': 'micrometer', 'y': 'micrometer', 'z': 'micrometer'} +ngff_image.scale = {"x": 0.5, "y": 0.5, "z": 2.0} +ngff_image.axes_units = {"x": "micrometer", "y": "micrometer", "z": "micrometer"} ``` ## Pyramidal TIFFs diff --git a/py/ngff_zarr/__init__.py b/py/ngff_zarr/__init__.py index 820d6007..747e2e21 100644 --- a/py/ngff_zarr/__init__.py +++ b/py/ngff_zarr/__init__.py @@ -197,7 +197,6 @@ "Translation", "Transform", "Dataset", - "Metadata", "Omero", "OmeroChannel", "OmeroWindow", diff --git a/py/ngff_zarr/multiscales.py b/py/ngff_zarr/multiscales.py index f7b7e37f..9b481f83 100644 --- a/py/ngff_zarr/multiscales.py +++ b/py/ngff_zarr/multiscales.py @@ -25,7 +25,7 @@ class NgffMultiscales: int | tuple[int, ...] | tuple[tuple[int, ...], ...] - | Mapping[Any, None | int | tuple[int, ...]] + | Mapping[Any, int | tuple[int, ...] | None] | None ) = None generated_data_keys: list[str] | None = field( diff --git a/py/ngff_zarr/to_multiscales.py b/py/ngff_zarr/to_multiscales.py index ff7ceaf9..f3533c44 100644 --- a/py/ngff_zarr/to_multiscales.py +++ b/py/ngff_zarr/to_multiscales.py @@ -433,7 +433,7 @@ def to_multiscales( chunks: int | tuple[int, ...] | tuple[tuple[int, ...], ...] - | Mapping[Any, None | int | tuple[int, ...]] + | Mapping[Any, int | tuple[int, ...] | None] | None = None, progress: NgffProgress | NgffProgressCallback | None = None, cache: bool | None = None, diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..e2ec2609 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,5 @@ +# Ruff configuration for Python files outside of py/ and mcp/ (for example +# .commitizen/ and docs/conf.py). Those directories carry their own +# [tool.ruff] settings; without this file, ruff falls back to its built-in +# defaults, which select a much broader rule set than the project uses. +extend = "py/pyproject.toml"