From 7bc97738fae438870d1e1addc17fab12326e05af Mon Sep 17 00:00:00 2001 From: Mark Bader Date: Mon, 29 Apr 2024 13:27:46 +0200 Subject: [PATCH] Add layer_name to kwargs of from_images (#1054) * Add layer_name to parameters of from_images method. * Update Changelog.md * Update webknossos/webknossos/dataset/dataset.py Co-authored-by: Norman Rzepka --------- Co-authored-by: Norman Rzepka --- webknossos/Changelog.md | 1 + webknossos/tests/dataset/test_from_images.py | 14 ++++++----- webknossos/webknossos/cli/convert.py | 7 ++++++ webknossos/webknossos/dataset/dataset.py | 26 ++++++++++++++++---- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/webknossos/Changelog.md b/webknossos/Changelog.md index 1aa0636be..6d7c19514 100644 --- a/webknossos/Changelog.md +++ b/webknossos/Changelog.md @@ -17,6 +17,7 @@ For upgrade instructions, please check the respective _Breaking Changes_ section ### Added ### Changed +- Added `layer_name` as optional argument to `Dataset.from_images` method. If the created dataset contains only a single layer, `layer_name` is used, otherwise the given `layer_name` is a common prefix for all layers. [1054](https://github.com/scalableminds/webknossos-libs/pull/1054) - The context variable of View.get_buffered_slice_writer() is a BufferedSliceWriter now instead of a Generator. Interaction with the SliceWriter does not change, but updating the offset after first initialization is possible now. [1052](https://github.com/scalableminds/webknossos-libs/pull/1052) ### Fixed diff --git a/webknossos/tests/dataset/test_from_images.py b/webknossos/tests/dataset/test_from_images.py index 68cc2ab03..3bfcf989a 100644 --- a/webknossos/tests/dataset/test_from_images.py +++ b/webknossos/tests/dataset/test_from_images.py @@ -25,13 +25,14 @@ def test_compare_tifffile(tmp_path: Path) -> None: tmp_path, (1, 1, 1), compress=True, + layer_name="tiff_stack", layer_category="segmentation", chunks_per_shard=(8, 8, 8), map_filepath_to_layer_name=wk.Dataset.ConversionLayerMapping.ENFORCE_SINGLE_LAYER, ) assert len(ds.layers) == 1 - assert "tiff" in ds.layers - data = ds.layers["tiff"].get_finest_mag().read()[0, :, :] + assert "tiff_stack" in ds.layers + data = ds.layers["tiff_stack"].get_finest_mag().read()[0, :, :] for z_index in range(0, data.shape[-1]): with TiffFile(TESTDATA_DIR / "tiff" / "test.0000.tiff") as tif_file: comparison_slice = tif_file.asarray().T @@ -43,14 +44,15 @@ def test_multiple_multitiffs(tmp_path: Path) -> None: TESTDATA_DIR / "various_tiff_formats", tmp_path, (1, 1, 1), + layer_name="tiffs", ) assert len(ds.layers) == 4 expected_dtype_channels_size_per_layer = { - "test_CS.tif": ("uint8", 3, (128, 128, 320)), - "test_C.tif": ("uint8", 1, (128, 128, 320)), - "test_I.tif": ("uint32", 1, (64, 128, 64)), - "test_S.tif": ("uint16", 3, (128, 128, 64)), + "tiffs_test_CS.tif": ("uint8", 3, (128, 128, 320)), + "tiffs_test_C.tif": ("uint8", 1, (128, 128, 320)), + "tiffs_test_I.tif": ("uint32", 1, (64, 128, 64)), + "tiffs_test_S.tif": ("uint16", 3, (128, 128, 64)), } for layer_name, layer in ds.layers.items(): diff --git a/webknossos/webknossos/cli/convert.py b/webknossos/webknossos/cli/convert.py index e61a18b30..eac4e024a 100644 --- a/webknossos/webknossos/cli/convert.py +++ b/webknossos/webknossos/cli/convert.py @@ -45,6 +45,12 @@ def main( metavar="VOXEL_SIZE", ), ], + layer_name: Annotated[ + Optional[str], + typer.Option( + help="This name is used if only one layer is created. Otherwise this name is used as a common prefix for all layers.", + ), + ] = None, category: Annotated[ Optional[LayerCategory], typer.Option( @@ -107,5 +113,6 @@ def main( data_format=data_format, executor=executor, compress=compress, + layer_name=layer_name, layer_category=category.value if category else None, ) diff --git a/webknossos/webknossos/dataset/dataset.py b/webknossos/webknossos/dataset/dataset.py index e1a804191..17cfdc7c4 100644 --- a/webknossos/webknossos/dataset/dataset.py +++ b/webknossos/webknossos/dataset/dataset.py @@ -566,6 +566,7 @@ def from_images( ConversionLayerMapping, Callable[[Path], str] ] = ConversionLayerMapping.INSPECT_SINGLE_FILE, z_slices_sort_key: Callable[[Path], Any] = natsort_keygen(), + layer_name: Optional[str] = None, layer_category: Optional[LayerCategoryType] = None, data_format: Union[str, DataFormat] = DEFAULT_DATA_FORMAT, chunk_shape: Optional[Union[Vec3IntLike, int]] = None, @@ -593,6 +594,9 @@ def from_images( The order of the z-slices can be customized by setting `z_slices_sort_key`. + If a layer_name is set, this name is used if a single layer is created. + Otherwise the layer_name is used as a common prefix for all layers. + The category of layers (`color` vs `segmentation`) is determined automatically by checking if `segmentation` is part of the path. Alternatively, a category can be enforced by passing `layer_category`. @@ -634,20 +638,32 @@ def from_images( filepaths_per_layer: Dict[str, List[Path]] = {} for input_file in input_files: - layer_name = map_filepath_to_layer_name(input_file) + layer_name_from_mapping = map_filepath_to_layer_name(input_file) # Remove characters from layer name that are not allowed - layer_name = _UNALLOWED_LAYER_NAME_CHARS.sub("", layer_name) + layer_name_from_mapping = _UNALLOWED_LAYER_NAME_CHARS.sub( + "", layer_name_from_mapping + ) # Ensure layer name does not start with a dot - layer_name = layer_name.lstrip(".") + layer_name_from_mapping = layer_name_from_mapping.lstrip(".") assert ( - layer_name != "" + layer_name_from_mapping != "" ), f"Could not determine a layer name for {input_file}." - filepaths_per_layer.setdefault(layer_name, []).append( + filepaths_per_layer.setdefault(layer_name_from_mapping, []).append( input_path / input_file ) + if layer_name is not None: + if len(filepaths_per_layer) == 1: + filepaths_per_layer[layer_name] = filepaths_per_layer.pop( + layer_name_from_mapping + ) + else: + filepaths_per_layer = { + f"{layer_name}_{k}": v for k, v in filepaths_per_layer.items() + } + for layer_name, filepaths in filepaths_per_layer.items(): filepaths.sort(key=z_slices_sort_key) category: LayerCategoryType