Example #1
0
def test_contrast_limits():
    """Test adding multichannel image with custom contrast limits."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((15, 10, 5))
    clims = [0.3, 0.7]
    viewer.add_multichannel(data, contrast_limits=clims)
    assert len(viewer.layers) == data.shape[-1]
    for i in range(data.shape[-1]):
        assert viewer.layers[i].contrast_limits == clims

    viewer = ViewerModel()
    clims = [[0.3, 0.7], [0.1, 0.9], [0.3, 0.9], [0.4, 0.9], [0.2, 0.9]]
    viewer.add_multichannel(data, contrast_limits=clims)
    assert len(viewer.layers) == data.shape[-1]
    for i in range(data.shape[-1]):
        assert viewer.layers[i].contrast_limits == clims[i]
Example #2
0
def test_not_mutable_fields(field):
    """Test appropriate fields are not mutable."""
    viewer = ViewerModel()

    # Check attribute lives on the viewer
    assert hasattr(viewer, field)
    # Check attribute does not have an event emitter
    assert not hasattr(viewer.events, field)

    # Check attribute is not settable
    with pytest.raises(TypeError) as err:
        setattr(viewer, field, 'test')

    assert 'has allow_mutation set to False and cannot be assigned' in str(
        err.value)
Example #3
0
def test_colormaps():
    """Test adding multichannel image with custom colormaps."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((15, 10, 5))
    colormap = 'gray'
    viewer.add_multichannel(data, colormap=colormap)
    assert len(viewer.layers) == data.shape[-1]
    for i in range(data.shape[-1]):
        assert viewer.layers[i].colormap[0] == colormap

    viewer = ViewerModel()
    colormaps = ['gray', 'blue', 'red', 'green', 'yellow']
    viewer.add_multichannel(data, colormap=colormaps)
    assert len(viewer.layers) == data.shape[-1]
    for i in range(data.shape[-1]):
        assert viewer.layers[i].colormap[0] == colormaps[i]
Example #4
0
def test_gamma():
    """Test adding multichannel image with custom gamma."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((15, 10, 5))
    gamma = 0.7
    viewer.add_image(data, gamma=gamma, channel_axis=-1)
    assert len(viewer.layers) == data.shape[-1]
    for i in range(data.shape[-1]):
        assert viewer.layers[i].gamma == gamma

    viewer = ViewerModel()
    gammas = [0.3, 0.4, 0.5, 0.6, 0.7]
    viewer.add_image(data, gamma=gammas, channel_axis=-1)
    assert len(viewer.layers) == data.shape[-1]
    for i in range(data.shape[-1]):
        assert viewer.layers[i].gamma == gammas[i]
Example #5
0
def test_qt_viewer(qtbot):
    """Test instantiating viewer."""
    viewer = ViewerModel()
    view = QtViewer(viewer)
    qtbot.addWidget(view)

    assert viewer.title == 'napari'
    assert view.viewer == viewer

    assert len(viewer.layers) == 0
    assert view.layers.vbox_layout.count() == 2

    assert viewer.dims.ndim == 2
    assert view.dims.nsliders == 0
    assert np.sum(view.dims._displayed) == 0
Example #6
0
def test_names():
    """Test adding multichannel image with custom names."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((15, 10, 5))
    names = ['multi ' + str(i + 3) for i in range(data.shape[-1])]
    viewer.add_image(data, name=names, channel_axis=-1)
    assert len(viewer.layers) == data.shape[-1]
    for i in range(data.shape[-1]):
        assert viewer.layers[i].name == names[i]

    viewer = ViewerModel()
    name = 'example'
    names = [name] + [name + f' [{i + 1}]' for i in range(data.shape[-1] - 1)]
    viewer.add_image(data, name=name, channel_axis=-1)
    assert len(viewer.layers) == data.shape[-1]
    for i in range(data.shape[-1]):
        assert viewer.layers[i].name == names[i]
Example #7
0
def test_add_multi_png_defaults(two_pngs):
    image_files = two_pngs
    viewer = ViewerModel()
    viewer.open(image_files, stack=True, plugin='builtins')
    assert len(viewer.layers) == 1
    assert viewer.dims.ndim == 3
    assert isinstance(viewer.layers[0].data, da.Array)
    assert viewer.layers[0].data.shape == (2, 512, 512)

    viewer.open(image_files, stack=False, plugin='builtins')
    assert len(viewer.layers) == 3
Example #8
0
def test_active_layer_status_update():
    """Test status updates from active layer on cursor move."""
    viewer = ViewerModel()
    np.random.seed(0)
    viewer.add_image(np.random.random((5, 5, 10, 15)))
    viewer.add_image(np.random.random((5, 6, 5, 10, 15)))
    assert len(viewer.layers) == 2
    assert viewer.active_layer == viewer.layers[1]

    viewer.cursor.position = [1, 1, 1, 1, 1]
    assert viewer.status == viewer.active_layer.status
Example #9
0
def test_active_layer_status_update():
    """Test status updates from active layer on cursor move."""
    viewer = ViewerModel()
    np.random.seed(0)
    viewer.add_image(np.random.random((5, 5, 10, 15)))
    viewer.add_image(np.random.random((5, 6, 5, 10, 15)))
    assert len(viewer.layers) == 2
    assert viewer.layers.selection.active == viewer.layers[1]

    # wait 1 s to avoid the cursor event throttling
    time.sleep(1)
    viewer._mouse_over_canvas = True
    viewer.cursor.position = [1, 1, 1, 1, 1]
    assert viewer.status == viewer.layers.selection.active.get_status(
        viewer.cursor.position, world=True)
