Skip to content

Commit 6f5df53

Browse files
Add BufferedSliceWriter from segmentation tools (#72)
* added BufferedSliceWriter from segmentation tools to utils * auto-reformatted utils * removed pid printing * started working on the test * finished initial version of buffered slice writer test. gave the buffered slice writer the argument mag * added z arg to buffered slice writer test * fixed test bugs * improved test output * fixed test by removing zeros and making the imput img nonzero * removed unnessecary assert statement * removed shape bug * added better assertion feedback * added equivalency test
1 parent 9d4a9f2 commit 6f5df53

File tree

2 files changed

+149
-2
lines changed

2 files changed

+149
-2
lines changed

tests/test_utils.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import numpy as np
2-
from wkcuber.utils import get_chunks, get_regular_chunks
2+
from wkcuber.utils import get_chunks, get_regular_chunks, BufferedSliceWriter
3+
import wkw
4+
from wkcuber.mag import Mag
5+
import os
36

47
BLOCK_LEN = 32
58

@@ -27,3 +30,58 @@ def test_get_regular_chunks_max_inclusive():
2730
assert list(target[0]) == list(range(4, 5))
2831
# The last chunk should include 44
2932
assert list(target[-1]) == list(range(44, 45))
33+
34+
35+
def test_buffered_slice_writer():
36+
test_img = np.arange(24 * 24).reshape(24, 24).astype(np.uint16) + 1
37+
dtype = test_img.dtype
38+
bbox = {'topleft': (0, 0, 0), 'size': (24, 24, 35)}
39+
origin = [0, 0, 0]
40+
dataset_dir = 'testoutput/buffered_slice_writer'
41+
layer_name = 'color'
42+
mag = Mag(1)
43+
dataset_path = os.path.join(dataset_dir, layer_name, mag.to_layer_name())
44+
45+
with BufferedSliceWriter(dataset_dir, layer_name, dtype, bbox, origin, mag=mag) as writer:
46+
for i in range(13):
47+
writer.write_slice(i, test_img)
48+
with wkw.Dataset.open(dataset_path, wkw.Header(dtype)) as data:
49+
try:
50+
read_data = data.read(origin, (24, 24, 13))
51+
if read_data[read_data.nonzero()].size != 0:
52+
raise AssertionError('Nothing should be written on the disk. But found data with shape: {}'
53+
.format(read_data.shape))
54+
except wkw.wkw.WKWException:
55+
pass
56+
57+
for i in range(13, 32):
58+
writer.write_slice(i, test_img)
59+
with wkw.Dataset.open(dataset_path, wkw.Header(dtype)) as data:
60+
read_data = data.read(origin, (24, 24, 32))
61+
assert np.squeeze(read_data).shape == (24, 24, 32), "The read data should have the shape: (24, 24, 32) " \
62+
"but has a shape of: {}"\
63+
.format(np.squeeze(read_data).shape)
64+
assert read_data.size == read_data[read_data.nonzero()].size, "The read data contains zeros while the " \
65+
"written image has no zeros"
66+
67+
for i in range(32, 35):
68+
writer.write_slice(i, test_img)
69+
70+
with wkw.Dataset.open(dataset_path, wkw.Header(dtype)) as data:
71+
read_data = data.read(origin, (24, 24, 35))
72+
read_data = np.squeeze(read_data)
73+
assert read_data.shape == (24, 24, 35), "The read data should have the shape: (24, 24, 35) " \
74+
"but has a shape of: {}"\
75+
.format(np.squeeze(read_data).shape)
76+
assert read_data.size == read_data[read_data.nonzero()].size, "The read data contains zeros while the " \
77+
"written image has no zeros"
78+
test_img_3d = np.zeros((test_img.shape[0], test_img.shape[1], 35))
79+
for i in np.arange(35):
80+
test_img_3d[:, :, i] = test_img
81+
# transpose because the slice writer takes [y, x] data and transposes it to [x, y] before writing
82+
test_img_3d = np.transpose(test_img_3d, (1, 0, 2))
83+
# check if the data are correct
84+
assert np.array_equal(test_img_3d, read_data), "The data from the disk is not the same " \
85+
"as the data that should be written."
86+
87+

wkcuber/utils.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
from multiprocessing import cpu_count, Lock
1111
import concurrent
1212
from concurrent.futures import ProcessPoolExecutor
13-
from os import path
13+
from os import path, getpid
1414
from platform import python_version
1515
from math import floor, ceil
16+
from wkcuber.mag import Mag
1617

1718
from .knossos import KnossosDataset, CUBE_EDGE_LEN
1819

@@ -179,3 +180,91 @@ def time_stop(identifier):
179180
def wait_and_ensure_success(futures):
180181
for fut in concurrent.futures.as_completed(futures):
181182
fut.result()
183+
184+
185+
class BufferedSliceWriter(object):
186+
def __init__(
187+
self,
188+
dataset_path,
189+
layer_name,
190+
dtype,
191+
bounding_box,
192+
origin,
193+
buffer_size=32,
194+
mag=Mag(1),
195+
):
196+
197+
self.dataset_path = dataset_path
198+
self.layer_name = layer_name
199+
self.buffer_size = buffer_size
200+
201+
layer_path = path.join(self.dataset_path, self.layer_name, mag.to_layer_name())
202+
203+
self.dataset = wkw.Dataset.open(layer_path, wkw.Header(dtype))
204+
self.origin = origin
205+
self.bounding_box = bounding_box
206+
207+
self.buffer = []
208+
self.current_z = None
209+
self.buffer_start_z = None
210+
211+
def write_slice(self, z: int, data: np.ndarray):
212+
"""Takes in a slice in [y, x] shape, writes to WKW file."""
213+
214+
if len(self.buffer) == 0:
215+
self.current_z = z
216+
self.buffer_start_z = z
217+
218+
assert (
219+
z == self.current_z
220+
), "({}) Slices have to be written sequentially!".format(getpid())
221+
222+
self.buffer.append(data.transpose())
223+
self.current_z += 1
224+
225+
if self.current_z % self.buffer_size == 0:
226+
self._write_buffer()
227+
228+
def _write_buffer(self):
229+
230+
if len(self.buffer) == 0:
231+
return
232+
233+
assert len(self.buffer) <= self.buffer_size
234+
235+
logging.debug(
236+
"({}) Writing {} slices at position {}.".format(
237+
getpid(), len(self.buffer), self.buffer_start_z
238+
)
239+
)
240+
241+
origin_with_offset = self.origin.copy()
242+
origin_with_offset[2] = self.buffer_start_z
243+
x_max = max(slice.shape[0] for slice in self.buffer)
244+
y_max = max(slice.shape[1] for slice in self.buffer)
245+
self.buffer = [
246+
np.pad(
247+
slice,
248+
mode="constant",
249+
pad_width=[(0, x_max - slice.shape[0]), (0, y_max - slice.shape[1])],
250+
)
251+
for slice in self.buffer
252+
]
253+
data = np.concatenate(
254+
[np.expand_dims(slice, 2) for slice in self.buffer], axis=2
255+
)
256+
257+
self.dataset.write(origin_with_offset, data)
258+
259+
self.buffer = []
260+
261+
def close(self):
262+
263+
self._write_buffer()
264+
self.dataset.close()
265+
266+
def __enter__(self):
267+
return self
268+
269+
def __exit__(self, type, value, tb):
270+
self.close()

0 commit comments

Comments
 (0)