Repository: BiAPoL/napari-clusters-plotter (language: python)

Issue:
Test current version before releasing
I'm not sure if all use cases were properly tested. It would be cool if someone volunteers for testing them before #281 , as the tool is downloaded and used by quite some folks.

* [x] 2D labels
* [x] 3D labels
* [x] 2D + time
* [x] 3D + time
* [x] points
* [x] surface
* [x] points + time ?  (bf09d794596de073a993f5e8c942782bb1a42ff3)
* [x] surface + time ?  (9cf67049d4222163e2f9c08f4c46778eddb52729)

For the last tests, example data might be necessary (see also #301 )

Thanks!


Discussion / hints:
I had made some progress on this issue but one Issue is that the [napari-skimage-regionprops](https://github.com/haesleinhuepf/napari-skimage-regionprops) function `show_table` is incompatible with surface layers currently. I have made a suggestion to fix it as a [PR](https://github.com/haesleinhuepf/napari-skimage-regionprops/pull/85) but until this gets merged and released we will need to either keep the newest cluster plotter release on hold or find a workaround for showing the table..

Fix patch:
diff --git a/napari_clusters_plotter/__init__.py b/napari_clusters_plotter/__init__.py
index 7ad46881..26a3977e 100644
--- a/napari_clusters_plotter/__init__.py
+++ b/napari_clusters_plotter/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "0.7.3"
+__version__ = "0.8.0"
 
 
 from ._dock_widget import (
diff --git a/setup.cfg b/setup.cfg
index b09132bc..18085b90 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = napari-clusters-plotter
-version = 0.7.3
+version = 0.8.0
 author = Laura Zigutyte, Ryan Savill, Johannes Müller, Marcelo Zoccoler, Thorsten Wagner, Robert Haase
 author_email = zigutyte@gmail.com, robert.haase@tu-dresden.de
 


Test patch:
diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 1b14df0a..f97db0f3 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -22,14 +22,14 @@ jobs:
     runs-on: ${{ matrix.platform }}
     strategy:
       matrix:
-        platform: [ubuntu-latest, macos-latest] # windows-latest (disabled, see related issue)
+        platform: [ubuntu-latest, windows-latest] # macos-latest (disabled, see related issue)
         python-version: [3.8, 3.9]
 
     steps:
       - uses: actions/checkout@v3
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
diff --git a/napari_clusters_plotter/_tests/test_plotter.py b/napari_clusters_plotter/_tests/test_plotter.py
index b7004f79..9c12edb4 100644
--- a/napari_clusters_plotter/_tests/test_plotter.py
+++ b/napari_clusters_plotter/_tests/test_plotter.py
@@ -36,7 +36,7 @@ def get_labels_array():
     return label
 
 
-def test_plotting(make_napari_viewer):
+def test_plotter_on_labels2d(make_napari_viewer):
     viewer = make_napari_viewer()
     widget_list = ncp.napari_experimental_provide_dock_widget()
 
@@ -79,6 +79,280 @@ def test_plotting(make_napari_viewer):
     print("check")
 
 
+def test_plotter_on_labels3d(make_napari_viewer):
+    from napari_clusters_plotter._plotter import PlotterWidget
+    from napari_clusters_plotter._utilities import get_layer_tabular_data
+
+    viewer = make_napari_viewer()
+    # Generate 3D data
+    z_slices = 3
+    n_labels = get_labels_array().max()
+    data_3d = np.stack([get_labels_array() for _ in range(z_slices)])
+    # Create some random features
+    label_column = np.arange(1, n_labels + 1)
+    feature1 = np.random.normal(size=n_labels)
+    feature2 = np.random.normal(size=n_labels, loc=1)
+    annotations = 1 * (np.random.uniform(size=n_labels) > 0.5)
+
+    viewer.add_labels(
+        data_3d,
+        properties={
+            "label": label_column,
+            "feature1": feature1,
+            "feature2": feature2,
+            "annotations": annotations,
+        },
+    )
+
+    widget = PlotterWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+
+    # Put the labels layer into the layer selection widget
+    widget.analysed_layer = viewer.layers[0]
+    widget.plot_x_axis.setCurrentText("feature1")
+    widget.plot_y_axis.setCurrentText("feature2")
+    widget.analysed_layer.features["MANUAL_CLUSTER_ID"] = annotations
+
+    # check that the features are found
+    features = get_layer_tabular_data(widget.analysed_layer)
+    assert "feature1" in features.columns
+    assert "feature2" in features.columns
+
+    widget.run(
+        widget.analysed_layer.features,
+        plot_x_axis_name="feature1",
+        plot_y_axis_name="feature2",
+        plot_cluster_name="MANUAL_CLUSTER_ID",
+        force_redraw=True,
+    )
+
+
+def test_plotter_on_labels2d_timelapse(make_napari_viewer):
+    from napari_clusters_plotter._plotter import PlotterWidget
+    from napari_clusters_plotter._utilities import get_layer_tabular_data
+
+    viewer = make_napari_viewer()
+    # Generate 2D timelapse data
+    n_timepoints = 2
+    n_labels = get_labels_array().max()
+    data_2d_timelapse = np.array(
+        [np.roll(get_labels_array(), t, axis=0) for t in range(n_timepoints)]
+    )
+    data_2d_timelapse = np.expand_dims(
+        data_2d_timelapse, axis=1
+    )  # add unidimensional z axis
+    assert len(data_2d_timelapse.shape) == 4
+    # Create some random features
+    label_column = np.tile(np.arange(1, n_labels + 1), n_timepoints)
+    feature1 = np.random.normal(size=n_labels * n_timepoints)
+    feature2 = np.random.normal(size=n_labels * n_timepoints, loc=1)
+    annotations = 1 * (np.random.uniform(size=n_labels * n_timepoints) > 0.5)
+    frame = np.arange(0, n_timepoints).repeat(
+        n_labels
+    )  # add frame column for timelapsed data
+
+    viewer.add_labels(
+        data_2d_timelapse,
+        properties={
+            "label": label_column,
+            "feature1": feature1,
+            "feature2": feature2,
+            "annotations": annotations,
+            "frame": frame,
+        },
+    )
+
+    widget = PlotterWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+
+    # Put the labels layer into the layer selection widget
+    widget.analysed_layer = viewer.layers[0]
+    widget.plot_x_axis.setCurrentText("feature1")
+    widget.plot_y_axis.setCurrentText("feature2")
+    widget.analysed_layer.features["MANUAL_CLUSTER_ID"] = annotations
+
+    # check that the features are found
+    features = get_layer_tabular_data(widget.analysed_layer)
+    assert "feature1" in features.columns
+    assert "feature2" in features.columns
+
+    widget.run(
+        widget.analysed_layer.features,
+        plot_x_axis_name="feature1",
+        plot_y_axis_name="feature2",
+        plot_cluster_name="MANUAL_CLUSTER_ID",
+        force_redraw=True,
+    )
+
+
+def test_plotter_on_labels3d_timelapse(make_napari_viewer):
+    from napari_clusters_plotter._plotter import PlotterWidget
+    from napari_clusters_plotter._utilities import get_layer_tabular_data
+
+    viewer = make_napari_viewer()
+    # Generate 3D timelapse data
+    z_slices = 3
+    n_timepoints = 2
+    n_labels = get_labels_array().max()
+    data_3d_timelapse = np.array(
+        [
+            np.roll(np.stack([get_labels_array() for _ in range(z_slices)]), t, axis=1)
+            for t in range(n_timepoints)
+        ]
+    )
+    assert len(data_3d_timelapse.shape) == 4
+    # Create some random features
+    label_column = np.tile(np.arange(1, n_labels + 1), n_timepoints)
+    feature1 = np.random.normal(size=n_labels * n_timepoints)
+    feature2 = np.random.normal(size=n_labels * n_timepoints, loc=1)
+    annotations = 1 * (np.random.uniform(size=n_labels * n_timepoints) > 0.5)
+    frame = np.arange(0, n_timepoints).repeat(
+        n_labels
+    )  # add frame column for timelapsed data
+
+    viewer.add_labels(
+        data_3d_timelapse,
+        properties={
+            "label": label_column,
+            "feature1": feature1,
+            "feature2": feature2,
+            "annotations": annotations,
+            "frame": frame,
+        },
+    )
+
+    widget = PlotterWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+
+    # Put the labels layer into the layer selection widget
+    widget.analysed_layer = viewer.layers[0]
+    widget.plot_x_axis.setCurrentText("feature1")
+    widget.plot_y_axis.setCurrentText("feature2")
+    widget.analysed_layer.features["MANUAL_CLUSTER_ID"] = annotations
+
+    # check that the features are found
+    features = get_layer_tabular_data(widget.analysed_layer)
+    assert "feature1" in features.columns
+    assert "feature2" in features.columns
+
+    widget.run(
+        widget.analysed_layer.features,
+        plot_x_axis_name="feature1",
+        plot_y_axis_name="feature2",
+        plot_cluster_name="MANUAL_CLUSTER_ID",
+        force_redraw=True,
+    )
+
+
+def test_plotter_on_labels2d_tracking(make_napari_viewer):
+    from napari_clusters_plotter._plotter import PlotterWidget
+    from napari_clusters_plotter._utilities import get_layer_tabular_data
+
+    viewer = make_napari_viewer()
+    # Generate 2D tracking data
+    n_timepoints = 2
+    n_labels = get_labels_array().max()
+    data_2d_tracking = np.array(
+        [np.roll(get_labels_array(), t, axis=0) for t in range(n_timepoints)]
+    )
+    data_2d_tracking = np.expand_dims(
+        data_2d_tracking, axis=1
+    )  # add unidimensional z axis
+    # Create some random features
+    label_column = np.arange(1, n_labels + 1)
+    feature1 = np.random.normal(size=n_labels)
+    feature2 = np.random.normal(size=n_labels, loc=1)
+    annotations = 1 * (np.random.uniform(size=n_labels) > 0.5)
+    # Note how there is no frame column for tracking data, because we are analyzing features for the whole tracks
+
+    viewer.add_labels(
+        data_2d_tracking,
+        properties={
+            "label": label_column,
+            "feature1": feature1,
+            "feature2": feature2,
+            "annotations": annotations,
+        },
+    )
+
+    widget = PlotterWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+
+    # Put the labels layer into the layer selection widget
+    widget.analysed_layer = viewer.layers[0]
+    widget.plot_x_axis.setCurrentText("feature1")
+    widget.plot_y_axis.setCurrentText("feature2")
+    widget.analysed_layer.features["MANUAL_CLUSTER_ID"] = annotations
+
+    # check that the features are found
+    features = get_layer_tabular_data(widget.analysed_layer)
+    assert "feature1" in features.columns
+    assert "feature2" in features.columns
+
+    widget.run(
+        widget.analysed_layer.features,
+        plot_x_axis_name="feature1",
+        plot_y_axis_name="feature2",
+        plot_cluster_name="MANUAL_CLUSTER_ID",
+        force_redraw=True,
+    )
+
+
+def test_plotter_on_labels3d_tracking(make_napari_viewer):
+    from napari_clusters_plotter._plotter import PlotterWidget
+    from napari_clusters_plotter._utilities import get_layer_tabular_data
+
+    viewer = make_napari_viewer()
+    # Generate 3D tracking data
+    z_slices = 3
+    n_timepoints = 2
+    n_labels = get_labels_array().max()
+    data_3d_tracking = np.array(
+        [
+            np.roll(np.stack([get_labels_array() for _ in range(z_slices)]), t, axis=1)
+            for t in range(n_timepoints)
+        ]
+    )
+    # Create some random features
+    label_column = np.arange(1, n_labels + 1)
+    feature1 = np.random.normal(size=n_labels)
+    feature2 = np.random.normal(size=n_labels, loc=1)
+    annotations = 1 * (np.random.uniform(size=n_labels) > 0.5)
+    # Note how there is no frame column for tracking data, because we are analyzing features for the whole tracks
+
+    viewer.add_labels(
+        data_3d_tracking,
+        properties={
+            "label": label_column,
+            "feature1": feature1,
+            "feature2": feature2,
+            "annotations": annotations,
+        },
+    )
+
+    widget = PlotterWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+
+    # Put the labels layer into the layer selection widget
+    widget.analysed_layer = viewer.layers[0]
+    widget.plot_x_axis.setCurrentText("feature1")
+    widget.plot_y_axis.setCurrentText("feature2")
+    widget.analysed_layer.features["MANUAL_CLUSTER_ID"] = annotations
+
+    # check that the features are found
+    features = get_layer_tabular_data(widget.analysed_layer)
+    assert "feature1" in features.columns
+    assert "feature2" in features.columns
+
+    widget.run(
+        widget.analysed_layer.features,
+        plot_x_axis_name="feature1",
+        plot_y_axis_name="feature2",
+        plot_cluster_name="MANUAL_CLUSTER_ID",
+        force_redraw=True,
+    )
+
+
 def test_plotter_utilities():
     frame_ids = [0, 0, 1, 1, 2, 2, 3, 3]
     predicts = [0, 1, -1, 1, 0, 1, 1, 0]
@@ -232,6 +506,49 @@ def test_plotter_on_points_data(make_napari_viewer):
     )
 
 
+def test_plotter_on_points_data4d(make_napari_viewer):
+    from napari_clusters_plotter._plotter import PlotterWidget
+    from napari_clusters_plotter._utilities import get_layer_tabular_data
+
+    viewer = make_napari_viewer()
+    n_points = 100
+    n_timepoints = 2
+    points = np.random.rand(n_points, 4)
+    points[:, 0] = np.arange(0, n_timepoints).repeat(n_points // n_timepoints)
+
+    feature1 = np.random.normal(size=n_points)
+    feature2 = np.random.normal(size=n_points, loc=1)
+    annotations = 1 * (np.random.uniform(size=100) > 0.5)
+
+    viewer.add_points(
+        points,
+        properties={"feature1": feature1, "feature2": feature2, "frame": points[:, 0]},
+        size=0.1,
+    )
+
+    widget = PlotterWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+
+    # Put the points layer into the layer selection widget
+    widget.analysed_layer = viewer.layers[0]
+    widget.plot_x_axis.setCurrentText("feature1")
+    widget.plot_y_axis.setCurrentText("feature2")
+    widget.analysed_layer.features["MANUAL_CLUSTER_ID"] = annotations
+
+    # check that the features are found
+    features = get_layer_tabular_data(widget.analysed_layer)
+    assert "feature1" in features.columns
+    assert "feature2" in features.columns
+
+    widget.run(
+        widget.analysed_layer.features,
+        plot_x_axis_name="feature1",
+        plot_y_axis_name="feature2",
+        plot_cluster_name="MANUAL_CLUSTER_ID",
+        force_redraw=True,
+    )
+
+
 def test_plotter_on_surface_data(make_napari_viewer):
     from napari_clusters_plotter._plotter import PlotterWidget
     from napari_clusters_plotter._utilities import get_layer_tabular_data
@@ -272,6 +589,61 @@ def test_plotter_on_surface_data(make_napari_viewer):
     )
 
 
+def test_plotter_on_surface_data4d(make_napari_viewer):
+
+    from napari_clusters_plotter._plotter import PlotterWidget
+    from napari_clusters_plotter._utilities import get_layer_tabular_data
+
+    viewer = make_napari_viewer()
+    n_points = 100
+    n_timepoints = 2
+    vertices = np.random.rand(n_points, 4)
+    vertices[:, 0] = np.arange(0, n_timepoints).repeat(n_points // n_timepoints)
+
+    # Create a random mesh
+    faces_t0 = np.random.randint(0, 49, (100, 3))
+    faces_t1 = np.random.randint(50, 99, (100, 3))
+    faces = np.concatenate((faces_t0, faces_t1))
+
+    feature1 = np.random.normal(size=n_points)
+    feature2 = np.random.normal(size=n_points, loc=1)
+    annotations = 1 * (np.random.uniform(size=n_points) > 0.5)
+    surface_tuple = (vertices, faces)
+
+    # need to add features later-on as napari currently doesn't support features for surfaces
+    layer = viewer.add_surface(surface_tuple, name="random_mesh")
+    layer.features = pd.DataFrame(
+        {
+            "feature1": feature1,
+            "feature2": feature2,
+            "frame": vertices[:, 0],
+            "annotations": annotations,
+        }
+    )
+
+    widget = PlotterWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+
+    # Put the points layer into the layer selection widget
+    widget.analysed_layer = viewer.layers[0]
+    widget.plot_x_axis.setCurrentText("feature1")
+    widget.plot_y_axis.setCurrentText("feature2")
+    widget.analysed_layer.features["MANUAL_CLUSTER_ID"] = annotations
+
+    # check that the features are found
+    features = get_layer_tabular_data(widget.analysed_layer)
+    assert "feature1" in features.columns
+    assert "feature2" in features.columns
+
+    widget.run(
+        widget.analysed_layer.features,
+        plot_x_axis_name="feature1",
+        plot_y_axis_name="feature2",
+        plot_cluster_name="MANUAL_CLUSTER_ID",
+        force_redraw=True,
+    )
+
+
 def test_cluster_image_generation_for_histogram(make_napari_viewer):
     from napari_clusters_plotter._plotter import PlotterWidget