From 2d115989f684b7edc509b7ad71871087b5fb14cc Mon Sep 17 00:00:00 2001 From: "Ali R. Khan" Date: Fri, 7 Nov 2025 20:22:57 -0500 Subject: [PATCH 01/14] fix and enhance imaris metadata was indexing wrong field (DataAxis3), fixes it and also adds in all the metadata into the json --- workflow/scripts/imaris_to_metadata.py | 112 ++++++++++++++++++------- 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/workflow/scripts/imaris_to_metadata.py b/workflow/scripts/imaris_to_metadata.py index e0a09f1..4853c41 100644 --- a/workflow/scripts/imaris_to_metadata.py +++ b/workflow/scripts/imaris_to_metadata.py @@ -1,40 +1,88 @@ import json - +import re import h5py import xmltodict +# ----------------------------- +# Helper functions +# ----------------------------- + +def parse_xml_bytes(xml_bytes): + """Convert raw byte array to parsed XML dict with namespace separator.""" + xml_str = bytes(xml_bytes).decode("utf-8", errors="ignore") + try: + return xmltodict.parse(f"{xml_str}", namespace_separator=":") + except Exception as e: + print(f"Error parsing XML: {e}") + return None + + +def clean_keys(obj): + """Recursively remove '@' from dict keys.""" + if isinstance(obj, dict): + return {k.replace("@", ""): clean_keys(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [clean_keys(i) for i in obj] + return obj + + +def build_bids_metadata(custom_attrs): + """Convert custom attribute dict into BIDS microscopy-compliant metadata.""" + # --- PixelSize and Units --- + px_x = float(custom_attrs["DataAxis0"]["@PhysicalUnit"]) + px_y = float(custom_attrs["DataAxis1"]["@PhysicalUnit"]) + px_z = abs(float(custom_attrs["DataAxis2"]["@PhysicalUnit"])) + pixel_size = [px_x, px_y, px_z] + + unit_label = custom_attrs.get("AxesLabels", {}).get("@FirstAxis-Unit", "µm") + unit_label = unit_label.replace("µ", "u") if "µ" in unit_label else unit_label + + # --- Extract Magnification (if present) --- + obj_id = custom_attrs.get("ObjectiveID", {}).get("@ObjectiveID", "") + magnification_match = re.search(r"(\d+(?:\.\d+)?)x", obj_id) + magnification = float(magnification_match.group(1)) if magnification_match else None + + # --- Build BIDS JSON dict --- + bids_json = { + "PixelSize": pixel_size, + "PixelSizeUnits": unit_label, + "Immersion": custom_attrs.get("ObjectiveMedium", {}).get("@ObjectiveMedium"), + "NumericalAperture": float(custom_attrs.get("ObjectiveNA", {}).get("@ObjectiveNA", 0.0)), + "Magnification": magnification, + "OtherAcquisitionParameters": custom_attrs.get("MeasurementMode", {}).get("@MeasurementMode"), + "InstrumentModel": custom_attrs.get("InstrumentMode", {}).get("@InstrumentMode"), + "SoftwareVersions": custom_attrs.get("ImspectorVersion", {}).get("@ImspectorVersion"), + } + + # --- Collect non-BIDS fields into ExtraMetadata --- + excluded = { + "ObjectiveMedium", "ObjectiveNA", "ObjectiveID", "MeasurementMode", + "InstrumentMode", "ImspectorVersion", "DataAxis0", "DataAxis1", "DataAxis2", "AxesLabels" + } + extra_metadata = {k: clean_keys(v) for k, v in custom_attrs.items() if k not in excluded} + bids_json["ExtraMetadata"] = extra_metadata + + return bids_json + + +# ----------------------------- +# Main extraction +# ----------------------------- + with h5py.File(snakemake.input.ims, "r") as hdf5_file: xml_data = hdf5_file["DataSetInfo/OME Image Tags/Image 0"][:] +xml_dict = parse_xml_bytes(xml_data) +if not xml_dict: + raise ValueError("Failed to parse XML from .ims file") + +custom_attrs = xml_dict["root"]["ca:CustomAttributes"] +bids_metadata = build_bids_metadata(custom_attrs) + +# ----------------------------- +# Write to JSON +# ----------------------------- +with open(snakemake.output.metadata_json, "w", encoding="utf-8") as fp: + json.dump(bids_metadata, fp, indent=4, ensure_ascii=False) + -# Convert byte array to string and then to a dictionary -xml_str = bytes(xml_data).decode( - "utf-8", errors="ignore" -) # Decode byte array to string - -try: - xml_dict = xmltodict.parse(f"{xml_str}", namespace_separator=":") -except Exception as e: - print(f"Error parsing XML: {e}") - - -metadata = {} -metadata["physical_size_x"] = float( - xml_dict["root"]["ca:CustomAttributes"]["DataAxis0"]["@PhysicalUnit"] -) -metadata["physical_size_y"] = float( - xml_dict["root"]["ca:CustomAttributes"]["DataAxis1"]["@PhysicalUnit"] -) -metadata["physical_size_z"] = abs( - float(xml_dict["root"]["ca:CustomAttributes"]["DataAxis3"]["@PhysicalUnit"]) -) -metadata["PixelSize"] = [ - metadata["physical_size_z"] / 1000.0, - metadata["physical_size_y"] / 1000.0, - metadata["physical_size_x"] / 1000.0, -] # zyx since OME-Zarr is ZYX -metadata["PixelSizeUnits"] = "mm" - -# write metadata to json -with open(snakemake.output.metadata_json, "w") as fp: - json.dump(metadata, fp, indent=4) From a9b108d15b2382c78a4ab051430c6e2bf7bd1f66 Mon Sep 17 00:00:00 2001 From: "Ali R. Khan" Date: Sat, 8 Nov 2025 21:37:22 -0500 Subject: [PATCH 02/14] linting, update zarrnii, naming for resampled derivatives instead of res-3x use level-3 (like spimquant) --- config/config.yml | 3 +- config/samples.tsv | 11 +- pixi.lock | 308 ++++++++++++++++++++++++- pixi.toml | 2 +- workflow/rules/common.smk | 2 +- workflow/rules/ome_zarr.smk | 11 +- workflow/scripts/generate_volume_qc.py | 1 + workflow/scripts/imaris_to_metadata.py | 36 ++- workflow/scripts/imaris_to_ome_zarr.py | 12 +- workflow/scripts/ome_zarr_to_nii.py | 96 +------- workflow/scripts/zarr_to_ome_zarr.py | 1 + 11 files changed, 354 insertions(+), 129 deletions(-) diff --git a/config/config.yml b/config/config.yml index 4be8ff7..ebec4c3 100644 --- a/config/config.yml +++ b/config/config.yml @@ -1,6 +1,7 @@ samples: 'config/samples.tsv' -root: '/cifs/khan_new/datasets/MIND/mouse_appmaptapoe' # can use a s3:// or gcs:// prefix to write output to cloud storage +root: '' +#cifs/khan_new/datasets/MIND/mouse_appmaptapoe' # can use a s3:// or gcs:// prefix to write output to cloud storage work: "" remote_creds: '~/.config/gcloud/application_default_credentials.json' #this is needed so we can pass creds to container diff --git a/config/samples.tsv b/config/samples.tsv index e0d18a4..e913ee1 100644 --- a/config/samples.tsv +++ b/config/samples.tsv @@ -1,11 +1,2 @@ subject sample acq stain_0 stain_1 stain_2 sample_path -F6A2Te3 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240801_13_F6_A2Te3_68_B_62_4x1_14-47-34 -F4A1Te3 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240802_1_F4_1e3_66_A_61_4x1_14-39-52 -M1A2Te3 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240803_18_M1_A2Te3_73_G_62_4x1_00-10-57 -M4A1Te3 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240828_8_M1_A1Te3_67_H_61_4x1_15-07-06 -F1A2Te3 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240829_14_F1_A2Te3_68_c_62_4x1_01-43-28 -F2A2Te3 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240903_15_F2_A2Te3_68_E_62_4x1_14-23-04 -M3A2Te3 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240904_19_M3_A2Te3_73_I_62_4x1_08-55-39 -F1A1Te4 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240904_4_F1_A1Te4_70_D_63_4x1_23-10-52 -M4A2Te3 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240906_20_M4_A2Te3_73_J_62_4x1_12-53-47 -M7A1Te4 brain blaze Abeta PI Lectin /cifs/trident/projects/mouse_appmaptapoe/lightsheet/sourcedata/newBlaze/4x1/240907_16_M7_A1Te4_71_F_63_4x1_21-07-03 +AS134F3 brain imaris4x Iba1 Abeta YoPro /nfs/trident3/lightsheet/prado/mouse_app_lecanemab_ki3/raw/ims_4x_stitched/A_AS134F3/10-59-07_a- AS134F3 IK3 PBS IBA1 647 ab RedX YoPro 4X1_Blaze_C00.ome.ims diff --git a/pixi.lock b/pixi.lock index 91d9c07..e1c1f19 100644 --- a/pixi.lock +++ b/pixi.lock @@ -426,6 +426,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/4d/4c9ba906175430d6f1cb40b7aa90673720c2c4b3fcea03a3719b1906f983/deepdiff-8.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/27/48/e791a7ed487dbb9729ef32bb5d1af16693d8925f4366befef54119b2e576/furo-2024.8.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/1e/23647191e7532b7d5ec2475537f8e7ec9baee30477180b0f7805d0161d6c/itk_core-5.4.4.post1-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -440,11 +441,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e2/de/21aa8394f16add8f7427f0a1326ccd2b3a2a8a3245c9252bc5ac034c6155/myst_parser-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/84/0fac7104f46c8c0809487b6e973065df95645ab99cb08cf61a9e9dd2aa41/ngff_zarr-0.13.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/21/59baa90924b815b70f88045f0b206b7eab0b68b461c0192692486b516ab7/ome_zarr-0.12.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/02/5bf3639f5b77e9b183011c08541c5039ba3d04f5316c70312b48a8e003a9/pims-0.7.tar.gz - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/31/f6/5fc0574af5379606ffd57a4b68ed88f9b415eb222047fe023aefcc00a648/rich_argparse-1.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e8/ae/fa6cd331b364ad2bbc31652d025f5747d89cbb75576733dfdf8efe3e4d62/slicerator-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl @@ -468,7 +472,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b9/68/a069da6a126e5369656a4170dfb9ab55ed543237205831718a0389da2e43/wasmtime-35.0.0-py3-none-manylinux1_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8a/98/6806760baf20ab206f56a7af25ef4e76a333dabca8473de6641a4afe4b60/zarrnii-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/c4/bb255ae50603117d084c68ec4270999a81a70abe8921fd5249ec23632ad0/zarrnii-0.8.0a1-py3-none-any.whl dev: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -899,6 +903,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/4a/4d/4c9ba906175430d6f1cb40b7aa90673720c2c4b3fcea03a3719b1906f983/deepdiff-8.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/27/48/e791a7ed487dbb9729ef32bb5d1af16693d8925f4366befef54119b2e576/furo-2024.8.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/1e/23647191e7532b7d5ec2475537f8e7ec9baee30477180b0f7805d0161d6c/itk_core-5.4.4.post1-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -913,11 +918,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e2/de/21aa8394f16add8f7427f0a1326ccd2b3a2a8a3245c9252bc5ac034c6155/myst_parser-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/84/0fac7104f46c8c0809487b6e973065df95645ab99cb08cf61a9e9dd2aa41/ngff_zarr-0.13.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4f/21/59baa90924b815b70f88045f0b206b7eab0b68b461c0192692486b516ab7/ome_zarr-0.12.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/02/5bf3639f5b77e9b183011c08541c5039ba3d04f5316c70312b48a8e003a9/pims-0.7.tar.gz - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/31/f6/5fc0574af5379606ffd57a4b68ed88f9b415eb222047fe023aefcc00a648/rich_argparse-1.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e8/ae/fa6cd331b364ad2bbc31652d025f5747d89cbb75576733dfdf8efe3e4d62/slicerator-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl @@ -941,7 +949,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/b9/68/a069da6a126e5369656a4170dfb9ab55ed543237205831718a0389da2e43/wasmtime-35.0.0-py3-none-manylinux1_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/8a/98/6806760baf20ab206f56a7af25ef4e76a333dabca8473de6641a4afe4b60/zarrnii-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/c4/bb255ae50603117d084c68ec4270999a81a70abe8921fd5249ec23632ad0/zarrnii-0.8.0a1-py3-none-any.whl dev-only: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -991,12 +999,18 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.1-pyhe01879c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - pypi: https://files.pythonhosted.org/packages/95/2a/d275ec4ce5cd0096665043995a7d76f5d0524853c76a3d04656de49f8808/aiobotocore-2.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/c5/f6ce561004db45f0b847c2cd9b19c67c6bf348a82018a48cb718be6b58b0/botocore-1.40.61-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl @@ -1007,9 +1021,11 @@ environments: - pypi: https://files.pythonhosted.org/packages/9e/dc/9f033526ed98b65cda8adbd10b6eeeca0659203f67bd3e065ce172008887/distributed-2025.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/27/48/e791a7ed487dbb9729ef32bb5d1af16693d8925f4366befef54119b2e576/furo-2024.8.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d6/1a/f12736807a179a4bd5a26387f2edf00cc3f972139b61f2833f6fbe9b9685/imagecodecs-2025.8.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl @@ -1025,8 +1041,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/be/17/b1a8b50187cdc81a979eb0f23dba13133bc0c4b5a355fa02eac39108134f/itkwasm_image_io-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/18/e284b2918dbd030945a6bad5e7e331d2b9d9fc43e3a9e32eeb320d20ed7a/itkwasm_image_io_wasi-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl @@ -1034,17 +1052,21 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e2/de/21aa8394f16add8f7427f0a1326ccd2b3a2a8a3245c9252bc5ac034c6155/myst_parser-3.0.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/84/0fac7104f46c8c0809487b6e973065df95645ab99cb08cf61a9e9dd2aa41/ngff_zarr-0.13.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/b2/dc384197be44e2a640bb43311850e23c2c30f3b82ce7c8cdabbf0e53045e/nibabel-5.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/64/7177bf632520705893683fa4ca202ed540450bf971c0453ad1351baa2007/numcodecs-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4f/21/59baa90924b815b70f88045f0b206b7eab0b68b461c0192692486b516ab7/ome_zarr-0.12.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/01/e383018feba0a1ead6cf5fe8728e5d767fee02f06a3d800e82c489e5daaf/pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b8/02/5bf3639f5b77e9b183011c08541c5039ba3d04f5316c70312b48a8e003a9/pims-0.7.tar.gz - pypi: https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl @@ -1055,6 +1077,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/31/f6/5fc0574af5379606ffd57a4b68ed88f9b415eb222047fe023aefcc00a648/rich_argparse-1.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/95/ae/5d15c83e337c082d0367053baeb40bfba683f42459f6ebff63a2fd7e5518/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ff/c7/30d13b7fd4f866ca3f30e9a6e7ae038f0c45226f6e26b3cc98d6d197f93b/s3fs-2025.7.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/ae/fa6cd331b364ad2bbc31652d025f5747d89cbb75576733dfdf8efe3e4d62/slicerator-1.1.0-py3-none-any.whl @@ -1087,8 +1111,10 @@ environments: - pypi: https://files.pythonhosted.org/packages/b9/68/a069da6a126e5369656a4170dfb9ab55ed543237205831718a0389da2e43/wasmtime-35.0.0-py3-none-manylinux1_x86_64.whl - pypi: https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c8/48/bde2f58cfbc9fd6ab844e2f2fd79d5e54195c12a17aa9b47c0b0e701a421/zarr-3.1.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/8a/98/6806760baf20ab206f56a7af25ef4e76a333dabca8473de6641a4afe4b60/zarrnii-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9c/c4/bb255ae50603117d084c68ec4270999a81a70abe8921fd5249ec23632ad0/zarrnii-0.8.0a1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/ab/11a76c1e2126084fde2639514f24e6111b789b0bfa4fc6264a8975c7e1f1/zict-3.0.0-py2.py3-none-any.whl packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -1124,6 +1150,22 @@ packages: - pkg:pypi/acres?source=hash-mapping size: 17303 timestamp: 1749066931849 +- pypi: https://files.pythonhosted.org/packages/95/2a/d275ec4ce5cd0096665043995a7d76f5d0524853c76a3d04656de49f8808/aiobotocore-2.25.1-py3-none-any.whl + name: aiobotocore + version: 2.25.1 + sha256: eb6daebe3cbef5b39a0bb2a97cffbe9c7cb46b2fcc399ad141f369f3c2134b1f + requires_dist: + - aiohttp>=3.9.2,<4.0.0 + - aioitertools>=0.5.1,<1.0.0 + - botocore>=1.40.46,<1.40.62 + - python-dateutil>=2.1,<3.0.0 + - jmespath>=0.7.1,<2.0.0 + - multidict>=6.0.0,<7.0.0 + - wrapt>=1.10.10,<2.0.0 + - awscli>=1.42.46,<1.42.62 ; extra == 'awscli' + - boto3>=1.40.46,<1.40.62 ; extra == 'boto3' + - httpx>=0.25.1,<0.29 ; extra == 'httpx' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/aiobotocore-2.24.0-pyhe01879c_0.conda sha256: 98e86e1bbbdfcd6318a4118cd151975bf726e415e8cf5b2207f82a112859e84e md5: 4c5cb2b7c40dd2a3bd3b68547fd9412d @@ -1143,6 +1185,11 @@ packages: - pkg:pypi/aiobotocore?source=hash-mapping size: 79596 timestamp: 1754943905140 +- pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl + name: aiohappyeyeballs + version: 2.6.1 + sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/aiohappyeyeballs-2.6.1-pyhd8ed1ab_0.conda sha256: 7842ddc678e77868ba7b92a726b437575b23aaec293bca0d40826f1026d90e27 md5: 18fd895e0e775622906cdabfc3cf0fb4 @@ -1154,6 +1201,24 @@ packages: - pkg:pypi/aiohappyeyeballs?source=hash-mapping size: 19750 timestamp: 1741775303303 +- pypi: https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: aiohttp + version: 3.13.2 + sha256: 4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e + requires_dist: + - aiohappyeyeballs>=2.5.0 + - aiosignal>=1.4.0 + - async-timeout>=4.0,<6.0 ; python_full_version < '3.11' + - attrs>=17.3.0 + - frozenlist>=1.1.1 + - multidict>=4.5,<7.0 + - propcache>=0.2.0 + - yarl>=1.17.0,<2.0 + - aiodns>=3.3.0 ; extra == 'speedups' + - brotli ; platform_python_implementation == 'CPython' and extra == 'speedups' + - brotlicffi ; platform_python_implementation != 'CPython' and extra == 'speedups' + - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/aiohttp-3.12.15-py312h8a5da7c_0.conda sha256: 524f7e7bcf2f68ec69fc4e097373b2affee48419b1568b9b7c60c09fca260caf md5: 26123b7166da2af08afb6172b5a4806c @@ -1175,6 +1240,13 @@ packages: - pkg:pypi/aiohttp?source=hash-mapping size: 1007572 timestamp: 1753805448349 +- pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl + name: aioitertools + version: 0.13.0 + sha256: 0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be + requires_dist: + - typing-extensions>=4.0 ; python_full_version < '3.10' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/aioitertools-0.12.0-pyhd8ed1ab_1.conda sha256: 7d56e547a819a03c058dd8793ca9df6ff9825812da52c214192edb61a7de1c95 md5: 3eb47adbffac44483f59e580f8600a1e @@ -1187,6 +1259,14 @@ packages: - pkg:pypi/aioitertools?source=hash-mapping size: 25063 timestamp: 1735329177103 +- pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl + name: aiosignal + version: 1.4.0 + sha256: 053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e + requires_dist: + - frozenlist>=1.1.0 + - typing-extensions>=4.2 ; python_full_version < '3.13' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/aiosignal-1.4.0-pyhd8ed1ab_0.conda sha256: 8dc149a6828d19bf104ea96382a9d04dae185d4a03cc6beb1bc7b84c428e3ca2 md5: 421a865222cd0c9d83ff08bc78bf3a61 @@ -1817,6 +1897,17 @@ packages: - pkg:pypi/boto3?source=hash-mapping size: 83978 timestamp: 1753315235669 +- pypi: https://files.pythonhosted.org/packages/38/c5/f6ce561004db45f0b847c2cd9b19c67c6bf348a82018a48cb718be6b58b0/botocore-1.40.61-py3-none-any.whl + name: botocore + version: 1.40.61 + sha256: 17ebae412692fd4824f99cde0f08d50126dc97954008e5ba2b522eb049238aa7 + requires_dist: + - jmespath>=0.7.1,<2.0.0 + - python-dateutil>=2.1,<3.0.0 + - urllib3>=1.25.4,<1.27 ; python_full_version < '3.10' + - urllib3>=1.25.4,!=2.2.0,<3 ; python_full_version >= '3.10' + - awscrt==0.27.6 ; extra == 'crt' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/botocore-1.39.11-pyge310_1234567_0.conda sha256: 8cfa5e9b6f8b8a0e1470208f53f4b0f39a8cbf90658b3c495f621097201aee47 md5: 568d1e4d62e6d0335e89f41b831bb4a8 @@ -2915,6 +3006,11 @@ packages: - pkg:pypi/frozendict?source=hash-mapping size: 30486 timestamp: 1728841445822 +- pypi: https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: frozenlist + version: 1.8.0 + sha256: 494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/frozenlist-1.7.0-py312h447239a_0.conda sha256: f4e0e6cd241bc24afb2d6d08e5d2ba170fad2475e522bdf297b7271bba268be6 md5: 63e20cf7b7460019b423fc06abb96c60 @@ -3342,6 +3438,13 @@ packages: - pkg:pypi/h2?source=hash-mapping size: 53888 timestamp: 1738578623567 +- pypi: https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: h5py + version: 3.15.1 + sha256: 25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.1-h15599e2_0.conda sha256: b43e4f3c70eca82d733eb26bb8f031552f30fa4fb24c9455555a8a1baba6e1cc md5: 7da3b5c281ded5bb6a634e1fe7d3272f @@ -3806,6 +3909,11 @@ packages: - pkg:pypi/jinja2-time?source=hash-mapping size: 11589 timestamp: 1735140783385 +- pypi: https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl + name: jmespath + version: 1.0.1 + sha256: 02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 + requires_python: '>=3.7' - conda: https://conda.anaconda.org/conda-forge/noarch/jmespath-1.0.1-pyhd8ed1ab_1.conda sha256: 3d2f20ee7fd731e3ff55c189db9c43231bc8bde957875817a609c227bcb295c6 md5: 972bdca8f30147135f951847b30399ea @@ -3952,6 +4060,18 @@ packages: purls: [] size: 1370023 timestamp: 1719463201255 +- pypi: https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl + name: lazy-loader + version: '0.4' + sha256: 342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc + requires_dist: + - packaging + - importlib-metadata ; python_full_version < '3.8' + - changelist==0.5 ; extra == 'dev' + - pre-commit==3.7.0 ; extra == 'lint' + - pytest>=7.4 ; extra == 'test' + - pytest-cov>=4.1 ; extra == 'test' + requires_python: '>=3.7' - conda: https://conda.anaconda.org/conda-forge/noarch/lazy-loader-0.4-pyhd8ed1ab_2.conda sha256: d7ea986507090fff801604867ef8e79c8fda8ec21314ba27c032ab18df9c3411 md5: d10d9393680734a8febc4b362a4c94f2 @@ -5169,6 +5289,13 @@ packages: - pkg:pypi/msgpack?source=hash-mapping size: 102924 timestamp: 1749813333354 +- pypi: https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: multidict + version: 6.7.0 + sha256: 123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d + requires_dist: + - typing-extensions>=4.1.0 ; python_full_version < '3.11' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/multidict-6.6.3-py312h178313f_0.conda sha256: c703d148a85ffb4f11001d31b7c4c686a46ad554eeeaa02c69da59fbf0e00dbb md5: f4e246ec4ccdf73e50eefb0fa359a64e @@ -5267,6 +5394,42 @@ packages: purls: [] size: 891641 timestamp: 1738195959188 +- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl + name: networkx + version: '3.5' + sha256: 0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec + requires_dist: + - numpy>=1.25 ; extra == 'default' + - scipy>=1.11.2 ; extra == 'default' + - matplotlib>=3.8 ; extra == 'default' + - pandas>=2.0 ; extra == 'default' + - pre-commit>=4.1 ; extra == 'developer' + - mypy>=1.15 ; extra == 'developer' + - sphinx>=8.0 ; extra == 'doc' + - pydata-sphinx-theme>=0.16 ; extra == 'doc' + - sphinx-gallery>=0.18 ; extra == 'doc' + - numpydoc>=1.8.0 ; extra == 'doc' + - pillow>=10 ; extra == 'doc' + - texext>=0.6.7 ; extra == 'doc' + - myst-nb>=1.1 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - osmnx>=2.0.0 ; extra == 'example' + - momepy>=0.7.2 ; extra == 'example' + - contextily>=1.6 ; extra == 'example' + - seaborn>=0.13 ; extra == 'example' + - cairocffi>=1.7 ; extra == 'example' + - igraph>=0.11 ; extra == 'example' + - scikit-learn>=1.5 ; extra == 'example' + - lxml>=4.6 ; extra == 'extra' + - pygraphviz>=1.14 ; extra == 'extra' + - pydot>=3.0.1 ; extra == 'extra' + - sympy>=1.10 ; extra == 'extra' + - pytest>=7.2 ; extra == 'test' + - pytest-cov>=4.0 ; extra == 'test' + - pytest-xdist>=3.0 ; extra == 'test' + - pytest-mpl ; extra == 'test-extras' + - pytest-randomly ; extra == 'test-extras' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/ec/84/0fac7104f46c8c0809487b6e973065df95645ab99cb08cf61a9e9dd2aa41/ngff_zarr-0.13.4-py3-none-any.whl name: ngff-zarr version: 0.13.4 @@ -5482,6 +5645,21 @@ packages: - pkg:pypi/oauthlib?source=hash-mapping size: 102059 timestamp: 1750415349440 +- pypi: https://files.pythonhosted.org/packages/4f/21/59baa90924b815b70f88045f0b206b7eab0b68b461c0192692486b516ab7/ome_zarr-0.12.2-py3-none-any.whl + name: ome-zarr + version: 0.12.2 + sha256: 655fe1b11ca01148603f9931a5b0af31207dfc03a3a35f9b0ab8639790282bbd + requires_dist: + - numpy + - dask + - zarr>=3.0.0 + - fsspec[s3]>=0.8,!=2021.7.0,!=2023.9.0 + - aiohttp + - requests + - scikit-image>=0.19.0 + - toolz + - pytest ; extra == 'tests' + requires_python: '>3.10' - conda: https://conda.anaconda.org/conda-forge/linux-64/openjdk-23.0.2-h53dfc1b_2.conda sha256: aac6fe6db0841e77f832fc21132ac7ebec1a9b5bae004b5e69e3a210e53e3bf8 md5: 47eea31e0c3f960459237823e5e21a32 @@ -6015,6 +6193,11 @@ packages: purls: [] size: 7182 timestamp: 1744724189376 +- pypi: https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: propcache + version: 0.4.1 + sha256: 15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/propcache-0.3.1-py312h178313f_0.conda sha256: d0ff67d89cf379a9f0367f563320621f0bc3969fe7f5c85e020f437de0927bb4 md5: 0cf580c1b73146bb9ff1bbdb4d4c8cf9 @@ -6808,6 +6991,17 @@ packages: purls: [] size: 357537 timestamp: 1751932188890 +- pypi: https://files.pythonhosted.org/packages/ff/c7/30d13b7fd4f866ca3f30e9a6e7ae038f0c45226f6e26b3cc98d6d197f93b/s3fs-2025.7.0-py3-none-any.whl + name: s3fs + version: 2025.7.0 + sha256: b6b2d3f84b6aa1c2ba5e62e39dd9410cf54f10a2cce1ea6db1ba0d1a6bcce685 + requires_dist: + - aiobotocore>=2.5.4,<3.0.0 + - fsspec==2025.7.0 + - aiohttp!=4.0.0a0,!=4.0.0a1 + - aiobotocore[awscli]>=2.5.4,<3.0.0 ; extra == 'awscli' + - aiobotocore[boto3]>=2.5.4,<3.0.0 ; extra == 'boto3' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/s3fs-2025.7.0-pyhd8ed1ab_0.conda sha256: 38addc258cf69e39c2df88c4c4995a2108ad782437bbae357b69493216c813db md5: 00502db3bc89fb08171b34ccfcbf8046 @@ -6834,6 +7028,69 @@ packages: - pkg:pypi/s3transfer?source=hash-mapping size: 65715 timestamp: 1752878105783 +- pypi: https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: scikit-image + version: 0.25.2 + sha256: a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824 + requires_dist: + - numpy>=1.24 + - scipy>=1.11.4 + - networkx>=3.0 + - pillow>=10.1 + - imageio>=2.33,!=2.35.0 + - tifffile>=2022.8.12 + - packaging>=21 + - lazy-loader>=0.4 + - meson-python>=0.16 ; extra == 'build' + - ninja>=1.11.1.1 ; extra == 'build' + - cython>=3.0.8 ; extra == 'build' + - pythran>=0.16 ; extra == 'build' + - numpy>=2.0 ; extra == 'build' + - spin==0.13 ; extra == 'build' + - build>=1.2.1 ; extra == 'build' + - pooch>=1.6.0 ; extra == 'data' + - pre-commit ; extra == 'developer' + - ipython ; extra == 'developer' + - tomli ; python_full_version < '3.11' and extra == 'developer' + - sphinx>=8.0 ; extra == 'docs' + - sphinx-gallery[parallel]>=0.18 ; extra == 'docs' + - numpydoc>=1.7 ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - matplotlib>=3.7 ; extra == 'docs' + - dask[array]>=2023.2.0 ; extra == 'docs' + - pandas>=2.0 ; extra == 'docs' + - seaborn>=0.11 ; extra == 'docs' + - pooch>=1.6 ; extra == 'docs' + - tifffile>=2022.8.12 ; extra == 'docs' + - myst-parser ; extra == 'docs' + - intersphinx-registry>=0.2411.14 ; extra == 'docs' + - ipywidgets ; extra == 'docs' + - ipykernel ; extra == 'docs' + - plotly>=5.20 ; extra == 'docs' + - kaleido==0.2.1 ; extra == 'docs' + - scikit-learn>=1.2 ; extra == 'docs' + - sphinx-design>=0.5 ; extra == 'docs' + - pydata-sphinx-theme>=0.16 ; extra == 'docs' + - pywavelets>=1.6 ; extra == 'docs' + - pytest-doctestplus ; extra == 'docs' + - simpleitk ; extra == 'optional' + - astropy>=5.0 ; extra == 'optional' + - cloudpickle>=1.1.1 ; extra == 'optional' + - dask[array]>=2023.2.0 ; extra == 'optional' + - matplotlib>=3.7 ; extra == 'optional' + - pooch>=1.6.0 ; extra == 'optional' + - pyamg>=5.2 ; extra == 'optional' + - pywavelets>=1.6 ; extra == 'optional' + - scikit-learn>=1.2 ; extra == 'optional' + - asv ; extra == 'test' + - numpydoc>=1.7 ; extra == 'test' + - pooch>=1.6.0 ; extra == 'test' + - pytest>=8 ; extra == 'test' + - pytest-cov>=2.11.0 ; extra == 'test' + - pytest-localserver ; extra == 'test' + - pytest-faulthandler ; extra == 'test' + - pytest-doctestplus ; extra == 'test' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl name: scipy version: 1.16.1 @@ -7973,6 +8230,11 @@ packages: version: 15.0.1 sha256: 64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + name: wrapt + version: 1.17.3 + sha256: 042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828 + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/linux-64/wrapt-1.17.3-py312h4c3975b_0.conda sha256: af711a6449d2ca3fa4c245dee78665050c6ff3a08e8ea5d4bed8472f290c8b67 md5: 28f4b2672dab90c896adf9acf2b774c1 @@ -8194,6 +8456,15 @@ packages: purls: [] size: 223526 timestamp: 1745307989800 +- pypi: https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: yarl + version: 1.22.0 + sha256: 50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a + requires_dist: + - idna>=2.0 + - multidict>=4.0 + - propcache>=0.2.1 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/yarl-1.20.1-py312h178313f_0.conda sha256: f5c2c572423fac9ea74512f96a7c002c81fd2eb260608cfa1edfaeda4d81582e md5: 3b3fa80c71d6a8d0380e9e790f5a4a8a @@ -8295,19 +8566,42 @@ packages: - pkg:pypi/zarr?source=hash-mapping size: 267884 timestamp: 1753948848920 -- pypi: https://files.pythonhosted.org/packages/8a/98/6806760baf20ab206f56a7af25ef4e76a333dabca8473de6641a4afe4b60/zarrnii-0.3.0a1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/9c/c4/bb255ae50603117d084c68ec4270999a81a70abe8921fd5249ec23632ad0/zarrnii-0.8.0a1-py3-none-any.whl name: zarrnii - version: 0.3.0a1 - sha256: d8647cfc62b7cccf0d323cf2f9e6b1ca37a58e1708e1d130b21259fd4f7fc673 + version: 0.8.0a1 + sha256: f9b5fc77873ac5086096829d7e124cc17c3acbc14e147e082ee08e697fe023df requires_dist: - dask>=2025.5.1 + - h5py>=3.14.0 - ngff-zarr[all]>=0.13.1 - nibabel>=5.2.0 - numpy>=1.26.4 + - ome-zarr>=0.12.0 - pandas>=2.2.0 + - pyyaml>=6.0 + - scikit-image>=0.22.0 - scipy>=1.12.0 - zarr>=3.0.8 - requires_python: '>=3.11,<4.0' + - black>=24.10.0 ; extra == 'dev' + - bokeh>=3.4.1 ; extra == 'dev' + - flake8-bugbear>=24.12.12 ; extra == 'dev' + - flake8-docstrings>=1.7.0 ; extra == 'dev' + - flake8-import-order>=0.18.2 ; extra == 'dev' + - flake8>=7.3.0 ; extra == 'dev' + - isort>=5.13.2 ; extra == 'dev' + - jupyterlab>=4.2.1 ; extra == 'dev' + - matplotlib>=3.9.0 ; extra == 'dev' + - mkdocs-material>=9.5.50 ; extra == 'dev' + - mkdocs>=1.6.1 ; extra == 'dev' + - mkdocstrings-python>=1.13.0 ; extra == 'dev' + - mkdocstrings>=0.27.0 ; extra == 'dev' + - pre-commit>=4.0.0 ; extra == 'dev' + - pytest-cov>=6.0.0 ; extra == 'dev' + - pytest>=8.2.0 ; extra == 'dev' + - h5py>=3.8.0 ; extra == 'imaris' + - antspyx>=0.5.0 ; extra == 'n4' + - templateflow>=0.8.0 ; extra == 'templateflow' + requires_python: '>=3.11' - conda: https://conda.anaconda.org/conda-forge/linux-64/zfp-1.0.1-h5888daf_2.conda sha256: 0dfafc75c72f308c0200836f2b973766cdcb8741b1ab61e0b462a34dd6b6ad20 md5: e0409515c467b87176b070bff5d9442e diff --git a/pixi.toml b/pixi.toml index 4c275ae..c3a00c7 100644 --- a/pixi.toml +++ b/pixi.toml @@ -59,4 +59,4 @@ features = ["dev"] [pypi-dependencies] ngff-zarr = { version = ">=0.13.2, <0.14", extras = ["all"] } -zarrnii = "==0.3.0-a1" +zarrnii = ">=0.8.0a1,<0.9.0" diff --git a/workflow/rules/common.smk b/workflow/rules/common.smk index a46a976..e0d2141 100644 --- a/workflow/rules/common.smk +++ b/workflow/rules/common.smk @@ -109,7 +109,7 @@ def get_all_targets(): datatype="micr", sample="{sample}", acq="{acq}", - res="{level}x", + level="{level}", stain="{stain}", suffix="SPIM.nii", expand_kwargs=dict( diff --git a/workflow/rules/ome_zarr.smk b/workflow/rules/ome_zarr.smk index 49a4c8f..d788243 100644 --- a/workflow/rules/ome_zarr.smk +++ b/workflow/rules/ome_zarr.smk @@ -90,12 +90,7 @@ rule tif_stacks_to_ome_zarr: rule ome_zarr_to_nii: input: - **get_storage_creds(), zarr=get_input_ome_zarr_to_nii, - params: - channel_index=lambda wildcards: get_stains(wildcards).index(wildcards.stain), - uri=get_output_ome_zarr_uri(), - storage_provider_settings=workflow.storage_provider_settings, output: nii=bids( root=resampled, @@ -103,7 +98,7 @@ rule ome_zarr_to_nii: datatype="micr", sample="{sample}", acq="{acq}", - res="{level}x", + level="{level}", stain="{stain}", suffix="SPIM.nii", ), @@ -114,7 +109,7 @@ rule ome_zarr_to_nii: subject="{subject}", sample="{sample}", acq="{acq}", - res="{level}x", + level="{level}", stain="{stain}", suffix="benchmark.tsv", ) @@ -125,7 +120,7 @@ rule ome_zarr_to_nii: subject="{subject}", sample="{sample}", acq="{acq}", - res="{level}x", + level="{level}", stain="{stain}", suffix="log.txt", ), diff --git a/workflow/scripts/generate_volume_qc.py b/workflow/scripts/generate_volume_qc.py index db0b86b..0d8a0b6 100644 --- a/workflow/scripts/generate_volume_qc.py +++ b/workflow/scripts/generate_volume_qc.py @@ -8,6 +8,7 @@ import zarr from lib.cloud_io import get_fsspec, is_remote from upath import UPath as Path + from zarrnii import ZarrNii # directory containing the volume rendering files diff --git a/workflow/scripts/imaris_to_metadata.py b/workflow/scripts/imaris_to_metadata.py index 4853c41..cb059b0 100644 --- a/workflow/scripts/imaris_to_metadata.py +++ b/workflow/scripts/imaris_to_metadata.py @@ -1,5 +1,6 @@ import json import re + import h5py import xmltodict @@ -7,6 +8,7 @@ # Helper functions # ----------------------------- + def parse_xml_bytes(xml_bytes): """Convert raw byte array to parsed XML dict with namespace separator.""" xml_str = bytes(xml_bytes).decode("utf-8", errors="ignore") @@ -47,19 +49,37 @@ def build_bids_metadata(custom_attrs): "PixelSize": pixel_size, "PixelSizeUnits": unit_label, "Immersion": custom_attrs.get("ObjectiveMedium", {}).get("@ObjectiveMedium"), - "NumericalAperture": float(custom_attrs.get("ObjectiveNA", {}).get("@ObjectiveNA", 0.0)), + "NumericalAperture": float( + custom_attrs.get("ObjectiveNA", {}).get("@ObjectiveNA", 0.0) + ), "Magnification": magnification, - "OtherAcquisitionParameters": custom_attrs.get("MeasurementMode", {}).get("@MeasurementMode"), - "InstrumentModel": custom_attrs.get("InstrumentMode", {}).get("@InstrumentMode"), - "SoftwareVersions": custom_attrs.get("ImspectorVersion", {}).get("@ImspectorVersion"), + "OtherAcquisitionParameters": custom_attrs.get("MeasurementMode", {}).get( + "@MeasurementMode" + ), + "InstrumentModel": custom_attrs.get("InstrumentMode", {}).get( + "@InstrumentMode" + ), + "SoftwareVersions": custom_attrs.get("ImspectorVersion", {}).get( + "@ImspectorVersion" + ), } # --- Collect non-BIDS fields into ExtraMetadata --- excluded = { - "ObjectiveMedium", "ObjectiveNA", "ObjectiveID", "MeasurementMode", - "InstrumentMode", "ImspectorVersion", "DataAxis0", "DataAxis1", "DataAxis2", "AxesLabels" + "ObjectiveMedium", + "ObjectiveNA", + "ObjectiveID", + "MeasurementMode", + "InstrumentMode", + "ImspectorVersion", + "DataAxis0", + "DataAxis1", + "DataAxis2", + "AxesLabels", + } + extra_metadata = { + k: clean_keys(v) for k, v in custom_attrs.items() if k not in excluded } - extra_metadata = {k: clean_keys(v) for k, v in custom_attrs.items() if k not in excluded} bids_json["ExtraMetadata"] = extra_metadata return bids_json @@ -84,5 +104,3 @@ def build_bids_metadata(custom_attrs): # ----------------------------- with open(snakemake.output.metadata_json, "w", encoding="utf-8") as fp: json.dump(bids_metadata, fp, indent=4, ensure_ascii=False) - - diff --git a/workflow/scripts/imaris_to_ome_zarr.py b/workflow/scripts/imaris_to_ome_zarr.py index f2299ca..d463913 100644 --- a/workflow/scripts/imaris_to_ome_zarr.py +++ b/workflow/scripts/imaris_to_ome_zarr.py @@ -25,10 +25,16 @@ with open(metadata_json) as fp: metadata = json.load(fp) +units = metadata.get("PixelSizeUnits", "um") +if units == "um": + unit_convert = 1000.0 +elif units == "mm": + unit_convert = 1.0 + voxdim = [ - float(metadata["physical_size_z"]) * float(downsampling) / 1000.0, - float(metadata["physical_size_y"]) * float(downsampling) / 1000.0, - float(metadata["physical_size_x"]) * float(downsampling) / 1000.0, + float(metadata["PixelSize"][2]) * float(downsampling) / unit_convert, + float(metadata["PixelSize"][1]) * float(downsampling) / unit_convert, + float(metadata["PixelSize"][0]) * float(downsampling) / unit_convert, ] diff --git a/workflow/scripts/ome_zarr_to_nii.py b/workflow/scripts/ome_zarr_to_nii.py index c4eb675..59fa7dd 100644 --- a/workflow/scripts/ome_zarr_to_nii.py +++ b/workflow/scripts/ome_zarr_to_nii.py @@ -1,98 +1,16 @@ -import zarr from dask.diagnostics import ProgressBar -from upath import UPath as Path -from zarrnii import ZarrNii - -uri = snakemake.params.uri -in_zarr = snakemake.input.zarr -channel_index = snakemake.params.channel_index - -if Path(uri).suffix == ".zip": - store = zarr.storage.ZipStore(Path(uri).path, mode="r") -else: - store = zarr.storage.LocalStore(Path(uri).path, read_only=True) +from zarrnii import ZarrNii znimg = ZarrNii.from_ome_zarr( - store, level=int(snakemake.wildcards.level), channels=[channel_index] + snakemake.input.zarr, + level=int(snakemake.wildcards.level), + channel_labels=[snakemake.wildcards.stain], + downsample_near_isotropic=True, + **snakemake.params.zarrnii_kwargs, ) - -# before updating zarrnii ngffzarr3 branch to accommodate anisotropically downsampled data, instead -# we will calculate the z downsampling factor and downsample accordingly - TODO: move this to zarrnii - -import numpy as np - -# Get scale and axes order -scale = znimg.coordinate_transformations[0].scale - -axes = znimg.axes # list of Axis objects - -# Build a mapping from axis name to index -axis_index = {axis.name.lower(): i for i, axis in enumerate(axes)} - -# Extract x and z scales -x_scale = scale[axis_index["x"]] -z_scale = scale[axis_index["z"]] - -# Compute ratio and power -ratio = x_scale / z_scale -level = int(np.log2(round(ratio))) - - with ProgressBar(): - if level == 0: - znimg.to_nifti(snakemake.output.nii) - else: - znimg.downsample(along_z=2**level).to_nifti(snakemake.output.nii) - - -""" - -if is_remote(uri): - fs_args={'storage_provider_settings':snakemake.params.storage_provider_settings,'creds':snakemake.input.creds} -else: - fs_args={} - -fs = get_fsspec(uri,**fs_args) - -if Path(uri).suffix == '.zip': - store = zarr.storage.ZipStore(Path(uri).path,dimension_separator='/',mode='r') -else: - store = zarr.storage.FSStore(Path(uri).path,fs=fs,dimension_separator='/',mode='r') - -zi = zarr.open(store=store,mode='r') - - -attrs=zi['/'].attrs.asdict() - -level=int(snakemake.wildcards.level) - -#read coordinate transform from ome-zarr -transforms = attrs['multiscales'][0]['datasets'][level]['coordinateTransformations'] - - -#zarr uses z,y,x ordering, we reverse this for nifti -# also flip to set orientation properly -affine = np.eye(4) -affine[0,0]=-transforms[0]['scale'][3] #x -affine[1,1]=-transforms[0]['scale'][2] #y -affine[2,2]=-transforms[0]['scale'][1] #z - -if is_remote(uri): - #grab the channel index corresponding to the stain - darr = da.from_zarr(store,component=f'/{level}')[channel_index,:,:,:].squeeze() -else: - darr = da.from_zarr(in_zarr,component=f'/{level}')[channel_index,:,:,:].squeeze() - -#input array axes are ZYX -#writing to nifti we want XYZ -out_arr = np.moveaxis(darr,(0,1,2),(2,1,0)) + znimg.to_nifti(snakemake.output.nii) -nii = nib.Nifti1Image(out_arr, - affine=affine - ) - -nii.to_filename(snakemake.output.nii) -""" diff --git a/workflow/scripts/zarr_to_ome_zarr.py b/workflow/scripts/zarr_to_ome_zarr.py index 88e0ba0..4ec3863 100644 --- a/workflow/scripts/zarr_to_ome_zarr.py +++ b/workflow/scripts/zarr_to_ome_zarr.py @@ -5,6 +5,7 @@ from dask.diagnostics import ProgressBar from lib.cloud_io import get_fsspec, is_remote from upath import UPath as Path + from zarrnii import ZarrNii in_zarr = snakemake.input.zarr From 0283eb6e91dec7f245eafead7acdddbd26722bb1 Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Sat, 8 Nov 2025 22:07:18 -0500 Subject: [PATCH 03/14] add another test subj --- config/samples.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/config/samples.tsv b/config/samples.tsv index e913ee1..2805c91 100644 --- a/config/samples.tsv +++ b/config/samples.tsv @@ -1,2 +1,3 @@ subject sample acq stain_0 stain_1 stain_2 sample_path AS134F3 brain imaris4x Iba1 Abeta YoPro /nfs/trident3/lightsheet/prado/mouse_app_lecanemab_ki3/raw/ims_4x_stitched/A_AS134F3/10-59-07_a- AS134F3 IK3 PBS IBA1 647 ab RedX YoPro 4X1_Blaze_C00.ome.ims +AS135M5 brain imaris4x Iba1 Abeta YoPro /nfs/trident3/lightsheet/prado/mouse_app_lecanemab_ki3/raw/ims_4x_stitched/A_AS134F3/10-59-07_a- AS134F3 IK3 PBS IBA1 647 ab RedX YoPro 4X1_Blaze_C00.ome.ims From 843c2c980dd1d4b389c5b071f53563c7c00bbd97 Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Sun, 9 Nov 2025 12:58:57 -0500 Subject: [PATCH 04/14] update versions --- pixi.lock | 28 +++++++++++++++------------- pixi.toml | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pixi.lock b/pixi.lock index e1c1f19..2bd08f4 100644 --- a/pixi.lock +++ b/pixi.lock @@ -434,7 +434,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/9c/a9/33f799db5e6cb53755753d4ce44712b7019e94c4a6d3a0073ef20054e9c4/itk_io-5.4.4.post1-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6c/6a/2868a0cd0ae08506fede1ae37a251da10fa25b9ab56c173e26370d3a0d13/itk_numerics-5.4.4.post1-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1d/df/5ba47e12638ac537a8166a454c497032af83f2d5eb826bca81024ac8f78c/itkwasm-1.0b195-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/fc/0e70d69ca0aaeb968aafb63a04692d3d4e8a27ccd4f2dc4836d7fb1301c7/itkwasm_downsample-1.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c6/906a1c94a9e0d12b6cbc8a36d6fe284d06298d3d3eda14934d21609845ab/itkwasm_downsample-1.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/3f/08249cbd0370eeb762c0f3d706a70f8e8aab5a98df5765568001d2dc3d34/itkwasm_downsample_wasi-1.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/17/b1a8b50187cdc81a979eb0f23dba13133bc0c4b5a355fa02eac39108134f/itkwasm_image_io-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/18/e284b2918dbd030945a6bad5e7e331d2b9d9fc43e3a9e32eeb320d20ed7a/itkwasm_image_io_wasi-1.6.0-py3-none-any.whl @@ -442,7 +442,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e2/de/21aa8394f16add8f7427f0a1326ccd2b3a2a8a3245c9252bc5ac034c6155/myst_parser-3.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ec/84/0fac7104f46c8c0809487b6e973065df95645ab99cb08cf61a9e9dd2aa41/ngff_zarr-0.13.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/b8/c72c98004fac5e494a81570887d5cc06c6daa8898f32dc264b3ea6f17eb8/ngff_zarr-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/21/59baa90924b815b70f88045f0b206b7eab0b68b461c0192692486b516ab7/ome_zarr-0.12.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/02/5bf3639f5b77e9b183011c08541c5039ba3d04f5316c70312b48a8e003a9/pims-0.7.tar.gz @@ -911,7 +911,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/9c/a9/33f799db5e6cb53755753d4ce44712b7019e94c4a6d3a0073ef20054e9c4/itk_io-5.4.4.post1-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6c/6a/2868a0cd0ae08506fede1ae37a251da10fa25b9ab56c173e26370d3a0d13/itk_numerics-5.4.4.post1-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1d/df/5ba47e12638ac537a8166a454c497032af83f2d5eb826bca81024ac8f78c/itkwasm-1.0b195-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/fc/0e70d69ca0aaeb968aafb63a04692d3d4e8a27ccd4f2dc4836d7fb1301c7/itkwasm_downsample-1.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c6/906a1c94a9e0d12b6cbc8a36d6fe284d06298d3d3eda14934d21609845ab/itkwasm_downsample-1.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/3f/08249cbd0370eeb762c0f3d706a70f8e8aab5a98df5765568001d2dc3d34/itkwasm_downsample_wasi-1.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/17/b1a8b50187cdc81a979eb0f23dba13133bc0c4b5a355fa02eac39108134f/itkwasm_image_io-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/18/e284b2918dbd030945a6bad5e7e331d2b9d9fc43e3a9e32eeb320d20ed7a/itkwasm_image_io_wasi-1.6.0-py3-none-any.whl @@ -919,7 +919,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e2/de/21aa8394f16add8f7427f0a1326ccd2b3a2a8a3245c9252bc5ac034c6155/myst_parser-3.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ec/84/0fac7104f46c8c0809487b6e973065df95645ab99cb08cf61a9e9dd2aa41/ngff_zarr-0.13.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/b8/c72c98004fac5e494a81570887d5cc06c6daa8898f32dc264b3ea6f17eb8/ngff_zarr-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/21/59baa90924b815b70f88045f0b206b7eab0b68b461c0192692486b516ab7/ome_zarr-0.12.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/02/5bf3639f5b77e9b183011c08541c5039ba3d04f5316c70312b48a8e003a9/pims-0.7.tar.gz @@ -1036,7 +1036,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/9c/a9/33f799db5e6cb53755753d4ce44712b7019e94c4a6d3a0073ef20054e9c4/itk_io-5.4.4.post1-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/6c/6a/2868a0cd0ae08506fede1ae37a251da10fa25b9ab56c173e26370d3a0d13/itk_numerics-5.4.4.post1-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1d/df/5ba47e12638ac537a8166a454c497032af83f2d5eb826bca81024ac8f78c/itkwasm-1.0b195-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/16/fc/0e70d69ca0aaeb968aafb63a04692d3d4e8a27ccd4f2dc4836d7fb1301c7/itkwasm_downsample-1.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/12/c6/906a1c94a9e0d12b6cbc8a36d6fe284d06298d3d3eda14934d21609845ab/itkwasm_downsample-1.8.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/68/3f/08249cbd0370eeb762c0f3d706a70f8e8aab5a98df5765568001d2dc3d34/itkwasm_downsample_wasi-1.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/17/b1a8b50187cdc81a979eb0f23dba13133bc0c4b5a355fa02eac39108134f/itkwasm_image_io-1.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/18/e284b2918dbd030945a6bad5e7e331d2b9d9fc43e3a9e32eeb320d20ed7a/itkwasm_image_io_wasi-1.6.0-py3-none-any.whl @@ -1055,7 +1055,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e2/de/21aa8394f16add8f7427f0a1326ccd2b3a2a8a3245c9252bc5ac034c6155/myst_parser-3.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ec/84/0fac7104f46c8c0809487b6e973065df95645ab99cb08cf61a9e9dd2aa41/ngff_zarr-0.13.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/90/b8/c72c98004fac5e494a81570887d5cc06c6daa8898f32dc264b3ea6f17eb8/ngff_zarr-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/43/b2/dc384197be44e2a640bb43311850e23c2c30f3b82ce7c8cdabbf0e53045e/nibabel-5.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/64/7177bf632520705893683fa4ca202ed540450bf971c0453ad1351baa2007/numcodecs-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl @@ -3829,10 +3829,10 @@ packages: - typing-extensions - wasmtime>=28.0.0 ; sys_platform != 'emscripten' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/16/fc/0e70d69ca0aaeb968aafb63a04692d3d4e8a27ccd4f2dc4836d7fb1301c7/itkwasm_downsample-1.7.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/12/c6/906a1c94a9e0d12b6cbc8a36d6fe284d06298d3d3eda14934d21609845ab/itkwasm_downsample-1.8.1-py3-none-any.whl name: itkwasm-downsample - version: 1.7.1 - sha256: 16877cff883c5549f93634eb1fcb7f8bfe28a3d535353a303a344f62d4fd4cf4 + version: 1.8.1 + sha256: f1315d32a1d80a610c682c3a32ab41aa46b584f91f17a839a7c0b6e3796e60c7 requires_dist: - itkwasm-downsample-emscripten ; sys_platform == 'emscripten' - itkwasm-downsample-wasi ; sys_platform != 'emscripten' @@ -5430,14 +5430,14 @@ packages: - pytest-mpl ; extra == 'test-extras' - pytest-randomly ; extra == 'test-extras' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/ec/84/0fac7104f46c8c0809487b6e973065df95645ab99cb08cf61a9e9dd2aa41/ngff_zarr-0.13.4-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/90/b8/c72c98004fac5e494a81570887d5cc06c6daa8898f32dc264b3ea6f17eb8/ngff_zarr-0.18.1-py3-none-any.whl name: ngff-zarr - version: 0.13.4 - sha256: 01eda6eac5c9a5efb9a417799c1c0a958457ab2bc1f2f09f171afe389b5c48d3 + version: 0.18.1 + sha256: 84b728845d4cdd14ff4625c563f4a8b1b1c33e67041f78c01043b32c8801c0eb requires_dist: - dask[array] - importlib-resources - - itkwasm-downsample>=1.7.1 + - itkwasm-downsample>=1.8.0 - itkwasm>=1.0b183 - numpy - platformdirs @@ -5457,6 +5457,7 @@ packages: - itkwasm-image-io ; extra == 'all' - jsonschema ; extra == 'all' - myst-parser>=3.0.1,<4 ; extra == 'all' + - nibabel ; extra == 'all' - pooch ; extra == 'all' - pytest>=6 ; extra == 'all' - sphinx-autobuild>=2024.4.16,<2025 ; extra == 'all' @@ -5474,6 +5475,7 @@ packages: - itk-filtering>=5.3.0 ; extra == 'cli' - itk-io>=5.3.0 ; extra == 'cli' - itkwasm-image-io ; extra == 'cli' + - nibabel ; extra == 'cli' - tifffile>=2024.7.24 ; extra == 'cli' - dask-image ; extra == 'dask-image' - furo>=2024.7.18,<2025 ; extra == 'docs' diff --git a/pixi.toml b/pixi.toml index c3a00c7..9573fac 100644 --- a/pixi.toml +++ b/pixi.toml @@ -58,5 +58,5 @@ features = ["runtime", "dev"] features = ["dev"] [pypi-dependencies] -ngff-zarr = { version = ">=0.13.2, <0.14", extras = ["all"] } +ngff-zarr = { version = ">=0.18.1,<0.19", extras = ["all"] } zarrnii = ">=0.8.0a1,<0.9.0" From 68d566e06b8ddaabc5987c1e5a88e0297794e37c Mon Sep 17 00:00:00 2001 From: "Ali R. Khan" Date: Sun, 9 Nov 2025 14:01:50 -0500 Subject: [PATCH 05/14] fix for submit_jobs (was testing single node) --- submit_jobs.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/submit_jobs.py b/submit_jobs.py index ed5ffd5..70991a1 100644 --- a/submit_jobs.py +++ b/submit_jobs.py @@ -46,12 +46,10 @@ def submit_job( slurm_config["time"], "--mem", slurm_config["mem"], - "--nodelist", - "rri-cbs-h2.schulich.uwo.ca", "--cpus-per-task", slurm_config["cpus"], - "--tmp", - slurm_config["tmp"], +# "--tmp", +# slurm_config["tmp"], "--output", str(log_out), "--export", From 793cf018fa0625daa93c59aacac944fcfb8569e8 Mon Sep 17 00:00:00 2001 From: "Ali R. Khan" Date: Sun, 9 Nov 2025 21:36:44 -0500 Subject: [PATCH 06/14] updates to orientation and a fix --- workflow/rules/ome_zarr.smk | 2 ++ workflow/scripts/imaris_to_ome_zarr.py | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/workflow/rules/ome_zarr.smk b/workflow/rules/ome_zarr.smk index d788243..6c7bc32 100644 --- a/workflow/rules/ome_zarr.smk +++ b/workflow/rules/ome_zarr.smk @@ -91,6 +91,8 @@ rule tif_stacks_to_ome_zarr: rule ome_zarr_to_nii: input: zarr=get_input_ome_zarr_to_nii, + params: + zarrnii_kwargs={} output: nii=bids( root=resampled, diff --git a/workflow/scripts/imaris_to_ome_zarr.py b/workflow/scripts/imaris_to_ome_zarr.py index d463913..86cc89f 100644 --- a/workflow/scripts/imaris_to_ome_zarr.py +++ b/workflow/scripts/imaris_to_ome_zarr.py @@ -119,9 +119,16 @@ group = zarr.group(store, overwrite=True) + + +# TODO: orientation was RAI for earlier scans but seems RPI for newer, need to confirm +# whether brains were placed in a different orientation, or scanned differently, or maybe +# difference the related to MACsIq vs Imaris stitching implementation.. confirm with Shaz. +# Also confirm L/R flip.. + # Add metadata for orientation -group.attrs["orientation"] = "IAR" # this is the updated orientation for imaris files -group.attrs["xyz_orientation"] = "RAI" +group.attrs["orientation"] = "IPR" # this is the updated orientation for imaris files +group.attrs["xyz_orientation"] = "RPI" scaler = Scaler(max_layer=max_layer, method=scaling_method) From e5b8384006daed2d7478347d2b7baf9cf03ce0fd Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Wed, 12 Nov 2025 09:02:02 -0500 Subject: [PATCH 07/14] fallback for imaris metadata grab from tif With some pre-conversion workflows, omero metadata isn't embedded in the imaris file. This uses standardized folder structure to find the associated tif and grab from there.. --- workflow/scripts/imaris_to_metadata.py | 75 ++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/workflow/scripts/imaris_to_metadata.py b/workflow/scripts/imaris_to_metadata.py index cb059b0..b0a476e 100644 --- a/workflow/scripts/imaris_to_metadata.py +++ b/workflow/scripts/imaris_to_metadata.py @@ -9,6 +9,15 @@ # ----------------------------- +def parse_xml_str(xml_str): + """Convert str to XML dict""" + try: + return xmltodict.parse(xml_str, namespace_separator=":") + except Exception as e: + print(f"Error parsing XML: {e}") + return None + + def parse_xml_bytes(xml_bytes): """Convert raw byte array to parsed XML dict with namespace separator.""" xml_str = bytes(xml_bytes).decode("utf-8", errors="ignore") @@ -89,14 +98,72 @@ def build_bids_metadata(custom_attrs): # Main extraction # ----------------------------- -with h5py.File(snakemake.input.ims, "r") as hdf5_file: - xml_data = hdf5_file["DataSetInfo/OME Image Tags/Image 0"][:] -xml_dict = parse_xml_bytes(xml_data) +def print_h5_keys_recursive(obj, indent=""): + """ + Recursively prints the keys and their hierarchy within an h5py object. + + Args: + obj: An h5py.File or h5py.Group object. + indent: A string representing the current indentation level for printing. + """ + if isinstance(obj, h5py.File) or isinstance(obj, h5py.Group): + for key, value in obj.items(): + print(f"{indent}- {key}") + if isinstance(value, h5py.Group): + print_h5_keys_recursive(value, indent + " ") + elif isinstance(obj, h5py.Dataset): + # Datasets are typically leaf nodes, their names are already printed by the parent group + pass + else: + print(f"{indent}- (Unknown HDF5 object type: {type(obj)})") + + +try: + + with h5py.File(snakemake.input.ims, "r") as hdf5_file: + # print(snakemake.input.ims) + # print_h5_keys_recursive(hdf5_file) + xml_data = hdf5_file["DataSetInfo/OME Image Tags/Image 0"][:] + xml_dict = parse_xml_bytes(xml_data) + custom_attrs = xml_dict["root"]["ca:CustomAttributes"] +except: + print( + "error reading ome metadata from imaris, searching for tif file using standard folder structure, in ../../tif_4x*/" + ) + + from glob import glob + from pathlib import Path + + import tifffile + + p = Path(snakemake.input.ims) + + # 1) Name of the parent folder + subj = p.parent.name # -> 'B_AS161F3' + + # 2) Root path of the 'raw' folder (3 levels up from the file) + raw_root = p.parents[2] # -> '/nfs/trident3/.../raw' + + # 3) Construct the new path + new_path = str(raw_root / "tif_4x_*" / subj / "*") + try: + tif_file = sorted(glob(new_path))[0] + with tifffile.TiffFile(tif_file) as tif: + xml_data = tif.ome_metadata + xml_dict = parse_xml_str(xml_data) + custom_attrs = xml_dict["OME"]["Image"]["ca:CustomAttributes"] + except: + raise ( + ValueError( + "Cannot find OME metadata in imaris file or in associated tif files from standard folder structure" + ) + ) + + if not xml_dict: raise ValueError("Failed to parse XML from .ims file") -custom_attrs = xml_dict["root"]["ca:CustomAttributes"] bids_metadata = build_bids_metadata(custom_attrs) # ----------------------------- From 85a2b37190730c48308474633e5e72a4182f4c2e Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Wed, 12 Nov 2025 09:07:25 -0500 Subject: [PATCH 08/14] fix for glob --- workflow/scripts/imaris_to_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflow/scripts/imaris_to_metadata.py b/workflow/scripts/imaris_to_metadata.py index b0a476e..b4f236b 100644 --- a/workflow/scripts/imaris_to_metadata.py +++ b/workflow/scripts/imaris_to_metadata.py @@ -129,7 +129,7 @@ def print_h5_keys_recursive(obj, indent=""): custom_attrs = xml_dict["root"]["ca:CustomAttributes"] except: print( - "error reading ome metadata from imaris, searching for tif file using standard folder structure, in ../../tif_4x*/" + "Warning: cannot find OME metadata from imaris file, searching for tif file using standard folder structure" ) from glob import glob @@ -146,7 +146,7 @@ def print_h5_keys_recursive(obj, indent=""): raw_root = p.parents[2] # -> '/nfs/trident3/.../raw' # 3) Construct the new path - new_path = str(raw_root / "tif_4x_*" / subj / "*") + new_path = str(raw_root / "tif_4x_*" / subj / "*.ome.tif") try: tif_file = sorted(glob(new_path))[0] with tifffile.TiffFile(tif_file) as tif: From e6124dad6a04c9815b7908227973f0d0e89b8177 Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Wed, 12 Nov 2025 10:17:00 -0500 Subject: [PATCH 09/14] fix parsing xml, um error --- workflow/scripts/imaris_to_metadata.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/workflow/scripts/imaris_to_metadata.py b/workflow/scripts/imaris_to_metadata.py index b4f236b..28ef8f5 100644 --- a/workflow/scripts/imaris_to_metadata.py +++ b/workflow/scripts/imaris_to_metadata.py @@ -3,6 +3,7 @@ import h5py import xmltodict +import html # ----------------------------- # Helper functions @@ -10,14 +11,27 @@ def parse_xml_str(xml_str): - """Convert str to XML dict""" + """Convert XML string to dict, decoding HTML entities (e.g. µ → µ).""" + + def decode_entities(obj): + """Recursively decode HTML entities in all strings.""" + if isinstance(obj, dict): + return {k: decode_entities(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [decode_entities(i) for i in obj] + elif isinstance(obj, str): + return html.unescape(obj) + else: + return obj + try: - return xmltodict.parse(xml_str, namespace_separator=":") + parsed = xmltodict.parse(xml_str, namespace_separator=":") + return decode_entities(parsed) + except Exception as e: print(f"Error parsing XML: {e}") return None - def parse_xml_bytes(xml_bytes): """Convert raw byte array to parsed XML dict with namespace separator.""" xml_str = bytes(xml_bytes).decode("utf-8", errors="ignore") From e0be58898f4d8ce027a0072fdfc17289065665f6 Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Wed, 12 Nov 2025 11:25:40 -0500 Subject: [PATCH 10/14] formatting --- workflow/scripts/generate_volume_qc.py | 1 - workflow/scripts/imaris_to_metadata.py | 3 ++- workflow/scripts/imaris_to_ome_zarr.py | 5 ++--- workflow/scripts/ome_zarr_to_nii.py | 3 --- workflow/scripts/zarr_to_ome_zarr.py | 1 - 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/workflow/scripts/generate_volume_qc.py b/workflow/scripts/generate_volume_qc.py index 0d8a0b6..db0b86b 100644 --- a/workflow/scripts/generate_volume_qc.py +++ b/workflow/scripts/generate_volume_qc.py @@ -8,7 +8,6 @@ import zarr from lib.cloud_io import get_fsspec, is_remote from upath import UPath as Path - from zarrnii import ZarrNii # directory containing the volume rendering files diff --git a/workflow/scripts/imaris_to_metadata.py b/workflow/scripts/imaris_to_metadata.py index 28ef8f5..fbec2d3 100644 --- a/workflow/scripts/imaris_to_metadata.py +++ b/workflow/scripts/imaris_to_metadata.py @@ -1,9 +1,9 @@ +import html import json import re import h5py import xmltodict -import html # ----------------------------- # Helper functions @@ -32,6 +32,7 @@ def decode_entities(obj): print(f"Error parsing XML: {e}") return None + def parse_xml_bytes(xml_bytes): """Convert raw byte array to parsed XML dict with namespace separator.""" xml_str = bytes(xml_bytes).decode("utf-8", errors="ignore") diff --git a/workflow/scripts/imaris_to_ome_zarr.py b/workflow/scripts/imaris_to_ome_zarr.py index 86cc89f..7440083 100644 --- a/workflow/scripts/imaris_to_ome_zarr.py +++ b/workflow/scripts/imaris_to_ome_zarr.py @@ -120,14 +120,13 @@ group = zarr.group(store, overwrite=True) - # TODO: orientation was RAI for earlier scans but seems RPI for newer, need to confirm -# whether brains were placed in a different orientation, or scanned differently, or maybe +# whether brains were placed in a different orientation, or scanned differently, or maybe # difference the related to MACsIq vs Imaris stitching implementation.. confirm with Shaz. # Also confirm L/R flip.. # Add metadata for orientation -group.attrs["orientation"] = "IPR" # this is the updated orientation for imaris files +group.attrs["orientation"] = "IPR" # this is the updated orientation for imaris files group.attrs["xyz_orientation"] = "RPI" scaler = Scaler(max_layer=max_layer, method=scaling_method) diff --git a/workflow/scripts/ome_zarr_to_nii.py b/workflow/scripts/ome_zarr_to_nii.py index 59fa7dd..c7577c1 100644 --- a/workflow/scripts/ome_zarr_to_nii.py +++ b/workflow/scripts/ome_zarr_to_nii.py @@ -1,8 +1,6 @@ from dask.diagnostics import ProgressBar - from zarrnii import ZarrNii - znimg = ZarrNii.from_ome_zarr( snakemake.input.zarr, level=int(snakemake.wildcards.level), @@ -13,4 +11,3 @@ with ProgressBar(): znimg.to_nifti(snakemake.output.nii) - diff --git a/workflow/scripts/zarr_to_ome_zarr.py b/workflow/scripts/zarr_to_ome_zarr.py index 4ec3863..88e0ba0 100644 --- a/workflow/scripts/zarr_to_ome_zarr.py +++ b/workflow/scripts/zarr_to_ome_zarr.py @@ -5,7 +5,6 @@ from dask.diagnostics import ProgressBar from lib.cloud_io import get_fsspec, is_remote from upath import UPath as Path - from zarrnii import ZarrNii in_zarr = snakemake.input.zarr From 457fe1fbb030fef8bfc0fdbb1202253069b0e2f3 Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Wed, 12 Nov 2025 11:29:10 -0500 Subject: [PATCH 11/14] update jobs script --- submit_jobs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/submit_jobs.py b/submit_jobs.py index 70991a1..e87677b 100644 --- a/submit_jobs.py +++ b/submit_jobs.py @@ -48,8 +48,8 @@ def submit_job( slurm_config["mem"], "--cpus-per-task", slurm_config["cpus"], -# "--tmp", -# slurm_config["tmp"], + "--tmp", + slurm_config["tmp"], "--output", str(log_out), "--export", From 91733642dfab13671fdcaf725f59a8a86dc62384 Mon Sep 17 00:00:00 2001 From: "Ali R. Khan" Date: Tue, 18 Nov 2025 14:29:52 -0500 Subject: [PATCH 12/14] fix fmt --- workflow/rules/ome_zarr.smk | 2 +- workflow/scripts/generate_volume_qc.py | 1 + workflow/scripts/ome_zarr_to_nii.py | 1 + workflow/scripts/zarr_to_ome_zarr.py | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/workflow/rules/ome_zarr.smk b/workflow/rules/ome_zarr.smk index 6c7bc32..2227cfb 100644 --- a/workflow/rules/ome_zarr.smk +++ b/workflow/rules/ome_zarr.smk @@ -92,7 +92,7 @@ rule ome_zarr_to_nii: input: zarr=get_input_ome_zarr_to_nii, params: - zarrnii_kwargs={} + zarrnii_kwargs={}, output: nii=bids( root=resampled, diff --git a/workflow/scripts/generate_volume_qc.py b/workflow/scripts/generate_volume_qc.py index db0b86b..0d8a0b6 100644 --- a/workflow/scripts/generate_volume_qc.py +++ b/workflow/scripts/generate_volume_qc.py @@ -8,6 +8,7 @@ import zarr from lib.cloud_io import get_fsspec, is_remote from upath import UPath as Path + from zarrnii import ZarrNii # directory containing the volume rendering files diff --git a/workflow/scripts/ome_zarr_to_nii.py b/workflow/scripts/ome_zarr_to_nii.py index c7577c1..599a777 100644 --- a/workflow/scripts/ome_zarr_to_nii.py +++ b/workflow/scripts/ome_zarr_to_nii.py @@ -1,4 +1,5 @@ from dask.diagnostics import ProgressBar + from zarrnii import ZarrNii znimg = ZarrNii.from_ome_zarr( diff --git a/workflow/scripts/zarr_to_ome_zarr.py b/workflow/scripts/zarr_to_ome_zarr.py index 88e0ba0..4ec3863 100644 --- a/workflow/scripts/zarr_to_ome_zarr.py +++ b/workflow/scripts/zarr_to_ome_zarr.py @@ -5,6 +5,7 @@ from dask.diagnostics import ProgressBar from lib.cloud_io import get_fsspec, is_remote from upath import UPath as Path + from zarrnii import ZarrNii in_zarr = snakemake.input.zarr From 4c1e4eeb1fbe7017473fb7f620627f4e207809a6 Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Tue, 18 Nov 2025 15:11:08 -0500 Subject: [PATCH 13/14] fmt --- workflow/scripts/imaris_channel_to_zarr.py | 45 +++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/workflow/scripts/imaris_channel_to_zarr.py b/workflow/scripts/imaris_channel_to_zarr.py index 619398c..32b6718 100644 --- a/workflow/scripts/imaris_channel_to_zarr.py +++ b/workflow/scripts/imaris_channel_to_zarr.py @@ -9,14 +9,49 @@ store = zarr.ZipStore(snakemake.output.zarr, dimension_separator="/", mode="x") dest = zarr.group(store) -zarr.copy( + +def print_tree_item(name, obj): + """ + A visitor function to print the name and type of HDF5 objects. + """ + # Indentation based on group depth + shift = name.count("/") * " " + if isinstance(obj, h5py.Dataset): + print(f"{shift}Dataset: {name} (shape: {obj.shape}, dtype: {obj.dtype})") + elif isinstance(obj, h5py.Group): + print(f"{shift}Group: {name}/") + else: + print(f"{shift}Other: {name}") + + # Optionally, print attributes as well + for key, val in obj.attrs.items(): + print(f"{shift} Attribute: {key}: {val}") + + +print( + source[ + "DataSet/ResolutionLevel 0/TimePoint 0/Channel {chan}/Data".format( + chan=snakemake.params.channel + ) + ].shape +) +source.visititems(print_tree_item) +print( source[ "DataSet/ResolutionLevel 0/TimePoint 0/Channel {chan}/Data".format( chan=snakemake.params.channel ) - ], - dest, - log=stdout, - compressor=None, + ].shape ) + +# zarr.copy( +# source[ +# "DataSet/ResolutionLevel 0/TimePoint 0/Channel {chan}/Data".format( +# chan=snakemake.params.channel +# ) +# ], +# dest, +# log=stdout, +# compressor=None, +# ) source.close() From 38b579584add599df1a0b1500cdedd443a63b164 Mon Sep 17 00:00:00 2001 From: Ali Khan Date: Tue, 18 Nov 2025 15:13:51 -0500 Subject: [PATCH 14/14] fmt --- workflow/scripts/generate_volume_qc.py | 1 - workflow/scripts/imaris_to_metadata.py | 1 + workflow/scripts/ome_zarr_to_nii.py | 1 - workflow/scripts/zarr_to_ome_zarr.py | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/workflow/scripts/generate_volume_qc.py b/workflow/scripts/generate_volume_qc.py index 0d8a0b6..db0b86b 100644 --- a/workflow/scripts/generate_volume_qc.py +++ b/workflow/scripts/generate_volume_qc.py @@ -8,7 +8,6 @@ import zarr from lib.cloud_io import get_fsspec, is_remote from upath import UPath as Path - from zarrnii import ZarrNii # directory containing the volume rendering files diff --git a/workflow/scripts/imaris_to_metadata.py b/workflow/scripts/imaris_to_metadata.py index fbec2d3..66b854a 100644 --- a/workflow/scripts/imaris_to_metadata.py +++ b/workflow/scripts/imaris_to_metadata.py @@ -179,6 +179,7 @@ def print_h5_keys_recursive(obj, indent=""): if not xml_dict: raise ValueError("Failed to parse XML from .ims file") +print(custom_attrs) bids_metadata = build_bids_metadata(custom_attrs) # ----------------------------- diff --git a/workflow/scripts/ome_zarr_to_nii.py b/workflow/scripts/ome_zarr_to_nii.py index 599a777..c7577c1 100644 --- a/workflow/scripts/ome_zarr_to_nii.py +++ b/workflow/scripts/ome_zarr_to_nii.py @@ -1,5 +1,4 @@ from dask.diagnostics import ProgressBar - from zarrnii import ZarrNii znimg = ZarrNii.from_ome_zarr( diff --git a/workflow/scripts/zarr_to_ome_zarr.py b/workflow/scripts/zarr_to_ome_zarr.py index 4ec3863..88e0ba0 100644 --- a/workflow/scripts/zarr_to_ome_zarr.py +++ b/workflow/scripts/zarr_to_ome_zarr.py @@ -5,7 +5,6 @@ from dask.diagnostics import ProgressBar from lib.cloud_io import get_fsspec, is_remote from upath import UPath as Path - from zarrnii import ZarrNii in_zarr = snakemake.input.zarr