Example #10
0
def test_new_labels_image():
    """Test adding new labels layer with image present."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((10, 15))
    viewer.add_image(data)
    viewer._new_labels()
    assert len(viewer.layers) == 2
    assert np.max(viewer.layers[1].data) == 0
    assert viewer.dims.ndim == 2
    np.testing.assert_equal(viewer.layers[1].data.shape, (10, 15))
    np.testing.assert_equal(viewer.layers[1].scale, (1, 1))
    np.testing.assert_equal(viewer.layers[1].translate, (0, 0))
Example #11
0
def test_new_labels_scaled_translated_image():
    """Test adding new labels layer with transformed image present."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((10, 15))
    viewer.add_image(data, scale=(3, 3), translate=(20, -5))
    viewer._new_labels()
    assert len(viewer.layers) == 2
    assert np.max(viewer.layers[1].data) == 0
    assert viewer.dims.ndim == 2
    np.testing.assert_almost_equal(viewer.layers[1].data.shape, (10, 15))
    np.testing.assert_almost_equal(viewer.layers[1].scale, (3, 3))
    np.testing.assert_almost_equal(viewer.layers[1].translate, (20, -5))
Example #12
0
def test_add_delete_layers():
    """Test adding and deleting layers with different dims."""
    viewer = ViewerModel()
    np.random.seed(0)
    viewer.add_image(np.random.random((5, 5, 10, 15)))
    assert len(viewer.layers) == 1
    assert viewer.dims.ndim == 4
    viewer.add_image(np.random.random((5, 6, 5, 10, 15)))
    assert len(viewer.layers) == 2
    assert viewer.dims.ndim == 5
    viewer.layers.remove_selected()
    assert len(viewer.layers) == 1
    assert viewer.dims.ndim == 4
Example #13
0
def test_scaled_images():
    """Test two scaled images."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((10, 10, 10))
    viewer.add_image(data)
    viewer.add_image(data[::2], scale=[2, 1, 1])
    assert viewer.dims.range[0] == (0, 10 - 1, 1)
    assert viewer.dims.range[1] == (0, 10 - 1, 1)
    assert viewer.dims.range[2] == (0, 10 - 1, 1)
    assert viewer.dims.nsteps == (10, 10, 10)
    for i in range(viewer.dims.nsteps[0]):
        viewer.dims.set_current_step(0, i)
        assert viewer.dims.current_step[0] == i
Example #14
0
def test_translated_images():
    """Test two translated images."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((10, 10, 10))
    viewer.add_image(data)
    viewer.add_image(data, translate=[10, 0, 0])
    assert viewer.dims.range[0] == (0, 20 - 1, 1)
    assert viewer.dims.range[1] == (0, 10 - 1, 1)
    assert viewer.dims.range[2] == (0, 10 - 1, 1)
    assert viewer.dims.nsteps == (20, 10, 10)
    for i in range(viewer.dims.nsteps[0]):
        viewer.dims.set_current_step(0, i)
        assert viewer.dims.current_step[0] == i
Example #15
0
def test_both_scaled_and_translated_images():
    """Test both scaled and translated images."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((10, 10, 10))
    viewer.add_image(data, scale=[2, 1, 1])
    viewer.add_image(data, scale=[2, 1, 1], translate=[20, 0, 0])
    assert viewer.dims.range[0] == (0, 40 - 2, 2)
    assert viewer.dims.range[1] == (0, 10 - 1, 1)
    assert viewer.dims.range[2] == (0, 10 - 1, 1)
    assert viewer.dims.nsteps == (20, 10, 10)
    for i in range(viewer.dims.nsteps[0]):
        viewer.dims.set_current_step(0, i)
        assert viewer.dims.current_step[0] == i
Example #16
0
def test_dask_cache_resizing(delayed_dask_stack):
    """Test that we can spin up, resize, and spin down the cache."""

    # make sure we have a cache
    # big enough for 10+ (10, 10, 10) "timepoints"
    resize_dask_cache(100000)

    # add dask stack to the viewer, making sure to pass multiscale and clims

    v = ViewerModel()
    dask_stack = delayed_dask_stack['stack']

    v.add_image(dask_stack)
    assert _dask_utils._DASK_CACHE.cache.available_bytes > 0
    # make sure the cache actually has been populated
    assert len(_dask_utils._DASK_CACHE.cache.heap.heap) > 0

    # we can resize that cache back to 0 bytes
    resize_dask_cache(0)
    assert _dask_utils._DASK_CACHE.cache.available_bytes == 0

    # adding a 2nd stack should not adjust the cache size once created
    v.add_image(dask_stack)
    assert _dask_utils._DASK_CACHE.cache.available_bytes == 0
    # and the cache will remain empty regardless of what we do
    for i in range(3):
        v.dims.set_point(1, i)
    assert len(_dask_utils._DASK_CACHE.cache.heap.heap) == 0

    # but we can always spin it up again
    resize_dask_cache(1e4)
    assert _dask_utils._DASK_CACHE.cache.available_bytes == 1e4
    # and adding a new image doesn't change the size
    v.add_image(dask_stack)
    assert _dask_utils._DASK_CACHE.cache.available_bytes == 1e4
    # but the cache heap is getting populated again
    for i in range(3):
        v.dims.set_point(0, i)
    assert len(_dask_utils._DASK_CACHE.cache.heap.heap) > 0
Example #17
0
def test_mix_dims():
    """Test adding images of mixed dimensionality."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((10, 15))
    viewer.add_image(data)
    assert len(viewer.layers) == 1
    assert np.all(viewer.layers[0].data == data)
    assert viewer.dims.ndim == 2

    data = np.random.random((6, 10, 15))
    viewer.add_image(data)
    assert len(viewer.layers) == 2
    assert np.all(viewer.layers[1].data == data)
    assert viewer.dims.ndim == 3
