Skip to content

Commit 9ad15de

Browse files
authored
Interpret offsets for MagDataset.get_view as absolute values (#237)
* interpret the offset parameter for MagDataset.get_view as an absolute value * fix calculation for the size of the new view and add tests * imporve error messages for View creation
1 parent 21f1cfe commit 9ad15de

File tree

4 files changed

+113
-31
lines changed

4 files changed

+113
-31
lines changed

tests/test_dataset.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,3 +1204,75 @@ def test_changing_layer_bounding_box():
12041204
new_data = mag.read(new_bbox_size)
12051205
assert new_data.shape == (1, 255, 255, 10)
12061206
assert np.array_equal(original_data[:, 10:, 10:, :], new_data)
1207+
1208+
1209+
def test_view_offsets():
1210+
delete_dir("./testoutput/wk_offset_tests")
1211+
1212+
ds = WKDataset.create("./testoutput/wk_offset_tests", scale=(1, 1, 1))
1213+
mag = ds.add_layer("color", "color").add_mag("1")
1214+
1215+
# The dataset is new -> no data has been written.
1216+
# Therefore, the size of the bounding box in the properties.json is (0, 0, 0)
1217+
1218+
# Creating this view works because the size is set to (0, 0, 0)
1219+
# However, in practice such a view would not make sense because 'is_bounded' is set to 'True'
1220+
wk_view = ds.get_view("color", "1", size=(0, 0, 0), is_bounded=True)
1221+
assert wk_view.global_offset == tuple((0, 0, 0))
1222+
assert wk_view.size == tuple((0, 0, 0))
1223+
1224+
try:
1225+
# Creating this view does not work because the size (16, 16, 16) would exceed the boundingbox from the properties.json
1226+
ds.get_view("color", "1", size=(16, 16, 16), is_bounded=True)
1227+
raise Exception(expected_error_msg)
1228+
except AssertionError:
1229+
pass
1230+
1231+
# This works because 'is_bounded' is set to 'False'
1232+
# Therefore, the bounding box of the view can be larger than the bounding box from the properties.json
1233+
wk_view = ds.get_view("color", "1", size=(16, 16, 16), is_bounded=False)
1234+
assert wk_view.global_offset == tuple((0, 0, 0))
1235+
assert wk_view.size == tuple((16, 16, 16))
1236+
1237+
np.random.seed(1234)
1238+
write_data = (np.random.rand(100, 200, 300) * 255).astype(np.uint8)
1239+
mag.write(write_data, offset=(10, 20, 30))
1240+
1241+
# The bounding box of the dataset was updated according to the written data
1242+
# Therefore, creating a view with a size of (16, 16, 16) is now allowed
1243+
wk_view = ds.get_view("color", "1", size=(16, 16, 16), is_bounded=True)
1244+
assert wk_view.global_offset == tuple((10, 20, 30))
1245+
assert wk_view.size == tuple((16, 16, 16))
1246+
1247+
try:
1248+
# Creating this view does not work because the offset (0, 0, 0) would be outside of the boundingbox from the properties.json
1249+
ds.get_view("color", "1", size=(16, 16, 16), offset=(0, 0, 0), is_bounded=True)
1250+
raise Exception(expected_error_msg)
1251+
except AssertionError:
1252+
pass
1253+
1254+
# Creating this view works, even though the offset (0, 0, 0) is outside of the boundingbox from the properties.json, because 'is_bounded' is set to 'False'
1255+
wk_view = ds.get_view(
1256+
"color", "1", size=(16, 16, 16), offset=(0, 0, 0), is_bounded=False
1257+
)
1258+
assert wk_view.global_offset == tuple((0, 0, 0))
1259+
assert wk_view.size == tuple((16, 16, 16))
1260+
1261+
# Creating this view works because the bounding box of the view is inside the bounding box from the properties.json
1262+
wk_view = ds.get_view(
1263+
"color", "1", size=(16, 16, 16), offset=(20, 30, 40), is_bounded=True
1264+
)
1265+
assert wk_view.global_offset == tuple((20, 30, 40))
1266+
assert wk_view.size == tuple((16, 16, 16))
1267+
1268+
# Creating this subview works because the subview is completely inside the 'wk_view'
1269+
sub_view = wk_view.get_view(size=(8, 8, 8), relative_offset=(8, 8, 8))
1270+
assert sub_view.global_offset == tuple((28, 38, 48))
1271+
assert sub_view.size == tuple((8, 8, 8))
1272+
1273+
try:
1274+
# Creating this subview does not work because it is not completely inside the 'wk_view'
1275+
wk_view.get_view(size=(10, 10, 10), relative_offset=(8, 8, 8))
1276+
raise Exception(expected_error_msg)
1277+
except AssertionError:
1278+
pass

wkcuber/api/Dataset.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,7 @@ def delete_layer(self, layer_name):
129129
# delete files on disk
130130
rmtree(join(self.path, layer_name))
131131

132-
def get_view(
133-
self, layer_name, mag, size, offset=(0, 0, 0), is_bounded=True
134-
) -> View:
132+
def get_view(self, layer_name, mag, size, offset=None, is_bounded=True) -> View:
135133
layer = self.get_layer(layer_name)
136134
mag_ds = layer.get_mag(mag)
137135

wkcuber/api/MagDataset.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def __init__(self, layer, name):
1515
self.name = name
1616
self.header = self.get_header()
1717

18-
self.view = self.get_view(is_bounded=False)
18+
self.view = self.get_view(offset=(0, 0, 0), is_bounded=False)
1919

2020
def open(self):
2121
self.view.open()
@@ -68,30 +68,40 @@ def get_view(self, size=None, offset=None, is_bounded=True):
6868
self.layer.name
6969
].get_bounding_box_size()
7070

