Skip to content

Commit 738865f

Browse files
authored
Fix buffered slice writer overwriting data under certain conditions (#973)
* fix that buffered slice writer could overwrite data when writing less slices than buffer_size at unaligned offset * format * update changelog
1 parent cbbb3e2 commit 738865f

File tree

3 files changed

+49
-7
lines changed

3 files changed

+49
-7
lines changed

webknossos/Changelog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ For upgrade instructions, please check the respective _Breaking Changes_ section
1919
### Changed
2020

2121
### Fixed
22-
22+
- Fixes that the buffered slice writer could overwrite data when writing less slices than buffer_size at an offset that is not aligned. [#973](https://github.com/scalableminds/webknossos-libs/pull/973)
2323

2424
## [0.14.11](https://github.com/scalableminds/webknossos-libs/releases/tag/v0.14.11) - 2023-12-06
2525
[Commits](https://github.com/scalableminds/webknossos-libs/compare/v0.14.10...v0.14.11)

webknossos/tests/dataset/test_buffered_slice_utils.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,49 @@ def test_basic_buffered_slice_writer(tmp_path: Path) -> None:
164164
assert np.all(data == written_data)
165165

166166

167+
def test_buffered_slice_writer_unaligned(
168+
tmp_path: Path,
169+
) -> None:
170+
# Create DS
171+
dataset = Dataset(tmp_path, voxel_size=(1, 1, 1))
172+
layer = dataset.add_layer(
173+
layer_name="color", category="color", dtype_per_channel="uint8", num_channels=1
174+
)
175+
mag1 = layer.add_mag("1", chunk_shape=(32, 32, 32), chunks_per_shard=(8, 8, 8))
176+
177+
# Write some data to z=32. We will check that this
178+
# data is left untouched by the buffered slice writer.
179+
ones_at_z32 = np.ones((512, 512, 4), dtype=np.uint8)
180+
ones_offset = (0, 0, 32)
181+
mag1.write(ones_at_z32, absolute_offset=ones_offset)
182+
183+
# Allocate some data (~ 8 MB). Note that this will write
184+
# from z=1 to z=31 (i.e., 31 slices instead of 32 which
185+
# is the buffer_size with which we configure the BufferedSliceWriter).
186+
offset = (1, 1, 1)
187+
shape = (512, 512, 31)
188+
data = np.random.randint(0, 255, shape, dtype=np.uint8)
189+
190+
with warnings.catch_warnings():
191+
warnings.filterwarnings("default", module="webknossos", message=r"\[WARNING\]")
192+
with mag1.get_buffered_slice_writer(
193+
absolute_offset=offset, buffer_size=32
194+
) as writer:
195+
for z in range(0, shape[2]):
196+
section = data[:, :, z]
197+
writer.send(section)
198+
199+
written_data = mag1.read(absolute_offset=offset, size=shape)
200+
assert np.all(
201+
data == written_data
202+
), "Read data is not equal to the data that was just written."
203+
204+
data_at_z32 = mag1.read(absolute_offset=ones_offset, size=ones_at_z32.shape)
205+
assert np.all(
206+
ones_at_z32 == data_at_z32
207+
), "The BufferedSliceWriter seems to have overwritten older data."
208+
209+
167210
def test_buffered_slice_writer_should_warn_about_unaligned_usage(
168211
tmp_path: Path,
169212
) -> None:

webknossos/webknossos/dataset/_utils/buffered_slice_writer.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,23 +130,22 @@ def _flush_buffer(self) -> None:
130130
max_height = max(section.shape[-1] for section in self.slices_to_write)
131131
channel_count = self.slices_to_write[0].shape[0]
132132

133-
buffer_bbox = BoundingBox(
134-
(0, 0, 0), (max_width, max_height, self.buffer_size)
135-
)
133+
buffer_depth = min(self.buffer_size, len(self.slices_to_write))
134+
buffer_bbox = BoundingBox((0, 0, 0), (max_width, max_height, buffer_depth))
136135

137136
shard_dimensions = self.view._get_file_dimensions().moveaxis(
138137
-1, self.dimension
139138
)
140139
chunk_size = Vec3Int(
141140
min(shard_dimensions[0], max_width),
142141
min(shard_dimensions[1], max_height),
143-
self.buffer_size,
142+
buffer_depth,
144143
)
145144
for chunk_bbox in buffer_bbox.chunk(chunk_size):
146145
info(f"Writing chunk {chunk_bbox}")
147-
width, height, _ = chunk_bbox.size
146+
width, height, depth = chunk_bbox.size
148147
data = np.zeros(
149-
(channel_count, width, height, self.buffer_size),
148+
(channel_count, width, height, depth),
150149
dtype=self.slices_to_write[0].dtype,
151150
)
152151

0 commit comments

Comments
 (0)