Example #18
0
def test_active_layer_cursor_size():
    """Test cursor size update on active layer."""
    viewer = ViewerModel()
    np.random.seed(0)
    viewer.add_image(np.random.random((10, 10)))
    # Base layer has a default cursor size of 1
    assert viewer.cursor.size == 1

    viewer.add_labels(np.random.random((10, 10)))
    assert len(viewer.layers) == 2
    assert viewer.active_layer == viewer.layers[1]

    viewer.layers[1].mode = 'paint'
    # Labels layer has a default cursor size of 10
    # due to paintbrush
    assert viewer.cursor.size == 10
Example #19
0
def test_add_layer_from_data_raises():
    # make sure that adding invalid data or kwargs raises the right errors
    viewer = ViewerModel()
    # unrecognized layer type raises Value Error
    with pytest.raises(ValueError):
        # 'layer' is not a valid type
        # (even though there is an add_layer method)
        viewer._add_layer_from_data(np.random.random((10, 10)),
                                    layer_type='layer')

    # even with the correct meta kwargs, the underlying add_* method may raise
    with pytest.raises(ValueError):
        # improper dims for rgb data
        viewer._add_layer_from_data(np.random.random((10, 10, 6)),
                                    {'rgb': True})

    # using a kwarg in the meta dict that is invalid for the corresponding
    # add_* method raises a TypeError
    with pytest.raises(TypeError):
        viewer._add_layer_from_data(
            np.random.random((10, 2, 2)) * 20,
            {'rgb': True},  # vectors do not have an 'rgb' kwarg
            layer_type='vectors',
        )
