diff --git a/src/ndevio/nimage.py b/src/ndevio/nimage.py index 944d542..fdc9567 100644 --- a/src/ndevio/nimage.py +++ b/src/ndevio/nimage.py @@ -146,6 +146,26 @@ def _initialize_source_state(self, image: ImageLike) -> None: self.path = source self._is_remote = True + def set_scene(self, scene_id: str | int) -> None: + """Switch the operating scene, invalidating cached layer data. + + Extends :meth:`BioImage.set_scene` so that switching scenes clears + this image's lazy ``reference_xarray`` / ``layer_data`` / + ``_layer_metadata`` caches, which are scene-specific. Callers (scene + widget, reader's open-all-scenes path) get correct reload behaviour + without reaching into privates. + + Parameters + ---------- + scene_id : str | int + The scene id (string) or scene index (integer) to switch to. + + """ + super().set_scene(scene_id) + self._reference_xarray = None + self._layer_data = None + self._layer_metadata = None + def _fits_in_memory(self) -> bool: """Return True if the uncompressed image fits comfortably in RAM.""" if self.path is None: diff --git a/src/ndevio/widgets/_scene_widget.py b/src/ndevio/widgets/_scene_widget.py index 586d3a0..88e68ae 100644 --- a/src/ndevio/widgets/_scene_widget.py +++ b/src/ndevio/widgets/_scene_widget.py @@ -114,10 +114,6 @@ def open_scene(self) -> None: scene_index = int(scene.split(DELIMITER)[0]) self.img.set_scene(scene_index) - # Clear cached data so new scene is loaded - self.img._reference_xarray = None - self.img._layer_data = None - # Get layer tuples and add to viewer using napari's Layer.create() from napari.layers import Layer diff --git a/tests/test_nimage.py b/tests/test_nimage.py index 0d90131..9b2a084 100644 --- a/tests/test_nimage.py +++ b/tests/test_nimage.py @@ -514,6 +514,36 @@ def test_colormap_three_plus_channels_uses_multi_channel_cycle( assert colormaps[2] == MULTI_CHANNEL_CYCLE[2] # yellow assert colormaps[3] == MULTI_CHANNEL_CYCLE[3] # blue + +def test_set_scene_invalidates_cached_layer_data(resources_dir: Path): + """Switching scenes clears the lazy reference_xarray / layer_data caches. + + Regression test: the scene widget previously had to clear these privates + by hand, and the reader's open-all-scenes path never did — returning + stale scene-0 data for every scene. ``set_scene`` now owns the + invalidation so every caller reloads the new scene. + """ + img = nImage(resources_dir / CZI_FILE) + assert len(img.scenes) > 1 + + # Prime the caches on the current scene + img.get_layer_data_tuples() + assert img._reference_xarray is not None + assert img._layer_data is not None + assert img._layer_metadata is not None + + img.set_scene(1) + + # Caches must be invalidated so the next access reloads the new scene + assert img._reference_xarray is None + assert img._layer_data is None + assert img._layer_metadata is None + + # And data reloads lazily for the new scene + assert img.reference_xarray is not None + assert img.layer_scale is not None + assert img._layer_metadata is not None + def test_auto_detect_labels_from_channel_name(self, resources_dir: Path): """Test that channels with label-like names are detected as labels.""" import numpy as np