Skip to content

Commit 09331ce

Browse files
sfmigclaude
andcommitted
Keep root attributes when appending mask parameters
`to_zarr(mode="a")` replaces the store's root attributes with those of the appended dataset. `ds_props` has none, so every store lost `map_image_id_to_filename` and the rest of ethology's and the script's metadata. Copy the stored attributes onto `ds_props` before appending, in a new `append_mask_props` shared by `main` and the round-trip test, which now checks that the attributes survive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent b73a6d3 commit 09331ce

2 files changed

Lines changed: 26 additions & 8 deletions

File tree

‎scripts/generate_masks_from_bboxes.py‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,23 @@ def compute_mask_props(labels_zarr, ds_bboxes):
350350
)
351351

352352

353+
def append_mask_props(path_to_zarr, ds_props):
354+
"""Append the data variables of ``ds_props`` to the store.
355+
356+
Only the data variables are appended: the coordinates are already in
357+
the store, and xarray checks that rewritten variables keep their stored
358+
dtype. `space` is <U1 here, but reads back from the store as a
359+
variable-length string dtype (StringDType in recent xarray/zarr), so
360+
appending it would raise a dtype mismatch.
361+
362+
``to_zarr(mode="a")`` replaces the store's root attributes with those of
363+
the appended dataset, so the stored attributes are copied onto it first.
364+
"""
365+
ds_props = ds_props.drop_vars(list(ds_props.coords))
366+
ds_props.attrs = dict(zarr.open_group(path_to_zarr).attrs)
367+
ds_props.to_zarr(path_to_zarr, mode="a")
368+
369+
353370
def predict_masks_across_images(
354371
model,
355372
image_array,
@@ -536,12 +553,7 @@ def main(args):
536553

537554
# Add mask and ellipse parameters per bbox, measured on the stored masks
538555
ds_props = compute_mask_props(labels_zarr, ds_bboxes)
539-
# append only the data variables: the coordinates are already in the
540-
# store, and xarray checks that rewritten variables keep their stored
541-
# dtype. `space` is <U1 here, but reads back from the store as a
542-
# variable-length string dtype (StringDType in recent xarray/zarr),
543-
# so appending it would raise a dtype mismatch
544-
ds_props.drop_vars(list(ds_props.coords)).to_zarr(output_zarr, mode="a")
556+
append_mask_props(output_zarr, ds_props)
545557

546558
print(f"Saved bboxes, masks and mask parameters to {output_zarr}")
547559

‎tests/test_unit/test_generate_masks_from_bboxes.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from scripts.generate_masks_from_bboxes import (
1010
ImageArrayLazy,
11+
append_mask_props,
1112
compute_mask_props,
1213
create_dataset_store,
1314
flatten_masks_smallest_wins,
@@ -88,15 +89,20 @@ def test_compute_mask_props(labels, ds_bboxes):
8889
def test_store_round_trip(tmp_path, ds_bboxes, labels):
8990
"""Run the three passes of main, without SAM2."""
9091
store = tmp_path / "bboxes_and_masks.zarr"
91-
labels_zarr = create_dataset_store(store, ds_bboxes, (100, 120), {})
92+
labels_zarr = create_dataset_store(
93+
store, ds_bboxes, (100, 120), {"occlusion_policy": "smallest_wins"}
94+
)
9295
labels_zarr[:] = labels # what predict_masks_across_images does
9396
ds_props = compute_mask_props(labels_zarr, ds_bboxes)
94-
ds_props.drop_vars(list(ds_props.coords)).to_zarr(store, mode="a")
97+
append_mask_props(store, ds_props)
9598

9699
ds = xr.open_zarr(store)
97100
mask = ds.labels.sel(image_id=7) == ds.label_of.sel(id=2)
98101
assert int(mask.sum()) == ds.mask_area.sel(image_id=7, id=2)
99102
np.testing.assert_array_equal(ds.labels.values, labels)
103+
# root attributes survive the append
104+
assert ds.attrs["map_image_id_to_filename"] == {"5": "a.png", "7": "b.png"}
105+
assert ds.attrs["occlusion_policy"] == "smallest_wins"
100106

101107

102108
def test_create_dataset_store(tmp_path, ds_bboxes):

0 commit comments

Comments
 (0)