Example #20
0
def test_scaled_and_translated_images():
    """Test scaled and translated images."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((10, 10, 10))
    viewer.add_image(data)
    viewer.add_image(data[::2], scale=[2, 1, 1], translate=[10, 0, 0])
    assert viewer.dims.range[0] == (
        0,
        19.5,
        1,
    )  # TODO: non-integer with mixed scale?
    assert viewer.dims.range[1] == (0, 10, 1)
    assert viewer.dims.range[2] == (0, 10, 1)
    assert viewer.dims.nsteps == (19, 10, 10)
    for i in range(viewer.dims.nsteps[0]):
        viewer.dims.set_current_step(0, i)
        assert viewer.dims.current_step[0] == i
Example #21
0
def test_add_remove_layer_external_callbacks(Layer, data, ndim):
    """Test external callbacks for layer emmitters preserved."""
    viewer = ViewerModel()

    layer = Layer(data)
    # Check layer has been correctly created
    assert layer.ndim == ndim

    # Connect a custom callback
    def my_custom_callback(event):
        return

    layer.events.connect(my_custom_callback)

    # Check that no internal callbacks have been registered
    len(layer.events.callbacks) == 1
    for em in layer.events.emitters.values():
        if not isinstance(em, WarningEmitter):
            assert len(em.callbacks) == 1

    viewer.layers.append(layer)
    # Check layer added correctly
    assert len(viewer.layers) == 1

    # check that adding a layer created new callbacks
    assert any(len(em.callbacks) > 0 for em in layer.events.emitters.values())

    viewer.layers.remove(layer)
    # Check layer added correctly
    assert len(viewer.layers) == 0

    # Check that all internal callbacks have been removed
    assert len(layer.events.callbacks) == 1
    for em in layer.events.emitters.values():
        if not isinstance(em, WarningEmitter):
            assert len(em.callbacks) == 1
Example #22
0
def test_add_image_colormap_variants():
    """Test adding image with all valid colormap argument types."""
    viewer = ViewerModel()
    np.random.seed(0)
    data = np.random.random((10, 15))
    # as string
    assert viewer.add_image(data, colormap='green')

    # as string that is valid, but not a default colormap
    assert viewer.add_image(data, colormap='cubehelix')

    # as tuple
    cmap_tuple = ("my_colormap", Colormap(['g', 'm', 'y']))
    assert viewer.add_image(data, colormap=cmap_tuple)

    # as dict
    cmap_dict = {"your_colormap": Colormap(['g', 'r', 'y'])}
    assert viewer.add_image(data, colormap=cmap_dict)

    # as Colormap instance
    blue_cmap = AVAILABLE_COLORMAPS['blue']
    assert viewer.add_image(data, colormap=blue_cmap)

    # string values must be known colormap types
    with pytest.raises(KeyError) as err:
        viewer.add_image(data, colormap='nonsense')

    assert 'Colormap "nonsense" not found' in str(err.value)

    # lists are only valid with channel_axis
    with pytest.raises(TypeError) as err:
        viewer.add_image(data, colormap=['green', 'red'])

    assert "did you mean to specify a 'channel_axis'" in str(err.value)
Example #23
0
def test_svg():
    "Test generating svg"
    viewer = ViewerModel()

    np.random.seed(0)
    # Add image
    data = np.random.random((10, 15))
    viewer.add_image(data)

    # Add labels
    data = np.random.randint(20, size=(10, 15))
    viewer.add_labels(data)

    # Add points
    data = 20 * np.random.random((10, 2))
    viewer.add_points(data)

    # Add vectors
    data = 20 * np.random.random((10, 2, 2))
    viewer.add_vectors(data)

    # Add shapes
    data = 20 * np.random.random((10, 4, 2))
    viewer.add_shapes(data)

    # Generate svg
    svg = viewer.to_svg()
    assert type(svg) == str
Example #24
0
def test_viewer_object_event_sources():
    viewer = ViewerModel()
    assert viewer.cursor.events.source is viewer.cursor
    assert viewer.camera.events.source is viewer.camera
Example #25
0
def test_add_empty_points_to_empty_viewer():
    viewer = ViewerModel()
    pts = viewer.add_points(name='empty points')
    assert pts.dims.ndim == 2
    pts.add([1000.0, 27.0])
    assert pts.data.shape == (1, 2)
Example #26
0
def test_screenshot(qtbot):
    "Test taking a screenshot"
    viewer = ViewerModel()
    view = QtViewer(viewer)
    qtbot.addWidget(view)

    np.random.seed(0)
    # Add image
    data = np.random.random((10, 15))
    viewer.add_image(data)

    # Add labels
    data = np.random.randint(20, size=(10, 15))
    viewer.add_labels(data)

    # Add points
    data = 20 * np.random.random((10, 2))
    viewer.add_points(data)

    # Add vectors
    data = 20 * np.random.random((10, 2, 2))
    viewer.add_vectors(data)

    # Add shapes
    data = 20 * np.random.random((10, 4, 2))
    viewer.add_shapes(data)

    # Take screenshot
    screenshot = view.screenshot()
    assert screenshot.ndim == 3
Example #27
0
def test_add_image_multichannel_share_memory():
    viewer = ViewerModel()
    image = np.random.random((10, 5, 64, 64))
    layers = viewer.add_image(image, channel_axis=1)
    for layer in layers:
        assert np.may_share_memory(image, layer.data)
Example #28
0
class ImageView(QWidget):
    position_changed = Signal([int, int, int], [int, int])
    component_clicked = Signal(int)
    text_info_change = Signal(str)
    hide_signal = Signal(bool)
    view_changed = Signal()
    image_added = Signal()

    def __init__(
        self,
        settings: BaseSettings,
        channel_property: ChannelProperty,
        name: str,
        parent: Optional[QWidget] = None,
        ndisplay=2,
    ):
        super().__init__(parent=parent)

        self.settings = settings
        self.channel_property = channel_property
        self.name = name
        self.image_info: Dict[str, ImageInfo] = {}
        self.current_image = ""
        self._current_order = "xy"
        self.components = None
        self.worker_list = []

        self.viewer = Viewer(ndisplay=ndisplay)
        self.viewer.theme = self.settings.theme_name
        self.viewer_widget = NapariQtViewer(self.viewer)
        self.image_state = ImageShowState(settings, name)
        self.channel_control = ColorComboBoxGroup(settings,
                                                  name,
                                                  channel_property,
                                                  height=30)
        self.ndim_btn = QtNDisplayButton(self.viewer)
        self.reset_view_button = QtViewerPushButton(self.viewer, "home",
                                                    "Reset view",
                                                    self._reset_view)
        self.roll_dim_button = QtViewerPushButton(self.viewer, "roll",
                                                  "Roll dimension",
                                                  self._rotate_dim)
        self.roll_dim_button.setContextMenuPolicy(Qt.CustomContextMenu)
        self.roll_dim_button.customContextMenuRequested.connect(
            self._dim_order_menu)
        self.mask_chk = QCheckBox()
        self.mask_label = QLabel("Mask:")

        self.btn_layout = QHBoxLayout()
        self.btn_layout.addWidget(self.reset_view_button)
        self.btn_layout.addWidget(self.ndim_btn)
        self.btn_layout.addWidget(self.roll_dim_button)
        self.btn_layout.addWidget(self.channel_control, 1)
        self.btn_layout.addWidget(self.mask_label)
        self.btn_layout.addWidget(self.mask_chk)
        self.btn_layout2 = QHBoxLayout()
        layout = QVBoxLayout()
        layout.addLayout(self.btn_layout)
        layout.addLayout(self.btn_layout2)
        layout.addWidget(self.viewer_widget)

        self.setLayout(layout)

        self.channel_control.change_channel.connect(self.change_visibility)
        self.viewer.events.status.connect(self.print_info)

        settings.mask_changed.connect(self.set_mask)
        settings.mask_representation_changed.connect(
            self.update_mask_parameters)
        settings.roi_changed.connect(self.set_roi)
        settings.roi_clean.connect(self.set_roi)
        settings.image_changed.connect(self.set_image)
        settings.image_spacing_changed.connect(self.update_spacing_info)
        # settings.labels_changed.connect(self.paint_layer)
        self.old_scene: BaseCamera = self.viewer_widget.view.scene

        self.image_state.coloring_changed.connect(self.update_roi_coloring)
        self.image_state.roi_presented_changed.connect(
            self.update_roi_representation)
        self.image_state.borders_changed.connect(
            self.update_roi_representation)
        self.mask_chk.stateChanged.connect(self.change_mask_visibility)
        self.viewer_widget.view.scene.transform.changed.connect(
            self._view_changed, position="last")
        try:
            self.viewer.dims.events.current_step.connect(self._view_changed,
                                                         position="last")
        except AttributeError:
            self.viewer.dims.events.axis.connect(self._view_changed,
                                                 position="last")
        self.viewer.dims.events.ndisplay.connect(self._view_changed,
                                                 position="last")
        if hasattr(self.viewer.dims.events, "ndisplay"):
            self.viewer.dims.events.ndisplay.connect(self._view_changed,
                                                     position="last")
            self.viewer.dims.events.ndisplay.connect(self.camera_change,
                                                     position="last")
        else:
            self.viewer.dims.events.camera.connect(self._view_changed,
                                                   position="last")
            self.viewer.dims.events.camera.connect(self.camera_change,
                                                   position="last")
        self.viewer.events.reset_view.connect(self._view_changed,
                                              position="last")

    def _dim_order_menu(self, point: QPoint):
        menu = QMenu()
        for key in ORDER_DICT:
            action = menu.addAction(key)
            action.triggered.connect(partial(self._set_new_order, key))
            if key == self._current_order:
                font = action.font()
                font.setBold(True)
                action.setFont(font)

        menu.exec_(self.roll_dim_button.mapToGlobal(point))

    def _set_new_order(self, text: str):
        self._current_order = text
        self.viewer.dims.order = ORDER_DICT[text]
        self.update_roi_representation()

    def _reset_view(self):
        self._set_new_order("xy")
        self.viewer.dims.order = ORDER_DICT[self._current_order]
        self.viewer.reset_view()

    def _rotate_dim(self):
        self._set_new_order(NEXT_ORDER[self._current_order])

    def camera_change(self, _args):
        self.old_scene.transform.changed.disconnect(self._view_changed)
        self.old_scene: BaseCamera = self.viewer_widget.view.camera
        self.old_scene.transform.changed.connect(self._view_changed,
                                                 position="last")

    def _view_changed(self, _args):
        self.view_changed.emit()

    def get_state(self):
        return {
            "ndisplay": self.viewer.dims.ndisplay,
            "point": self.viewer.dims.point,
            "camera": self.viewer_widget.view.camera.get_state(),
        }

    def set_state(self, dkt):
        if "ndisplay" in dkt and self.viewer.dims.ndisplay != dkt["ndisplay"]:
            self.viewer.dims.ndisplay = dkt["ndisplay"]
            return
        if "point" in dkt:
            for i, val in enumerate(dkt["point"]):
                self.viewer.dims.set_point(i, val)
        if "camera" in dkt:
            try:
                self.viewer_widget.view.camera.set_state(dkt["camera"])
            except KeyError:
                pass

    def change_mask_visibility(self):
        for image_info in self.image_info.values():
            if image_info.mask is not None:
                image_info.mask.visible = self.mask_chk.isChecked()

    def update_spacing_info(self, image: Optional[Image] = None) -> None:
        """
        Update spacing of image if not provide, then use image pointed by settings.

        :param Optional[Image] image: image which spacing should be updated.
        :return: None
        """
        if image is None:
            image = self.settings.image

        if image.file_path not in self.image_info:
            raise ValueError("Image not registered")

        image_info = self.image_info[image.file_path]

        for layer in image_info.layers:
            layer.scale = image.normalized_scaling()

        if image_info.roi is not None:
            image_info.roi.scale = image.normalized_scaling()

        if image_info.mask is not None:
            image_info.mask.scale = image.normalized_scaling()

    def print_info(self, value):
        if not self.viewer.active_layer:
            return
        cords = np.array(
            [int(x) for x in self.viewer.active_layer.coordinates])
        bright_array = []
        components = []
        for image_info in self.image_info.values():
            if not image_info.coords_in(cords):
                continue
            moved_coords = image_info.translated_coords(cords)
            for layer in image_info.layers:
                if layer.visible:
                    bright_array.append(layer.data[tuple(moved_coords)])
            if image_info.roi_info.roi is not None and image_info.roi is not None:
                val = image_info.roi_info.roi[tuple(moved_coords)]
                if val:
                    components.append(val)

        if not bright_array and not components:
            self.text_info_change.emit("")
            return
        text = f"{cords}: "
        if bright_array:
            if len(bright_array) == 1:
                text += str(bright_array[0])
            else:
                text += str(bright_array)
        self.components = components
        if components:
            if len(components) == 1:
                text += f" component: {components[0]}"
            else:
                text += f" components: {components}"
        self.text_info_change.emit(text)

    def get_control_view(self) -> ImageShowState:
        return self.image_state

    @staticmethod
    def convert_to_vispy_colormap(colormap: ColorMap):
        return Colormap(ColorArray(create_color_map(colormap) / 255))

    def mask_opacity(self) -> float:
        """Get mask opacity"""
        return self.settings.get_from_profile("mask_presentation_opacity", 1)

    def mask_color(self) -> Colormap:
        """Get mask marking color"""
        color = Color(
            np.divide(
                self.settings.get_from_profile("mask_presentation_color",
                                               [255, 255, 255]), 255))
        return Colormap(ColorArray(["black", color.rgba]))

    def get_image(self, image: Optional[Image]) -> Image:
        if image is not None:
            return image
        if self.current_image not in self.image_info:
            return self.settings.image
        return self.image_info[self.current_image].image

    def set_roi(self,
                roi_info: Optional[ROIInfo] = None,
                image: Optional[Image] = None) -> None:
        image = self.get_image(image)
        if roi_info is None:
            roi_info = self.settings.roi_info
        image_info = self.image_info[image.file_path]
        if image_info.roi is not None:
            self.viewer.layers.unselect_all()
            image_info.roi.selected = True
            self.viewer.layers.remove_selected()
            image_info.roi = None

        if roi_info.roi is None:
            return

        image_info.roi_info = roi_info
        image_info.roi_count = max(
            roi_info.bound_info) if roi_info.bound_info else 0
        self.add_roi_layer(image_info)
        image_info.roi.colormap = self.get_roi_view_parameters(image_info)
        image_info.roi.opacity = self.image_state.opacity

    def get_roi_view_parameters(self, image_info: ImageInfo) -> Colormap:
        colors = self.settings.label_colors / 255
        if self.image_state.show_label == LabelEnum.Not_show or image_info.roi_count == 0 or colors.size == 0:
            colors = np.array([[0, 0, 0, 0], [0, 0, 0, 0]])
        else:
            repeat = int(np.ceil(image_info.roi_count / colors.shape[0]))
            colors = np.concatenate([colors] * repeat)
            colors = np.concatenate(
                [colors,
                 np.ones(colors.shape[0]).reshape(colors.shape[0], 1)],
                axis=1)
            colors = np.concatenate([[[0, 0, 0, 0]],
                                     colors[:image_info.roi_count]])
            if self.image_state.show_label == LabelEnum.Show_selected:
                try:
                    colors *= self.settings.components_mask().reshape(
                        (colors.shape[0], 1))
                except ValueError:
                    pass
        control_points = [0] + list(
            np.linspace(1 / (2 * colors.shape[0]),
                        1,
                        endpoint=True,
                        num=colors.shape[0]))
        return Colormap(colors, controls=control_points, interpolation="zero")

    def update_roi_coloring(self):
        for image_info in self.image_info.values():
            if image_info.roi is None:
                continue
            image_info.roi.colormap = self.get_roi_view_parameters(image_info)
            image_info.roi.opacity = self.image_state.opacity

    def remove_all_roi(self):
        self.viewer.layers.unselect_all()
        for image_info in self.image_info.values():
            if image_info.roi is None:
                continue
            image_info.roi.selected = True
            image_info.roi = None

        self.viewer.layers.remove_selected()

    def add_roi_layer(self, image_info: ImageInfo):
        if image_info.roi_info.roi is None:
            return
        try:
            max_num = max(1, image_info.roi_count)
        except ValueError:
            max_num = 1
        roi = image_info.roi_info.alternative.get(
            self.image_state.roi_presented, image_info.roi_info.roi)
        if self.image_state.only_borders:

            data = calculate_borders(
                roi.transpose(ORDER_DICT[self._current_order]),
                self.image_state.borders_thick // 2,
                self.viewer.dims.ndisplay == 2,
            ).transpose(np.argsort(ORDER_DICT[self._current_order]))
            image_info.roi = self.viewer.add_image(
                data,
                scale=image_info.image.normalized_scaling(),
                contrast_limits=[0, max_num],
            )
        else:
            image_info.roi = self.viewer.add_image(
                roi,
                scale=image_info.image.normalized_scaling(),
                contrast_limits=[0, max_num],
                name="ROI",
                blending="translucent",
            )
        image_info.roi._interpolation[3] = Interpolation3D.NEAREST

    def update_roi_representation(self):
        self.remove_all_roi()

        for image_info in self.image_info.values():
            self.add_roi_layer(image_info)

        self.update_roi_coloring()

    def set_mask(self,
                 mask: Optional[np.ndarray] = None,
                 image: Optional[Image] = None) -> None:
        image = self.get_image(image)
        if image.file_path not in self.image_info:
            raise ValueError("Image not added to viewer")
        if mask is None:
            mask = image.mask

        image_info = self.image_info[image.file_path]
        if image_info.mask is not None:
            self.viewer.layers.unselect_all()
            image_info.mask.selected = True
            self.viewer.layers.remove_selected()
            image_info.mask = None

        if mask is None:
            return

        mask_marker = mask == 0

        layer = self.viewer.add_image(mask_marker,
                                      scale=image.normalized_scaling(),
                                      blending="additive")
        layer.colormap = self.mask_color()
        layer.opacity = self.mask_opacity()
        layer.visible = self.mask_chk.isChecked()
        image_info.mask = layer

    def update_mask_parameters(self):
        opacity = self.mask_opacity()
        colormap = self.mask_color()
        for image_info in self.image_info.values():
            if image_info.mask is not None:
                image_info.mask.opacity = opacity
                image_info.mask.colormap = colormap

    def set_image(self, image: Optional[Image] = None):
        self.image_info = {}
        self.add_image(image, True)

    def has_image(self, image: Image):
        return image.file_path in self.image_info

    @staticmethod
    def calculate_filter(
            array: np.ndarray,
            parameters: Tuple[NoiseFilterType, float]) -> Optional[np.ndarray]:
        if parameters[0] == NoiseFilterType.No or parameters[1] == 0:
            return array
        if parameters[0] == NoiseFilterType.Gauss:
            return gaussian(array, parameters[1])
        return median(array, int(parameters[1]))

    def _remove_worker(self, sender):
        for worker in self.worker_list:
            signals = "_signals" if hasattr(worker, "_signals") else "signals"
            if sender is getattr(worker, signals):
                self.worker_list.remove(worker)
                break
        else:
            print("[_remove_worker]", sender)

    def _add_layer_util(self, index, layer, filters):
        self.viewer.add_layer(layer)

        def set_data(val):
            self._remove_worker(self.sender())
            data_, layer_ = val
            if data_ is None:
                return
            if layer_ not in self.viewer.layers:
                return
            layer_.data = data_

        @thread_worker(connect={"returned": set_data})
        def calc_filter(j, layer_):
            if filters[j][0] == NoiseFilterType.No or filters[j][1] == 0:
                return None, layer_
            return self.calculate_filter(layer_.data,
                                         parameters=filters[j]), layer_

        worker = calc_filter(index, layer)
        self.worker_list.append(worker)

    def _add_image(self, image_data: Tuple[ImageInfo, bool]):
        self._remove_worker(self.sender())

        image_info, replace = image_data
        image = image_info.image
        if replace:
            self.viewer.layers.select_all()
            self.viewer.layers.remove_selected()

        filters = self.channel_control.get_filter()
        for i, layer in enumerate(image_info.layers):
            self._add_layer_util(i, layer, filters)

        self.image_info[image.file_path].filter_info = filters
        self.image_info[image.file_path].layers = image_info.layers
        self.current_image = image.file_path
        self.viewer.reset_view()
        if self.viewer.layers:
            self.viewer.layers[-1].selected = True

        for i, axis in enumerate(image.axis_order):
            if axis == "C":
                continue
            self.viewer.dims.set_point(
                i, image.shape[i] * image.normalized_scaling()[i] // 2)
        if self.image_info[image.file_path].roi is not None:
            self.set_roi()
        if image_info.image.mask is not None:
            self.set_mask()
        self.image_added.emit()

    def add_image(self, image: Optional[Image], replace=False):
        if image is None:
            image = self.settings.image

        if not image.channels:
            raise ValueError("Need non empty image")

        if image.file_path in self.image_info:
            raise ValueError("Image already added")

        self.image_info[image.file_path] = ImageInfo(image, [])

        channels = image.channels
        if self.image_info and not replace:
            channels = max(
                channels,
                *[x.image.channels for x in self.image_info.values()])

        self.channel_control.set_channels(channels)
        visibility = self.channel_control.channel_visibility
        limits = self.channel_control.get_limits()
        ranges = image.get_ranges()
        limits = [
            ranges[i] if x is None else x
            for i, x in zip(range(image.channels), limits)
        ]
        gamma = self.channel_control.get_gamma()
        colormaps = [
            self.convert_to_vispy_colormap(
                self.channel_control.selected_colormaps[i])
            for i in range(image.channels)
        ]
        parameters = ImageParameters(limits, visibility, gamma, colormaps,
                                     image.normalized_scaling(),
                                     len(self.viewer.layers))

        self._prepare_layers(image, parameters, replace)

        return image

    def _prepare_layers(self, image, parameters, replace):
        worker = prepare_layers(image, parameters, replace)
        worker.returned.connect(self._add_image)
        self.worker_list.append(worker)
        worker.start()

    def images_bounds(self) -> Tuple[List[int], List[int]]:
        ranges = []
        for image_info in self.image_info.values():
            if not image_info.layers:
                continue
            ranges = [(min(a, b), max(c, d), min(e, f)) for (a, c, e), (
                b, d,
                f) in itertools.zip_longest(image_info.layers[0].dims.range,
                                            ranges,
                                            fillvalue=(np.inf, -np.inf,
                                                       np.inf))]

        visible = [ranges[i] for i in self.viewer.dims.displayed]
        min_shape, max_shape, _ = zip(*visible)
        size = np.subtract(max_shape, min_shape)
        return size, min_shape

    @staticmethod
    def _shift_layer(layer: Layer, translate_2d):
        translate = [0] * layer.ndim
        translate[-2:] = translate_2d
        layer.translate_grid = translate

    def grid_view(self):
        """Present multiple images in grid view"""
        n_row = np.ceil(np.sqrt(len(self.image_info))).astype(int)
        n_row = max(1, n_row)
        scene_size, _ = self.images_bounds()
        for image_info, pos in zip(self.image_info.values(),
                                   itertools.product(range(n_row), repeat=2)):
            translate_2d = np.multiply(scene_size[-2:], pos)
            for layer in image_info.layers:
                self._shift_layer(layer, translate_2d)

            if image_info.mask is not None:
                self._shift_layer(image_info.mask, translate_2d)

            if image_info.roi is not None:
                self._shift_layer(image_info.roi, translate_2d)
        self.viewer.reset_view()

    def change_visibility(self, name: str, index: int):
        for image_info in self.image_info.values():
            if len(image_info.layers) > index:
                image_info.layers[
                    index].visible = self.channel_control.channel_visibility[
                        index]
                if self.channel_control.channel_visibility[index]:
                    image_info.layers[
                        index].colormap = self.convert_to_vispy_colormap(
                            self.channel_control.selected_colormaps[index])
                    limits = self.channel_control.get_limits()[index]
                    limits = image_info.image.get_ranges(
                    )[index] if limits is None else limits
                    image_info.layers[index].contrast_limits = limits
                    image_info.layers[
                        index].gamma = self.channel_control.get_gamma()[index]
                    filter_type = self.channel_control.get_filter()[index]
                    if filter_type != image_info.filter_info[index]:
                        image_info.layers[index].data = self.calculate_filter(
                            image_info.image.get_channel(index), filter_type)
                        image_info.filter_info[index] = filter_type

    def reset_image_size(self):
        self.viewer.reset_view()

    def set_theme(self, theme: str):
        self.viewer.theme = theme

    def closeEvent(self, event):
        for worker in self.worker_list:
            worker.quit()
        super().closeEvent(event)

    def get_tool_tip_text(self) -> str:
        image = self.settings.image
        image_info = self.image_info[image.file_path]
        text_list = []
        for el in self.components:
            text_list.append(
                _print_dict(image_info.roi_info.annotations.get(el, {})))
        return " ".join(text_list)

    def event(self, event: QEvent):
        if event.type() == QEvent.ToolTip and self.components:
            text = self.get_tool_tip_text()
            if text:
                QToolTip.showText(event.globalPos(), text)
        return super().event(event)
Example #29
0
def test_add_empty_points_to_empty_viewer():
    viewer = ViewerModel()
    layer = viewer.add_points(name='empty points')
    assert layer.ndim == 2
    layer.add([1000.0, 27.0])
    assert layer.data.shape == (1, 2)
Example #30
0
    def __init__(
        self,
        settings: BaseSettings,
        channel_property: ChannelProperty,
        name: str,
        parent: Optional[QWidget] = None,
        ndisplay=2,
    ):
        super().__init__(parent=parent)

        self.settings = settings
        self.channel_property = channel_property
        self.name = name
        self.image_info: Dict[str, ImageInfo] = {}
        self.current_image = ""
        self._current_order = "xy"
        self.components = None
        self.worker_list = []

        self.viewer = Viewer(ndisplay=ndisplay)
        self.viewer.theme = self.settings.theme_name
        self.viewer_widget = NapariQtViewer(self.viewer)
        self.image_state = ImageShowState(settings, name)
        self.channel_control = ColorComboBoxGroup(settings,
                                                  name,
                                                  channel_property,
                                                  height=30)
        self.ndim_btn = QtNDisplayButton(self.viewer)
        self.reset_view_button = QtViewerPushButton(self.viewer, "home",
                                                    "Reset view",
                                                    self._reset_view)
        self.roll_dim_button = QtViewerPushButton(self.viewer, "roll",
                                                  "Roll dimension",
                                                  self._rotate_dim)
        self.roll_dim_button.setContextMenuPolicy(Qt.CustomContextMenu)
        self.roll_dim_button.customContextMenuRequested.connect(
            self._dim_order_menu)
        self.mask_chk = QCheckBox()
        self.mask_label = QLabel("Mask:")

        self.btn_layout = QHBoxLayout()
        self.btn_layout.addWidget(self.reset_view_button)
        self.btn_layout.addWidget(self.ndim_btn)
        self.btn_layout.addWidget(self.roll_dim_button)
        self.btn_layout.addWidget(self.channel_control, 1)
        self.btn_layout.addWidget(self.mask_label)
        self.btn_layout.addWidget(self.mask_chk)
        self.btn_layout2 = QHBoxLayout()
        layout = QVBoxLayout()
        layout.addLayout(self.btn_layout)
        layout.addLayout(self.btn_layout2)
        layout.addWidget(self.viewer_widget)

        self.setLayout(layout)

        self.channel_control.change_channel.connect(self.change_visibility)
        self.viewer.events.status.connect(self.print_info)

        settings.mask_changed.connect(self.set_mask)
        settings.mask_representation_changed.connect(
            self.update_mask_parameters)
        settings.roi_changed.connect(self.set_roi)
        settings.roi_clean.connect(self.set_roi)
        settings.image_changed.connect(self.set_image)
        settings.image_spacing_changed.connect(self.update_spacing_info)
        # settings.labels_changed.connect(self.paint_layer)
        self.old_scene: BaseCamera = self.viewer_widget.view.scene

        self.image_state.coloring_changed.connect(self.update_roi_coloring)
        self.image_state.roi_presented_changed.connect(
            self.update_roi_representation)
        self.image_state.borders_changed.connect(
            self.update_roi_representation)
        self.mask_chk.stateChanged.connect(self.change_mask_visibility)
        self.viewer_widget.view.scene.transform.changed.connect(
            self._view_changed, position="last")
        try:
            self.viewer.dims.events.current_step.connect(self._view_changed,
                                                         position="last")
        except AttributeError:
            self.viewer.dims.events.axis.connect(self._view_changed,
                                                 position="last")
        self.viewer.dims.events.ndisplay.connect(self._view_changed,
                                                 position="last")
        if hasattr(self.viewer.dims.events, "ndisplay"):
            self.viewer.dims.events.ndisplay.connect(self._view_changed,
                                                     position="last")
            self.viewer.dims.events.ndisplay.connect(self.camera_change,
                                                     position="last")
        else:
            self.viewer.dims.events.camera.connect(self._view_changed,
                                                   position="last")
            self.viewer.dims.events.camera.connect(self.camera_change,
                                                   position="last")
        self.viewer.events.reset_view.connect(self._view_changed,
                                              position="last")