Skip to content

Commit 39e7522

Browse files
authored
Fixes the array shape when using Dataset.from_images (#1267)
* fixes the Zarr array shape for Dataset.from_images * changelog * rm expand_only * docstring
1 parent f192c22 commit 39e7522

File tree

4 files changed

+27
-57
lines changed

4 files changed

+27
-57
lines changed

webknossos/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ For upgrade instructions, please check the respective _Breaking Changes_ section
2121

2222
### Fixed
2323
- Fixed an issue with `RemoteDataset.explore_and_add_remote()` where including a remote dataset failed. [#1261](https://github.com/scalableminds/webknossos-libs/pull/1261)
24+
- Fixed an issue with the Zarr array's shape when using `Dataset.from_images`. [#1267](https://github.com/scalableminds/webknossos-libs/pull/1267)
2425

2526

2627
## [2.0.0](https://github.com/scalableminds/webknossos-libs/releases/tag/v2.0.0) - 2025-03-04

webknossos/tests/dataset/test_from_images.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import warnings
23
from pathlib import Path
34
from shutil import copytree
@@ -8,7 +9,6 @@
89
from cluster_tools import SequentialExecutor
910
from tifffile import TiffFile
1011

11-
import webknossos as wk
1212
from tests.constants import TESTDATA_DIR
1313
from webknossos.dataset import Dataset
1414

@@ -21,15 +21,15 @@ def ignore_warnings() -> Iterator:
2121

2222

2323
def test_compare_tifffile(tmp_path: Path) -> None:
24-
ds = wk.Dataset.from_images(
24+
ds = Dataset.from_images(
2525
TESTDATA_DIR / "tiff",
2626
tmp_path,
2727
(1, 1, 1),
2828
compress=True,
2929
layer_name="tiff_stack",
3030
layer_category="segmentation",
3131
shard_shape=(256, 256, 256),
32-
map_filepath_to_layer_name=wk.Dataset.ConversionLayerMapping.ENFORCE_SINGLE_LAYER,
32+
map_filepath_to_layer_name=Dataset.ConversionLayerMapping.ENFORCE_SINGLE_LAYER,
3333
)
3434
assert len(ds.layers) == 1
3535
assert "tiff_stack" in ds.layers
@@ -41,7 +41,7 @@ def test_compare_tifffile(tmp_path: Path) -> None:
4141

4242

4343
def test_multiple_multitiffs(tmp_path: Path) -> None:
44-
ds = wk.Dataset.from_images(
44+
ds = Dataset.from_images(
4545
TESTDATA_DIR / "various_tiff_formats",
4646
tmp_path,
4747
(1, 1, 1),
@@ -71,9 +71,18 @@ def test_multiple_multitiffs(tmp_path: Path) -> None:
7171
assert layer.num_channels == channels
7272
assert layer.bounding_box.size == size
7373

74+
# Check that the zarr.json metadata is correct
75+
mag1 = layer.get_finest_mag()
76+
array_shape = json.loads((mag1.path / "zarr.json").read_bytes())["shape"]
77+
shard_aligned_bottomright = layer.bounding_box.with_bottomright_xyz(
78+
layer.bounding_box.bottomright_xyz.ceildiv(mag1.info.shard_shape)
79+
* mag1.info.shard_shape
80+
).bottomright
81+
assert array_shape == [channels] + shard_aligned_bottomright.to_list()
82+
7483

7584
def test_from_dicom_images(tmp_path: Path) -> None:
76-
ds = wk.Dataset.from_images(
85+
ds = Dataset.from_images(
7786
TESTDATA_DIR / "dicoms",
7887
tmp_path,
7988
(1, 1, 1),
@@ -97,7 +106,7 @@ def test_no_slashes_in_layername(tmp_path: Path) -> None:
97106

98107
for strategy in Dataset.ConversionLayerMapping:
99108
with SequentialExecutor() as executor:
100-
dataset = wk.Dataset.from_images(
109+
dataset = Dataset.from_images(
101110
tmp_path / "tiff",
102111
tmp_path / str(strategy),
103112
voxel_size=(10, 10, 10),

webknossos/webknossos/dataset/_array.py

Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import re
2-
import warnings
32
from abc import ABC, abstractmethod
43
from collections.abc import Iterable
54
from dataclasses import dataclass
@@ -99,12 +98,7 @@ def write(self, bbox: NDBoundingBox, data: np.ndarray) -> None:
9998
pass
10099

101100
@abstractmethod
102-
def ensure_size(
103-
self,
104-
new_bbox: NDBoundingBox,
105-
align_with_shards: bool = True,
106-
warn: bool = False,
107-
) -> None:
101+
def resize(self, new_bbox: NDBoundingBox) -> None:
108102
pass
109103

110104
@abstractmethod
@@ -206,12 +200,7 @@ def read(self, bbox: NDBoundingBox) -> np.ndarray:
206200
def write(self, bbox: NDBoundingBox, data: np.ndarray) -> None:
207201
self._wkw_dataset.write(Vec3Int(bbox.topleft), data)
208202

209-
def ensure_size(
210-
self,
211-
new_bbox: NDBoundingBox,
212-
align_with_shards: bool = True,
213-
warn: bool = False,
214-
) -> None:
203+
def resize(self, new_bbox: NDBoundingBox) -> None:
215204
pass
216205

217206
def _list_files(self) -> Iterator[Path]:
@@ -511,42 +500,22 @@ def read(self, bbox: NDBoundingBox) -> np.ndarray:
511500
out = np.expand_dims(out, 0)
512501
return out
513502

514-
def ensure_size(
515-
self,
516-
new_bbox: NDBoundingBox,
517-
align_with_shards: bool = True,
518-
warn: bool = False,
519-
) -> None:
503+
def resize(self, new_bbox: NDBoundingBox) -> None:
520504
array = self._array
521505

522-
new_bbox = new_bbox.with_bottomright(
523-
(
524-
max(array.domain.exclusive_max[i + 1], new_bbox.bottomright[i])
525-
for i in range(len(new_bbox))
526-
)
506+
# Align with shards
507+
shard_shape = self.info.shard_shape
508+
new_bbox = new_bbox.with_bottomright_xyz(
509+
new_bbox.bottomright_xyz.ceildiv(shard_shape) * shard_shape
527510
)
528511
new_domain = tensorstore.IndexDomain(
529512
new_bbox.ndim + 1,
530513
shape=(self.info.num_channels,) + new_bbox.bottomright.to_tuple(),
531514
implicit_upper_bounds=tuple(True for _ in range(new_bbox.ndim + 1)),
532515
labels=array.domain.labels,
533516
)
534-
if new_domain != array.domain:
535-
if align_with_shards:
536-
shard_shape = self.info.shard_shape
537-
new_aligned_bbox = new_bbox.with_bottomright_xyz(
538-
new_bbox.bottomright_xyz.ceildiv(shard_shape) * shard_shape
539-
)
540-
new_domain = tensorstore.IndexDomain(
541-
new_aligned_bbox.ndim + 1,
542-
shape=(self.info.num_channels,)
543-
+ new_aligned_bbox.bottomright.to_tuple(),
544-
implicit_upper_bounds=tuple(
545-
True for _ in range(new_aligned_bbox.ndim + 1)
546-
),
547-
labels=array.domain.labels,
548-
)
549517

518+
if new_domain != array.domain:
550519
# Check on-disk for changes to shape
551520
current_array = tensorstore.open(
552521
{
@@ -560,17 +529,10 @@ def ensure_size(
560529
+ "This is likely happening because multiple processes changed the metadata of this array."
561530
)
562531

563-
if warn:
564-
warnings.warn(
565-
f"[INFO] Resizing Zarr array from `{array.domain}` to `{new_domain}`.",
566-
category=UserWarning,
567-
)
568-
569532
self._cached_array = array.resize(
570533
inclusive_min=None,
571534
exclusive_max=new_domain.exclusive_max,
572535
resize_metadata_only=True,
573-
expand_only=True,
574536
).result()
575537

576538
def write(self, bbox: NDBoundingBox, data: np.ndarray) -> None:

webknossos/webknossos/dataset/layer.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -357,15 +357,13 @@ def bounding_box(self) -> NDBoundingBox:
357357

358358
@bounding_box.setter
359359
def bounding_box(self, bbox: NDBoundingBox) -> None:
360-
"""
361-
Updates the offset and size of the bounding box of this layer in the properties.
362-
"""
360+
"""Updates the offset and size of the bounding box of this layer in the properties."""
363361
self.dataset._ensure_writable()
364362
assert bbox.topleft.is_positive(), f"Updating the bounding box of layer {self} to {bbox} failed, topleft must not contain negative dimensions."
365363
self._properties.bounding_box = bbox
366364
self.dataset._export_as_json()
367365
for mag in self.mags.values():
368-
mag._array.ensure_size(bbox.align_with_mag(mag.mag).in_mag(mag.mag))
366+
mag._array.resize(bbox.align_with_mag(mag.mag).in_mag(mag.mag))
369367

370368
@property
371369
def category(self) -> LayerCategoryType:
@@ -561,7 +559,7 @@ def add_mag(
561559
path=mag_path,
562560
)
563561

564-
mag_view._array.ensure_size(
562+
mag_view._array.resize(
565563
self.bounding_box.align_with_mag(mag, ceil=True).in_mag(mag)
566564
)
567565

0 commit comments

Comments
 (0)