Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 46 additions & 8 deletions src/bfio/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -1661,6 +1661,35 @@ def __init__(self, frontend):

self._axes_list = []

def _get_axis_info(self):
shape_len = len(self._rdr.shape)
if shape_len == 5:
self._axes_list = ["t", "c", "z", "y", "x"]
else:
try:
# OME-NGFF 0.5: multiscales is under attrs["ome"]["multiscales"]
ome_meta = self._root.attrs["ome"]
axes_metadata = ome_meta["multiscales"][0]["axes"]
for axes in axes_metadata:
self._axes_list.append(axes["name"])
except (AttributeError, KeyError):
# Fall back to v2 location (attrs["multiscales"])
try:
axes_metadata = self._root.attrs["multiscales"][0]["axes"]
for axes in axes_metadata:
self._axes_list.append(axes["name"])
except (AttributeError, KeyError):
self.logger.warning(
"Unable to find multiscales metadata. Z, C and T "
+ "dimensions might be incorrect."
)
if shape_len == 4:
self._axes_list = ["c", "z", "y", "x"]
elif shape_len == 3:
self._axes_list = ["z", "y", "x"]
elif shape_len == 2:
self._axes_list = ["y", "x"]

def read_metadata(self):
self.logger.debug("read_metadata(): Reading metadata (v3)...")
# First try OME metadata (same as v2)
Expand Down Expand Up @@ -1739,14 +1768,23 @@ def _init_writer(self):
with open(metadata_path, "w") as fw:
fw.write(str(self.frontend._metadata.to_xml()))

self._root.attrs["multiscales"] = [
{
"version": "0.1",
"name": self.frontend._file_path.name,
"datasets": [{"path": "0"}],
"metadata": {"method": "mean"},
}
]
self._root.attrs["ome"] = {
"version": "0.5",
"multiscales": [
{
"name": self.frontend._file_path.name,
"axes": [
{"name": "t", "type": "time"},
{"name": "c", "type": "channel"},
{"name": "z", "type": "space"},
{"name": "y", "type": "space"},
{"name": "x", "type": "space"},
],
"datasets": [{"path": "0"}],
"metadata": {"method": "mean"},
}
],
}

# Check for existing arrays when appending
if self.frontend.append is True:
Expand Down
57 changes: 57 additions & 0 deletions tests/test_zarr_v3.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""Tests for zarr v3 support in bfio using unittest."""

import json
import tempfile
import unittest
Expand Down Expand Up @@ -97,6 +98,28 @@ def test_write_v3_ome_metadata(self):
metadata_path = out_path / "OME" / "METADATA.ome.xml"
self.assertTrue(metadata_path.exists())

def test_write_v3_multiscales_under_ome_key(self):
"""Verify v3 writer stores multiscales under 'ome' key per NGFF 0.5."""
from bfio import BioWriter

with tempfile.TemporaryDirectory() as tmp:
out_path = Path(tmp) / "output.zarr"
data = numpy.random.randint(0, 255, (128, 128), dtype=numpy.uint8)

bw = BioWriter(out_path, backend="zarr3", X=128, Y=128, dtype=numpy.uint8)
bw[:128, :128, 0, 0, 0] = data
bw.close()

with open(out_path / "zarr.json") as f:
meta = json.load(f)
attrs = meta.get("attributes", {})
self.assertIn("ome", attrs)
self.assertIn("multiscales", attrs["ome"])
self.assertEqual(attrs["ome"]["version"], "0.5")
axes = attrs["ome"]["multiscales"][0]["axes"]
axis_names = [a["name"] for a in axes]
self.assertEqual(axis_names, ["t", "c", "z", "y", "x"])


class TestZarr3Reader(unittest.TestCase):
"""Test Zarr3Reader reads v3 format stores."""
Expand Down Expand Up @@ -147,6 +170,40 @@ def test_read_v3_dimensions(self):
self.assertEqual(br.T, 1)
br.close()

def test_read_v3_axis_info_from_ome_key(self):
"""Verify Zarr3Reader reads axis info from ome.multiscales per NGFF 0.5."""
from bfio import BioReader, BioWriter

with tempfile.TemporaryDirectory() as tmp:
out_path = Path(tmp) / "axis_test.zarr"
bw = BioWriter(
out_path,
backend="zarr3",
X=64,
Y=64,
Z=2,
C=3,
T=1,
dtype=numpy.uint8,
)
data = numpy.zeros((64, 64, 2, 3, 1), dtype=numpy.uint8)
bw.write(data)
bw.close()

# Verify the zarr.json has ome.multiscales with axes
with open(out_path / "zarr.json") as f:
meta = json.load(f)
axes = meta["attributes"]["ome"]["multiscales"][0]["axes"]
axis_names = [a["name"] for a in axes]
self.assertEqual(axis_names, ["t", "c", "z", "y", "x"])

# Verify reader correctly parses dimensions from ome key
br = BioReader(out_path, backend="zarr3")
self.assertEqual(br.Z, 2)
self.assertEqual(br.C, 3)
self.assertEqual(br.T, 1)
br.close()


class TestZarrAutoBackendSelection(unittest.TestCase):
"""Test auto-detection picks correct backend."""
Expand Down