diff --git a/testdata/simple_wk_dataset/datasource-properties.json b/testdata/simple_wk_dataset/datasource-properties.json index ab4417ed8..162dd2ddf 100644 --- a/testdata/simple_wk_dataset/datasource-properties.json +++ b/testdata/simple_wk_dataset/datasource-properties.json @@ -11,7 +11,7 @@ "dataFormat": "wkw", "name": "color", "category": "color", - "elementClass": "uint8", + "elementClass": "uint24", "num_channels": 3, "boundingBox": { "topLeft": [ diff --git a/tests/test_dataset.py b/tests/test_dataset.py index 80ea55397..cae39e884 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -32,7 +32,7 @@ def chunk_job(args): view, additional_args = args # increment the color value of each voxel - data = view.read(view.size) + data = view.read(size=view.size) if data.shape[0] == 1: data = data[0, :, :, :] data += 50 @@ -43,7 +43,7 @@ def advanced_chunk_job(args): view, additional_args = args # write different data for each chunk (depending on the global_offset of the chunk) - data = view.read(view.size) + data = view.read(size=view.size) data = np.ones(data.shape, dtype=np.uint8) * np.uint8(sum(view.global_offset)) view.write(data) @@ -108,7 +108,7 @@ def for_each_chunking_advanced(ds, view): ((128, 128, 10), (32, 32, 54)), ]: chunk = ds.get_view("color", "1", size=size, offset=offset, is_bounded=False) - chunk_data = chunk.read(chunk.size) + chunk_data = chunk.read(size=chunk.size) assert np.array_equal( np.ones(chunk_data.shape, dtype=np.uint8) * np.uint8(sum(chunk.global_offset)), @@ -147,7 +147,7 @@ def test_create_wk_dataset_with_explicit_header_fields(): delete_dir("./testoutput/wk_dataset_advanced") ds = WKDataset.create("./testoutput/wk_dataset_advanced", scale=(1, 1, 1)) - ds.add_layer("color", "color", dtype=np.uint16, num_channels=3) + ds.add_layer("color", "color", dtype_per_layer="uint48", num_channels=3) ds.get_layer("color").add_mag("1", block_len=64, file_len=64) ds.get_layer("color").add_mag("2-2-1") @@ -158,7 +158,7 @@ def test_create_wk_dataset_with_explicit_header_fields(): assert len(ds.properties.data_layers) == 1 assert len(ds.properties.data_layers["color"].wkw_magnifications) == 2 - assert ds.properties.data_layers["color"].element_class == np.dtype(np.uint16) + assert ds.properties.data_layers["color"].element_class == "uint48" assert ( ds.properties.data_layers["color"].wkw_magnifications[0].cube_length == 64 * 64 ) # mag "1" @@ -212,7 +212,7 @@ def test_view_read_with_open(): with wk_view.open(): assert wk_view._is_opened - data = wk_view.read((10, 10, 10)) + data = wk_view.read(size=(10, 10, 10)) assert data.shape == (3, 10, 10, 10) # three channel assert not wk_view._is_opened @@ -224,7 +224,7 @@ def test_tiff_mag_read_with_open(): layer = tiff_dataset.get_layer("color") mag = layer.get_mag("1") mag.open() - data = mag.read((10, 10, 10)) + data = mag.read(size=(10, 10, 10)) assert data.shape == (1, 10, 10, 10) # single channel @@ -238,7 +238,7 @@ def test_view_read_without_open(): assert not wk_view._is_opened # 'read()' checks if it was already opened. If not, it opens and closes automatically - data = wk_view.read((10, 10, 10)) + data = wk_view.read(size=(10, 10, 10)) assert data.shape == (3, 10, 10, 10) # three channel assert not wk_view._is_opened @@ -258,7 +258,7 @@ def test_view_wk_write(): wk_view.write(write_data) - data = wk_view.read((10, 10, 10)) + data = wk_view.read(size=(10, 10, 10)) assert np.array_equal(data, write_data) @@ -276,7 +276,7 @@ def test_view_tiff_write(): tiff_view.write(write_data) - data = tiff_view.read((5, 5, 5)) + data = tiff_view.read(size=(5, 5, 5)) assert data.shape == (1, 5, 5, 5) # this dataset has only one channel assert np.array_equal(data, np.expand_dims(write_data, 0)) @@ -436,7 +436,7 @@ def test_other_file_extensions_for_tiff_dataset(): np.random.seed(1234) write_data = (np.random.rand(10, 10, 10) * 255).astype(np.uint8) mag.write(write_data) - assert np.array_equal(mag.read((10, 10, 10)), np.expand_dims(write_data, 0)) + assert np.array_equal(mag.read(size=(10, 10, 10)), np.expand_dims(write_data, 0)) def test_tiff_write_multi_channel_uint8(): @@ -475,7 +475,7 @@ def test_tiff_write_multi_channel_uint16(): ds_tiff = TiffDataset.create(dataset_path, scale=(1, 1, 1)) mag = ds_tiff.add_layer( - "color", Layer.COLOR_TYPE, num_channels=3, dtype=np.uint16 + "color", Layer.COLOR_TYPE, num_channels=3, dtype_per_layer="uint48" ).add_mag("1") # 10 images (z-layers), each 250x200, dtype=np.uint16 @@ -495,7 +495,7 @@ def test_wk_write_multi_channel_uint16(): ds_tiff = WKDataset.create(dataset_path, scale=(1, 1, 1)) mag = ds_tiff.add_layer( - "color", Layer.COLOR_TYPE, num_channels=3, dtype=np.uint16 + "color", Layer.COLOR_TYPE, num_channels=3, dtype_per_layer="uint48" ).add_mag("1") # 10 images (z-layers), each 250x200, dtype=np.uint16 @@ -516,7 +516,7 @@ def test_wkw_empty_read(): .add_layer("color", Layer.COLOR_TYPE) .add_mag("1") ) - data = mag.read(size=(0, 0, 0), offset=(1, 1, 1)) + data = mag.read(offset=(1, 1, 1), size=(0, 0, 0)) assert data.shape == (1, 0, 0, 0) @@ -530,7 +530,7 @@ def test_tiff_empty_read(): .add_layer("color", Layer.COLOR_TYPE) .add_mag("1") ) - data = mag.read(size=(0, 0, 0), offset=(1, 1, 1)) + data = mag.read(offset=(1, 1, 1), size=(0, 0, 0)) assert data.shape == (1, 0, 0, 0) @@ -612,22 +612,22 @@ def test_get_or_add_layer(): # layer did not exist before layer = ds.get_or_add_layer( - "color", Layer.COLOR_TYPE, dtype=np.uint8, num_channels=1 + "color", Layer.COLOR_TYPE, dtype_per_layer="uint8", num_channels=1 ) assert "color" in ds.layers.keys() assert layer.name == "color" # layer did exist before layer = ds.get_or_add_layer( - "color", Layer.COLOR_TYPE, dtype=np.uint8, num_channels=1 + "color", Layer.COLOR_TYPE, dtype_per_layer="uint8", num_channels=1 ) assert "color" in ds.layers.keys() assert layer.name == "color" try: - # layer did exist before but with another 'dtype' (this would work the same for 'category' and 'num_channels') + # layer did exist before but with another 'dtype_per_layer' (this would work the same for 'category' and 'num_channels') layer = ds.get_or_add_layer( - "color", Layer.COLOR_TYPE, dtype=np.uint16, num_channels=1 + "color", Layer.COLOR_TYPE, dtype_per_layer="uint16", num_channels=1 ) raise Exception( @@ -701,7 +701,7 @@ def test_tiled_tiff_read_and_write_multichannel(): data = get_multichanneled_data(np.uint8) mag.write(data, offset=(5, 5, 5)) - written_data = mag.read(size=(250, 200, 10), offset=(5, 5, 5)) + written_data = mag.read(offset=(5, 5, 5), size=(250, 200, 10)) assert written_data.shape == (3, 250, 200, 10) assert np.array_equal(data, written_data) @@ -724,7 +724,7 @@ def test_tiled_tiff_read_and_write(): data[i, j, h] = i + j % 250 mag.write(data, offset=(5, 5, 5)) - written_data = mag.read(size=(250, 200, 10), offset=(5, 5, 5)) + written_data = mag.read(offset=(5, 5, 5), size=(250, 200, 10)) assert written_data.shape == (1, 250, 200, 10) assert np.array_equal(written_data, np.expand_dims(data, 0)) @@ -868,7 +868,7 @@ def test_chunking_wk(): "color", "1", size=(256, 256, 256), is_bounded=False ) - original_data = view.read(view.size) + original_data = view.read(size=view.size) with get_executor_for_args(None) as executor: view.for_each_chunk( @@ -878,7 +878,7 @@ def test_chunking_wk(): executor=executor, ) - assert np.array_equal(original_data + 50, view.read(view.size)) + assert np.array_equal(original_data + 50, view.read(size=view.size)) def test_chunking_wk_advanced(): @@ -916,7 +916,7 @@ def test_chunking_tiff(): "color", "1", size=(265, 265, 10) ) - original_data = view.read(view.size) + original_data = view.read(size=view.size) with get_executor_for_args(None) as executor: view.for_each_chunk( @@ -926,7 +926,7 @@ def test_chunking_tiff(): executor=executor, ) - new_data = view.read(view.size) + new_data = view.read(size=view.size) assert np.array_equal(original_data + 50, new_data) @@ -992,7 +992,7 @@ def test_tiled_tiff_inverse_pattern(): data[i, j, h] = i + j % 250 mag.write(data, offset=(5, 5, 5)) - written_data = mag.read(size=(250, 200, 10), offset=(5, 5, 5)) + written_data = mag.read(offset=(5, 5, 5), size=(250, 200, 10)) assert written_data.shape == (1, 250, 200, 10) assert np.array_equal(written_data, np.expand_dims(data, 0)) @@ -1174,14 +1174,14 @@ def test_changing_layer_bounding_box(): bbox_size = ds.properties.data_layers["color"].get_bounding_box_size() assert bbox_size == (265, 265, 10) - original_data = mag.read(bbox_size) + original_data = mag.read(size=bbox_size) assert original_data.shape == (1, 265, 265, 10) layer.set_bounding_box_size((100, 100, 10)) # decrease boundingbox bbox_size = ds.properties.data_layers["color"].get_bounding_box_size() assert bbox_size == (100, 100, 10) - less_data = mag.read(bbox_size) + less_data = mag.read(size=bbox_size) assert less_data.shape == (1, 100, 100, 10) assert np.array_equal(original_data[:, :100, :100, :10], less_data) @@ -1189,7 +1189,7 @@ def test_changing_layer_bounding_box(): bbox_size = ds.properties.data_layers["color"].get_bounding_box_size() assert bbox_size == (300, 300, 10) - more_data = mag.read(bbox_size) + more_data = mag.read(size=bbox_size) assert more_data.shape == (1, 300, 300, 10) assert np.array_equal(more_data[:, :265, :265, :10], original_data) @@ -1205,7 +1205,7 @@ def test_changing_layer_bounding_box(): new_bbox_size = ds.properties.data_layers["color"].get_bounding_box_size() assert new_bbox_offset == (10, 10, 0) assert new_bbox_size == (255, 255, 10) - new_data = mag.read(new_bbox_size) + new_data = mag.read(size=new_bbox_size) assert new_data.shape == (1, 255, 255, 10) assert np.array_equal(original_data[:, 10:, 10:, :], new_data) @@ -1282,6 +1282,31 @@ def test_view_offsets(): pass +def test_adding_layer_with_invalid_dtype_per_layer(): + delete_dir("./testoutput/invalid_dtype") + + ds = WKDataset.create("./testoutput/invalid_dtype", scale=(1, 1, 1)) + with pytest.raises(TypeError): + # this would lead to a dtype_per_channel of "uint10", but that is not a valid dtype + ds.add_layer("color", "color", dtype_per_layer="uint30", num_channels=3) + with pytest.raises(TypeError): + # "int" is interpreted as "int64", but 64 bit cannot be split into 3 channels + ds.add_layer("color", "color", dtype_per_layer="int", num_channels=3) + ds.add_layer( + "color", "color", dtype_per_layer="int", num_channels=4 + ) # "int"/"int64" works with 4 channels + + +def test_adding_layer_with_valid_dtype_per_layer(): + delete_dir("./testoutput/valid_dtype") + + ds = WKDataset.create("./testoutput/valid_dtype", scale=(1, 1, 1)) + ds.add_layer("color1", Layer.COLOR_TYPE, dtype_per_layer="uint24", num_channels=3) + ds.add_layer("color2", Layer.COLOR_TYPE, dtype_per_layer=np.uint8, num_channels=1) + ds.add_layer("color3", Layer.COLOR_TYPE, dtype_per_channel=np.uint8, num_channels=3) + ds.add_layer("color4", Layer.COLOR_TYPE, dtype_per_channel="uint8", num_channels=3) + + def test_writing_subset_of_compressed_data_multi_channel(): delete_dir("./testoutput/compressed_data/") @@ -1458,5 +1483,5 @@ def test_add_symlink_layer(): write_data = (np.random.rand(3, 10, 10, 10) * 255).astype(np.uint8) mag.write(write_data) - assert np.array_equal(mag.read((10, 10, 10)), write_data) - assert np.array_equal(original_mag.read((10, 10, 10)), write_data) + assert np.array_equal(mag.read(size=(10, 10, 10)), write_data) + assert np.array_equal(original_mag.read(size=(10, 10, 10)), write_data) diff --git a/wkcuber/api/Dataset.py b/wkcuber/api/Dataset.py index 112bc8df3..ed56bafaa 100644 --- a/wkcuber/api/Dataset.py +++ b/wkcuber/api/Dataset.py @@ -1,3 +1,4 @@ +import operator from shutil import rmtree from abc import ABC, abstractmethod from os import makedirs, path @@ -5,6 +6,7 @@ from pathlib import Path import numpy as np import os +import re from wkcuber.api.Properties.DatasetProperties import ( WKProperties, @@ -14,6 +16,54 @@ from wkcuber.api.Layer import Layer, WKLayer, TiffLayer, TiledTiffLayer from wkcuber.api.View import View +DEFAULT_BIT_DEPTH = 8 + + +def is_int(s: str) -> bool: + try: + int(s) + return True + except ValueError: + return False + + +def convert_dtypes(dtype, num_channels, dtype_per_layer_to_dtype_per_channel): + if dtype is None: + return None + op = operator.truediv if dtype_per_layer_to_dtype_per_channel else operator.mul + + # split the dtype into the actual type and the number of bits + # example: "uint24" -> ["uint", "24"] + dtype_parts = re.split("(\d+)", str(dtype)) + # calculate number of bits for dtype_per_channel + converted_dtype_parts = [ + (str(int(op(int(part), num_channels))) if is_int(part) else part) + for part in dtype_parts + ] + return "".join(converted_dtype_parts) + + +def dtype_per_layer_to_dtype_per_channel(dtype_per_layer, num_channels): + try: + return np.dtype( + convert_dtypes( + dtype_per_layer, num_channels, dtype_per_layer_to_dtype_per_channel=True + ) + ) + except TypeError as e: + raise TypeError( + "Converting dtype_per_layer to dtype_per_channel failed. Double check if the dtype_per_layer value is correct. " + + str(e) + ) + + +def dtype_per_channel_to_dtype_per_layer(dtype_per_channel, num_channels): + return convert_dtypes( + np.dtype(dtype_per_channel), + num_channels, + dtype_per_layer_to_dtype_per_channel=False, + ) + class AbstractDataset(ABC): @abstractmethod @@ -30,7 +80,10 @@ def __init__(self, dataset_path): for layer_name in self.properties.data_layers: layer = self.properties.data_layers[layer_name] self.add_layer( - layer.name, layer.category, layer.element_class, layer.num_channels + layer.name, + layer.category, + dtype_per_layer=layer.element_class, + num_channels=layer.num_channels, ) for resolution in layer.wkw_magnifications: self.layers[layer_name].setup_mag(resolution.mag.to_layer_name()) @@ -72,14 +125,44 @@ def get_layer(self, layer_name) -> Layer: ) return self.layers[layer_name] - def add_layer(self, layer_name, category, dtype=None, num_channels=None, **kwargs): - if dtype is None: - dtype = np.dtype("uint8") + def add_layer( + self, + layer_name, + category, + dtype_per_layer=None, + dtype_per_channel=None, + num_channels=None, + **kwargs, + ): if num_channels is None: num_channels = 1 - # normalize the value of dtype in case the parameter was passed as a string - dtype = np.dtype(dtype) + if dtype_per_layer is not None and dtype_per_channel is not None: + raise AttributeError( + "Cannot add layer. Specifying both 'dtype_per_layer' and 'dtype_per_channel' is not allowed" + ) + elif dtype_per_channel is not None: + try: + dtype_per_channel = np.dtype(dtype_per_channel) + except TypeError as e: + raise TypeError( + "Cannot add layer. The specified 'dtype_per_channel' must be a valid dtype. " + + str(e) + ) + dtype_per_layer = dtype_per_channel_to_dtype_per_layer( + dtype_per_channel, num_channels + ) + elif dtype_per_layer is not None: + try: + dtype_per_layer = str(np.dtype(dtype_per_layer)) + except Exception: + pass # casting to np.dtype fails if the user specifies a special dtype like "uint24" + dtype_per_channel = dtype_per_layer_to_dtype_per_channel( + dtype_per_layer, num_channels + ) + else: + dtype_per_layer = "uint" + str(DEFAULT_BIT_DEPTH * num_channels) + dtype_per_channel = np.dtype("uint" + str(DEFAULT_BIT_DEPTH)) if layer_name in self.layers.keys(): raise IndexError( @@ -87,14 +170,28 @@ def add_layer(self, layer_name, category, dtype=None, num_channels=None, **kwarg layer_name ) ) + self.properties._add_layer( - layer_name, category, dtype.name, self.data_format, num_channels, **kwargs + layer_name, + category, + dtype_per_layer, + self.data_format, + num_channels, + **kwargs, + ) + self.layers[layer_name] = self._create_layer( + layer_name, dtype_per_channel, num_channels ) - self.layers[layer_name] = self._create_layer(layer_name, dtype, num_channels) return self.layers[layer_name] def get_or_add_layer( - self, layer_name, category, dtype=None, num_channels=None, **kwargs + self, + layer_name, + category, + dtype_per_layer=None, + dtype_per_channel=None, + num_channels=None, + **kwargs, ) -> Layer: if layer_name in self.layers.keys(): assert self.properties.data_layers[layer_name].category == category, ( @@ -102,10 +199,16 @@ def get_or_add_layer( + f"The category of the existing layer is '{self.properties.data_layers[layer_name].category}' " + f"and the passed parameter is '{category}'." ) - assert dtype is None or self.layers[layer_name].dtype == np.dtype(dtype), ( + dtype_per_channel = dtype_per_layer_to_dtype_per_channel( + dtype_per_layer, num_channels + ) + assert ( + dtype_per_layer is None + or self.layers[layer_name].dtype_per_channel == dtype_per_channel + ), ( f"Cannot get_or_add_layer: The layer '{layer_name}' already exists, but the dtypes do not match. " - + f"The dtype of the existing layer is '{self.layers[layer_name].dtype}' " - + f"and the passed parameter is '{dtype}'." + + f"The dtype_per_channel of the existing layer is '{self.layers[layer_name].dtype_per_channel}' " + + f"and the passed parameter would result in a dtype_per_channel of '{dtype_per_channel}'." ) assert ( num_channels is None @@ -117,7 +220,14 @@ def get_or_add_layer( ) return self.layers[layer_name] else: - return self.add_layer(layer_name, category, dtype, num_channels, **kwargs) + return self.add_layer( + layer_name, + category, + dtype_per_layer=dtype_per_layer, + dtype_per_channel=dtype_per_channel, + num_channels=num_channels, + **kwargs, + ) def delete_layer(self, layer_name): if layer_name not in self.layers.keys(): @@ -148,7 +258,9 @@ def add_symlink_layer(self, foreign_layer_path) -> Layer: self.layers[layer_name] = self._create_layer( layer_name, - np.dtype(layer_properties.element_class), + dtype_per_layer_to_dtype_per_channel( + layer_properties.element_class, layer_properties.num_channels + ), layer_properties.num_channels, ) for resolution in layer_properties.wkw_magnifications: @@ -161,7 +273,7 @@ def get_view(self, layer_name, mag, size, offset=None, is_bounded=True) -> View: return mag_ds.get_view(size=size, offset=offset, is_bounded=is_bounded) - def _create_layer(self, layer_name, dtype, num_channels) -> Layer: + def _create_layer(self, layer_name, dtype_per_channel, num_channels) -> Layer: raise NotImplementedError @abstractmethod @@ -201,8 +313,8 @@ def __init__(self, dataset_path): def to_tiff_dataset(self, new_dataset_path): raise NotImplementedError # TODO; implement - def _create_layer(self, layer_name, dtype, num_channels) -> Layer: - return WKLayer(layer_name, self, dtype, num_channels) + def _create_layer(self, layer_name, dtype_per_channel, num_channels) -> Layer: + return WKLayer(layer_name, self, dtype_per_channel, num_channels) def _get_properties_type(self): return WKProperties @@ -253,8 +365,8 @@ def __init__(self, dataset_path): def to_wk_dataset(self, new_dataset_path): raise NotImplementedError # TODO; implement - def _create_layer(self, layer_name, dtype, num_channels) -> Layer: - return TiffLayer(layer_name, self, dtype, num_channels) + def _create_layer(self, layer_name, dtype_per_channel, num_channels) -> Layer: + return TiffLayer(layer_name, self, dtype_per_channel, num_channels) def _get_properties_type(self): return TiffProperties @@ -310,8 +422,8 @@ def __init__(self, dataset_path): self.data_format = "tiled_tiff" assert isinstance(self.properties, TiffProperties) - def _create_layer(self, layer_name, dtype, num_channels) -> Layer: - return TiledTiffLayer(layer_name, self, dtype, num_channels) + def _create_layer(self, layer_name, dtype_per_channel, num_channels) -> Layer: + return TiledTiffLayer(layer_name, self, dtype_per_channel, num_channels) def _get_properties_type(self): return TiffProperties diff --git a/wkcuber/api/Layer.py b/wkcuber/api/Layer.py index 0d3041635..41a87262a 100644 --- a/wkcuber/api/Layer.py +++ b/wkcuber/api/Layer.py @@ -22,10 +22,10 @@ class Layer: COLOR_TYPE = "color" SEGMENTATION_TYPE = "segmentation" - def __init__(self, name, dataset, dtype, num_channels): + def __init__(self, name, dataset, dtype_per_channel, num_channels): self.name = name self.dataset = dataset - self.dtype = dtype + self.dtype_per_channel = dtype_per_channel self.num_channels = num_channels self.mags = {} diff --git a/wkcuber/api/MagDataset.py b/wkcuber/api/MagDataset.py index 37a77d484..1decfc53d 100644 --- a/wkcuber/api/MagDataset.py +++ b/wkcuber/api/MagDataset.py @@ -23,8 +23,8 @@ def open(self): def close(self): self.view.close() - def read(self, size, offset=(0, 0, 0)) -> np.array: - return self.view.read(size, offset) + def read(self, offset=(0, 0, 0), size=None) -> np.array: + return self.view.read(offset, size) def write(self, data, offset=(0, 0, 0), allow_compressed_write=False): self._assert_valid_num_channels(data.shape) @@ -128,7 +128,7 @@ def __init__(self, layer, name, block_len, file_len, block_type): def get_header(self) -> wkw.Header: return wkw.Header( - voxel_type=self.layer.dtype, + voxel_type=self.layer.dtype_per_channel, num_channels=self.layer.num_channels, version=1, block_len=self.block_len, @@ -157,7 +157,7 @@ def __init__(self, layer, name, pattern): def get_header(self) -> TiffMagHeader: return TiffMagHeader( pattern=self.pattern, - dtype=self.layer.dtype, + dtype_per_channel=self.layer.dtype_per_channel, num_channels=self.layer.num_channels, tile_size=self.layer.dataset.properties.tile_size, ) @@ -178,4 +178,4 @@ def get_tile(self, x_index, y_index, z_index) -> np.array: offset = np.array((0, 0, 0)) + np.array(size) * np.array( (x_index, y_index, z_index) ) - return self.read(size, offset) + return self.read(offset, size) diff --git a/wkcuber/api/TiffData/TiffMag.py b/wkcuber/api/TiffData/TiffMag.py index f2b941dce..21b433006 100644 --- a/wkcuber/api/TiffData/TiffMag.py +++ b/wkcuber/api/TiffData/TiffMag.py @@ -186,7 +186,7 @@ def read(self, off, shape) -> np.array: # modify the shape to also include the num_channels shape = tuple(shape) + tuple([self.header.num_channels]) - data = np.zeros(shape=shape, dtype=self.header.dtype) + data = np.zeros(shape=shape, dtype=self.header.dtype_per_channel) for ( xyz, _, @@ -198,7 +198,9 @@ def read(self, off, shape) -> np.array: if xyz in self.tiffs: # load data and discard the padded data - loaded_data = np.array(self.tiffs[xyz].read(), self.header.dtype)[ + loaded_data = np.array( + self.tiffs[xyz].read(), self.header.dtype_per_channel + )[ offset_in_output_data[0] : offset_in_output_data[0] + shape[0], offset_in_output_data[1] : offset_in_output_data[1] + shape[1], ] @@ -250,7 +252,7 @@ def write(self, off, data): # initialize an empty image with the right shape self.tiffs[xyz] = TiffReader.init_tiff( - np.zeros(output_data_shape, self.header.dtype), + np.zeros(output_data_shape, self.header.dtype_per_channel), self.get_file_name_for_layer(xyz), ) @@ -363,9 +365,9 @@ def assert_correct_data_format(self, data): raise AttributeError( f"The shape of the provided data does not match the expected shape. (Expected {self.header.num_channels} channels)" ) - if not np.dtype(data.dtype) == self.header.dtype: + if not np.dtype(data.dtype) == self.header.dtype_per_channel: raise AttributeError( - f"The type of the provided data does not match the expected type. (Expected np.array of type {self.header.dtype.name})" + f"The type of the provided data does not match the expected type. (Expected np.array of type {self.header.dtype_per_channel.name})" ) def get_file_name_for_layer(self, xyz) -> str: @@ -389,12 +391,12 @@ class TiffMagHeader: def __init__( self, pattern="{zzzzz}.tif", - dtype=np.dtype("uint8"), + dtype_per_channel=np.dtype("uint8"), num_channels=1, tile_size=(32, 32), ): self.pattern = pattern - self.dtype = np.dtype(dtype) + self.dtype_per_channel = np.dtype(dtype_per_channel) self.num_channels = num_channels self.tile_size = tile_size diff --git a/wkcuber/api/View.py b/wkcuber/api/View.py index d987db296..9b52a120e 100644 --- a/wkcuber/api/View.py +++ b/wkcuber/api/View.py @@ -58,7 +58,7 @@ def write(self, data, relative_offset=(0, 0, 0), allow_compressed_write=False): if not was_opened: self.close() - def read(self, size=None, offset=(0, 0, 0)) -> np.array: + def read(self, offset=(0, 0, 0), size=None) -> np.array: was_opened = self._is_opened size = self.size if size is None else size diff --git a/wkcuber/check_equality.py b/wkcuber/check_equality.py index 0ec2c6e1e..d5652c00e 100644 --- a/wkcuber/check_equality.py +++ b/wkcuber/check_equality.py @@ -62,7 +62,7 @@ def assert_equality_for_chunk( mag_ds = layer.get_mag(mag) logging.info(f"Checking sub_box: {sub_box}") - data = mag_ds.read(sub_box.size, sub_box.topleft) + data = mag_ds.read(sub_box.topleft, sub_box.size) backup_data = backup_wkw.read(sub_box.topleft, sub_box.size) assert np.all( data == backup_data diff --git a/wkcuber/convert_nifti.py b/wkcuber/convert_nifti.py index 557675a3a..c07c15c82 100644 --- a/wkcuber/convert_nifti.py +++ b/wkcuber/convert_nifti.py @@ -140,7 +140,6 @@ def convert_nifti( assume_segmentation_layer = ( False ) # Since webKnossos does not support multiple segmention layers, this is hardcoded to False right now. - max_cell_id_args = ( {"largest_segment_id": int(np.max(cube_data) + 1)} if assume_segmentation_layer @@ -202,7 +201,10 @@ def convert_nifti( if write_tiff: ds = TiffDataset.get_or_create(target_path, scale=scale or (1, 1, 1)) layer = ds.get_or_add_layer( - layer_name, category_type, np.dtype(dtype), **max_cell_id_args + layer_name, + category_type, + dtype_per_layer=np.dtype(dtype), + **max_cell_id_args, ) mag = layer.get_or_add_mag("1") @@ -210,7 +212,10 @@ def convert_nifti( else: ds = WKDataset.get_or_create(target_path, scale=scale or (1, 1, 1)) layer = ds.get_or_add_layer( - layer_name, category_type, np.dtype(dtype), **max_cell_id_args + layer_name, + category_type, + dtype_per_layer=np.dtype(dtype), + **max_cell_id_args, ) mag = layer.get_or_add_mag("1", file_len=file_len) mag.write(cube_data)