Skip to content

Commit baaa337

Browse files
authored
relax layer name constraints (#1402)
* relax layer name constraints * revert attachment check * revert attachment eq * diag * better num_channel detection * changelog
1 parent 8ecf5f0 commit baaa337

File tree

4 files changed

+34
-10
lines changed

4 files changed

+34
-10
lines changed

webknossos/Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ For upgrade instructions, please check the respective _Breaking Changes_ section
1717
### Added
1818

1919
### Changed
20+
- Relaxed layer name validation to match WEBKNOSSOS behavior. [#1402](https://github.com/scalableminds/webknossos-libs/pull/1402)
21+
- Better heuristic for detecting number of channels, when `numChannels` is missing. [#1402](https://github.com/scalableminds/webknossos-libs/pull/1402)
2022

2123
### Fixed
2224
- Fix mag and attachment paths in Zarr annotation volume layer export. [#1401](https://github.com/scalableminds/webknossos-libs/pull/1401)

webknossos/webknossos/dataset/dataset.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ def _find_array_info(layer_path: UPath) -> ArrayInfo | None:
146146
return None
147147

148148

149+
def _validate_layer_name(layer_name: str) -> None:
150+
from webknossos.dataset.layer.abstract_layer import _ALLOWED_LAYER_NAME_REGEX
151+
152+
if _ALLOWED_LAYER_NAME_REGEX.match(layer_name) is None:
153+
raise ValueError(
154+
f"The layer name '{layer_name}' is invalid. It must only contain letters, numbers, underscores, hyphens and dots."
155+
)
156+
157+
149158
class Dataset(AbstractDataset[Layer, SegmentationLayer]):
150159
"""A dataset is the entry point of the Dataset API.
151160
@@ -419,9 +428,12 @@ def _SegmentationLayerType(self) -> type[SegmentationLayer]:
419428
def _initialize_layer_from_properties(
420429
self, properties: LayerProperties, read_only: bool
421430
) -> Layer:
422-
# If the numChannels key is not present in the dataset properties, assume it is 1.
431+
# If the numChannels key is not present in the dataset properties, assume it is 1 unless we have uint24.
423432
if properties.num_channels is None:
424-
properties.num_channels = 1
433+
if properties.element_class == "uint24":
434+
properties.num_channels = 3
435+
else:
436+
properties.num_channels = 1
425437
return super()._initialize_layer_from_properties(properties, read_only)
426438

427439
@classmethod
@@ -1052,6 +1064,8 @@ def add_layer(
10521064

10531065
self._ensure_writable()
10541066

1067+
_validate_layer_name(layer_name)
1068+
10551069
if num_channels is None:
10561070
num_channels = 1
10571071

@@ -1243,6 +1257,8 @@ def add_layer_like(
12431257
) -> Layer:
12441258
self._ensure_writable()
12451259

1260+
_validate_layer_name(layer_name)
1261+
12461262
if layer_name in self.layers.keys():
12471263
raise IndexError(
12481264
f"Adding layer {layer_name} failed. There is already a layer with this name"
@@ -1339,6 +1355,8 @@ def add_layer_for_existing_files(
13391355
Magnifications are discovered automatically.
13401356
"""
13411357
self._ensure_writable()
1358+
1359+
_validate_layer_name(layer_name)
13421360
assert layer_name not in self.layers, f"Layer {layer_name} already exists!"
13431361

13441362
array_info = _find_array_info(self.path / layer_name)
@@ -1445,6 +1463,8 @@ def add_layer_from_images(
14451463
* `truncate_rgba_to_rgb`: only applies if `allow_multiple_layers=True`, set to `False` to write four channels into layers instead of an RGB channel
14461464
* `executor`: pass a `ClusterExecutor` instance to parallelize the conversion jobs across the batches
14471465
"""
1466+
1467+
_validate_layer_name(layer_name)
14481468
if category is None:
14491469
image_path_for_category_guess: UPath
14501470
if (
@@ -1965,6 +1985,8 @@ def add_layer_as_copy(
19651985

19661986
if new_layer_name is None:
19671987
new_layer_name = foreign_layer.name
1988+
else:
1989+
_validate_layer_name(new_layer_name)
19681990

19691991
if exists_ok:
19701992
layer = self.get_or_add_layer(
@@ -2192,6 +2214,8 @@ def add_layer_as_ref(
21922214

21932215
if new_layer_name is None:
21942216
new_layer_name = foreign_layer.name
2217+
else:
2218+
_validate_layer_name(new_layer_name)
21952219

21962220
if new_layer_name in self.layers.keys():
21972221
raise IndexError(

webknossos/webknossos/dataset/layer/abstract_layer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def _dtype_per_layer_to_dtype_per_channel(
7070
)
7171
except TypeError as e:
7272
raise TypeError(
73-
"Converting dtype_per_layer to dtype_per_channel failed. Double check if the dtype_per_layer value is correct."
73+
f"Converting dtype_per_layer to dtype_per_channel failed. Double check if the dtype_per_layer value is correct. Got {dtype_per_layer} and {num_channels} channels."
7474
) from e
7575

7676

@@ -111,8 +111,8 @@ def __init__(
111111
# It is possible that the properties on disk do not contain the number of channels.
112112
# Therefore, the parameter is optional. However at this point, 'num_channels' was already inferred.
113113
assert properties.num_channels is not None
114-
assert _ALLOWED_LAYER_NAME_REGEX.match(properties.name), (
115-
f"The layer name '{properties.name}' is invalid. It must only contain letters, numbers, underscores, hyphens and dots."
114+
assert "/" not in properties.name and not properties.name.startswith("."), (
115+
f"The layer name '{properties.name}' is invalid."
116116
)
117117
self._dataset = dataset
118118
self._name: str = properties.name # The name is also stored in the properties, but the name is required to get the properties.

webknossos/webknossos/dataset/layer/layer.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def name(self, layer_name: str) -> None:
224224
Renames the layer to `layer_name`. This changes the name of the directory on disk and updates the properties.
225225
Only layers on local file systems can be renamed.
226226
"""
227-
from webknossos.dataset.layer.abstract_layer import _ALLOWED_LAYER_NAME_REGEX
227+
from webknossos.dataset.dataset import _validate_layer_name
228228

229229
if layer_name == self.name:
230230
return
@@ -235,10 +235,8 @@ def name(self, layer_name: str) -> None:
235235
raise ValueError(
236236
f"Failed to rename layer {self.name} to {layer_name}: The new name already exists."
237237
)
238-
if _ALLOWED_LAYER_NAME_REGEX.match(layer_name) is None:
239-
raise ValueError(
240-
f"The layer name '{layer_name}' is invalid. It must only contain letters, numbers, underscores, hyphens and dots."
241-
)
238+
239+
_validate_layer_name(layer_name)
242240

243241
if self.path.exists():
244242
assert is_fs_path(self.dataset.path), (

0 commit comments

Comments
 (0)