diff --git a/README.md b/README.md index e61b1dc..e042e12 100644 --- a/README.md +++ b/README.md @@ -100,10 +100,10 @@ up the slicer (and which must be present in the layout) are: **method `VolumeSlicer.create_overlay_data(mask, color=None)`** -Given a 3D mask array and an index, create an object that -can be used as output for `slicer.overlay_data`. The color -can be a hex color or an rgb/rgba tuple. Alternatively, color -can be a list of such colors, defining a colormap. +Given a 3D mask array, create an object that can be used as +output for `slicer.overlay_data`. Set mask to `None` to clear the mask. +The color can be a hex color or an rgb/rgba tuple. Alternatively, +color can be a list of such colors, defining a colormap. **property `VolumeSlicer.axis`** (`int`): The axis to slice. diff --git a/dash_slicer/slicer.py b/dash_slicer/slicer.py index 6300bb8..c011684 100644 --- a/dash_slicer/slicer.py +++ b/dash_slicer/slicer.py @@ -247,15 +247,19 @@ def overlay_data(self): return self._overlay_data def create_overlay_data(self, mask, color=None): - """Given a 3D mask array and an index, create an object that - can be used as output for `slicer.overlay_data`. The color - can be a hex color or an rgb/rgba tuple. Alternatively, color - can be a list of such colors, defining a colormap. + """Given a 3D mask array, create an object that can be used as + output for `slicer.overlay_data`. Set mask to `None` to clear the mask. + The color can be a hex color or an rgb/rgba tuple. Alternatively, + color can be a list of such colors, defining a colormap. """ # Check the mask - if mask.dtype not in (np.bool, np.uint8): + if mask is None: + return [None for index in range(self.nslices)] # A reset + elif not isinstance(mask, np.ndarray): + raise TypeError("Mask must be an ndarray or None.") + elif mask.dtype not in (np.bool, np.uint8): raise ValueError(f"Mask must have bool or uint8 dtype, not {mask.dtype}.") - if mask.shape != self._volume.shape: + elif mask.shape != self._volume.shape: raise ValueError( f"Overlay must has shape {mask.shape}, but expected {self._volume.shape}" ) @@ -264,6 +268,8 @@ def create_overlay_data(self, mask, color=None): # Create a colormap (list) from the given color(s) if color is None: colormap = discrete_colors[3:] + elif isinstance(color, str): + colormap = [color] elif isinstance(color, (tuple, list)) and all( isinstance(x, (int, float)) for x in color ): diff --git a/tests/test_slicer.py b/tests/test_slicer.py index 089a7d2..c857b74 100644 --- a/tests/test_slicer.py +++ b/tests/test_slicer.py @@ -63,3 +63,43 @@ def test_scene_id_and_context_id(): # Context id's must be unique assert s1._context_id != s2._context_id and s1._context_id != s3._context_id + + +def test_create_overlay_data(): + + app = dash.Dash() + vol = np.random.uniform(0, 255, (100, 100, 100)).astype(np.uint8) + s = VolumeSlicer(app, vol) + + # Bool overlay + overlay = s.create_overlay_data(vol > 10) + assert isinstance(overlay, list) and len(overlay) == s.nslices + assert all(isinstance(x, str) for x in overlay) + + # Bool overlay - with color + overlay = s.create_overlay_data(vol > 10, "#ff0000") + assert isinstance(overlay, list) and len(overlay) == s.nslices + assert all(isinstance(x, str) for x in overlay) + + # Uint8 overlay - with colormap + overlay = s.create_overlay_data(vol.astype(np.uint8), ["#ff0000", "#00ff00"]) + assert isinstance(overlay, list) and len(overlay) == s.nslices + assert all(isinstance(x, str) for x in overlay) + + # Reset + overlay = s.create_overlay_data(None) + assert isinstance(overlay, list) and len(overlay) == s.nslices + assert all(x is None for x in overlay) + + # Reset by zero mask + overlay = s.create_overlay_data(vol > 300) + assert isinstance(overlay, list) and len(overlay) == s.nslices + assert all(x is None for x in overlay) + + # Wrong + with raises(TypeError): + s.create_overlay_data("not a valid mask") + with raises(ValueError): + s.create_overlay_data(vol.astype(np.float32)) # wrong dtype + with raises(ValueError): + s.create_overlay_data(vol[:-1]) # wrong shape