71+
offset_in_properties = self.layer.dataset.properties.data_layers[
72+
self.layer.name
73+
].get_bounding_box_offset()
74+
75+
if offset_in_properties == (-1, -1, -1):
76+
offset_in_properties = (0, 0, 0)
77+
7178
if offset is None:
72-
offset = (0, 0, 0)
79+
offset = offset_in_properties
7380

7481
if size is None:
75-
size = size_in_properties
76-
77-
# assert that the parameter size is valid
78-
for s1, s2, off in zip(size_in_properties, size, offset):
79-
if s2 + off > s1 and is_bounded:
80-
raise AssertionError(
81-
f"The combination of the passed parameter 'size' {size} and {offset} are not compatible with the "
82-
f"size ({size_in_properties}) from the properties.json."
83-
)
82+
size = np.array(size_in_properties) - (
83+
np.array(offset) - np.array(offset_in_properties)
84+
)
85+
86+
# assert that the parameters size and offset are valid
87+
if is_bounded:
88+
for off_prop, off in zip(offset_in_properties, offset):
89+
if off < off_prop:
90+
raise AssertionError(
91+
f"The passed parameter 'offset' {offset} is outside the bounding box from the properties.json. "
92+
f"Use is_bounded=False if you intend to write outside out the existing bounding box."
93+
)
94+
for s1, s2, off in zip(size_in_properties, size, offset):
95+
if s2 + off > s1:
96+
raise AssertionError(
97+
f"The combination of the passed parameter 'size' {size} and 'offset' {offset} are not compatible with the "
98+
f"size ({size_in_properties}) from the properties.json. "
99+
f"Use is_bounded=False if you intend to write outside out the existing bounding box."
100+
)
84101

85102
mag_file_path = join(self.layer.dataset.path, self.layer.name, self.name)
86-
offset_in_properties = self.layer.dataset.properties.data_layers[
87-
self.layer.name
88-
].get_bounding_box_offset()
89-
dataset_offset = (
90-
(0, 0, 0) if offset_in_properties == (-1, -1, -1) else offset_in_properties
91-
)
92-
global_offset = np.array(dataset_offset) + np.array(offset)
93103
return self._get_view_type()(
94-
mag_file_path, self.header, size, global_offset, is_bounded
104+
mag_file_path, self.header, size, offset, is_bounded
95105
)
96106

97107
def _get_view_type(self):

wkcuber/api/View.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ def close(self):
3636
self.dataset = None
3737
self._is_opened = False
3838

39-
def write(self, data, offset=(0, 0, 0)):
39+
def write(self, data, relative_offset=(0, 0, 0)):
4040
was_opened = self._is_opened
4141
# assert the size of the parameter data is not in conflict with the attribute self.size
42-
assert_non_negative_offset(offset)
43-
self.assert_bounds(offset, data.shape[-3:])
42+
assert_non_negative_offset(relative_offset)
43+
self.assert_bounds(relative_offset, data.shape[-3:])
4444

4545
# calculate the absolute offset
46-
absolute_offset = tuple(sum(x) for x in zip(self.global_offset, offset))
46+
absolute_offset = tuple(
47+
sum(x) for x in zip(self.global_offset, relative_offset)
48+
)
4749

4850
if not was_opened:
4951
self.open()
@@ -73,14 +75,14 @@ def read(self, size=None, offset=(0, 0, 0)) -> np.array:
7375

7476
return data
7577

76-
def get_view(self, size, offset=(0, 0, 0)):
77-
self.assert_bounds(offset, size)
78-
view_offset = self.global_offset + np.array(offset)
78+
def get_view(self, size, relative_offset=(0, 0, 0)):
79+
self.assert_bounds(relative_offset, size)
80+
view_offset = self.global_offset + np.array(relative_offset)
7981
return type(self)(
8082
self.path,
8183
self.header,
8284
size=size,
83-
global_offset=view_offset,
85+
global_offset=tuple(view_offset),
8486
is_bounded=self.is_bounded,
8587
)
8688

@@ -107,7 +109,7 @@ def for_each_chunk(self, work_on_chunk, job_args_per_chunk, chunk_size, executor
107109
relative_offset = np.array(chunk.topleft) - np.array(self.global_offset)
108110
job_args.append(
109111
(
110-
self.get_view(size=chunk.size, offset=relative_offset),
112+
self.get_view(size=chunk.size, relative_offset=relative_offset),
111113
job_args_per_chunk,
112114
)
113115
)

0 commit comments

Comments
 (0)