diff --git a/docs/02_interactive_usage/02_example_workflows/demo_fit_ellipsoid_interactive.md b/docs/02_interactive_usage/02_example_workflows/demo_fit_ellipsoid_interactive.md
index 3e598e1f..ca739574 100644
--- a/docs/02_interactive_usage/02_example_workflows/demo_fit_ellipsoid_interactive.md
+++ b/docs/02_interactive_usage/02_example_workflows/demo_fit_ellipsoid_interactive.md
@@ -47,7 +47,7 @@ Lastly, you can measure mean curvature on the surface of this ellipse. To do so,
![](imgs/demo_fit_ellipsoid7.png)
-You can also use the built-in [feature visualization widget](utility:visualize_features) to show, for instance, a hiostogram of the curvature on the surface.
+You can also use [napari-matplotlib](https://napari-matplotlib.github.io/index.html) [feature visualization widget](point_and_click:visualize_features) to show, for instance, a hiostogram of the curvature on the surface.
(fit_ellipsoid:normal_vectors)=
## Distances between pointcloud and approximation
diff --git a/docs/02_interactive_usage/02_example_workflows/demo_visualize_features.md b/docs/02_interactive_usage/02_example_workflows/demo_visualize_features.md
deleted file mode 100644
index 50005839..00000000
--- a/docs/02_interactive_usage/02_example_workflows/demo_visualize_features.md
+++ /dev/null
@@ -1,48 +0,0 @@
-(point_and_click:visualize_features)=
-(utility:visualize_features)=
-# Visualize measurements
-
-Napari-stress offers functionality to visualize measured data interactively in the napari viewer. This tutorial provides guidance on how to use these.
-
-* [Visualizing features](#visualize-features)
-* [Export data](#export-data)
-
-## Sample data
-
-To get started, create a pointcloud according to the workflow suggestions in this repository or load the sample data from napari-stress (`File > Open Sample > napari-stress: Droplet pointcloud`).
-
-![](imgs/open_sample_droplet.png)
-
-![](imgs/open_sample_droplet1.png)
-
-Create a spherical harmonics expansion with `Tools > Points > Fit spherical harmonics (n-STRESS)`:
-
-![](imgs/demo_visualize_features1.png)
-
-
-## Visualize features
-
-`Features` in the napari ecosystem are measurements that are assigned to a single Point (`Points` layer), label (`Labels` layer), or Surface vertex (`Surface` layer). In the context of napari-stress, such measurements include point-wise spherical harmonics expansion errors, curvature, etc. To do so for the created sample data, open the widget for this from `Tools > Utilities > Visualize pointcloud features (n-Stress)`.
-
-![](imgs/demo_visualize_features2.png)
-
-In the dropdown labelled `x axis key`, you'll see all available measurements for the selected layer - in this case, the feature to be visualized is called `error` and corresponds to the fit residue of the spherical harmonics expansion. By changing the number of bins (`n bins`) and clicking on `Update` you can change the number of bins of the histogram and apply the changes:
-
-![](imgs/demo_visualize_features3.png)
-
-You can additionally show the [cumulative distribution function ](https://en.wikipedia.org/wiki/Cumulative_distribution_function) (CDF) by clicking on the `CDF` button:
-
-![](imgs/demo_visualize_features4.png)
-
-Lastly, to explore the histogram/CDF distributions interactively, you can select parts of the histogram by drawing a rectangular selection on the plot:
-
-![](imgs/demo_visualize_features5.png)
-
-Note that the points pertaining to features in the respective range are highlighted in the viewer. You can also change the range of the selection by using the `Upper percentile` and `Lower percentile` spinboxes.
-
-
-To plot a different feature, select it from the dropdown and click `Update` to apply.
-
-## Export data
-
-Lastly, you can export the displayed (histogram) data as a `.csv` file using the `Export plot as csv` button.
\ No newline at end of file
diff --git a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_featureHistogram.png b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_featureHistogram.png
new file mode 100644
index 00000000..dabac853
Binary files /dev/null and b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_featureHistogram.png differ
diff --git a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_featureHistogram2.png b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_featureHistogram2.png
new file mode 100644
index 00000000..857d492c
Binary files /dev/null and b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_featureHistogram2.png differ
diff --git a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features1.png b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features1.png
deleted file mode 100644
index 413a16fe..00000000
Binary files a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features1.png and /dev/null differ
diff --git a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features2.png b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features2.png
deleted file mode 100644
index 8ca7e425..00000000
Binary files a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features2.png and /dev/null differ
diff --git a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features3.png b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features3.png
deleted file mode 100644
index cf93c1c5..00000000
Binary files a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features3.png and /dev/null differ
diff --git a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features4.png b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features4.png
deleted file mode 100644
index c2b9423f..00000000
Binary files a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features4.png and /dev/null differ
diff --git a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features5.png b/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features5.png
deleted file mode 100644
index 20b93c9c..00000000
Binary files a/docs/02_interactive_usage/02_example_workflows/imgs/demo_visualize_features5.png and /dev/null differ
diff --git a/docs/02_interactive_usage/02_example_workflows/visualize_measurements_in_viewer.md b/docs/02_interactive_usage/02_example_workflows/visualize_measurements_in_viewer.md
new file mode 100644
index 00000000..862ed305
--- /dev/null
+++ b/docs/02_interactive_usage/02_example_workflows/visualize_measurements_in_viewer.md
@@ -0,0 +1,12 @@
+(point_and_click:visualize_features)=
+# Visualize measurements
+
+Napari-stress (and the [measurement toolbox](toolboxes:stress_toolbox:stress_toolbox_interactive), in partciular) generate a number of measurements that can be visualized using the [napari-matplotlib](https://napari-matplotlib.github.io/) plugin. It is automatically installed with napari-stress and can be activated from the plugins menu (`Plugins > napari Matplotlib`).
+
+In order to use it, check out the documentation on the [napari-matplotlib page](https://napari-matplotlib.github.io/). In the scope of napari-stress, what you will need, is the Features Histogram (`Plugins > napari Matplotlib > FeaturesHistogram`).
+
+![](./imgs/demo_visualize_featureHistogram.png)
+
+To display measurements, select a layer of interest and the dropdown in the newly created Histogram widget will show all possible features for you to highlight.
+
+![](./imgs/demo_visualize_featureHistogram2.png)
\ No newline at end of file
diff --git a/docs/_toc.yml b/docs/_toc.yml
index 38903665..7231e634 100644
--- a/docs/_toc.yml
+++ b/docs/_toc.yml
@@ -27,6 +27,7 @@ parts:
- file: 01_code_usage/02_example_workflows/demo_spherical_harmonics_code
- file: 01_code_usage/02_example_workflows/demo_timelapse_processing
- file: 01_code_usage/02_example_workflows/demo_measure_intensity_on_surface
+ - file: 01_code_usage/02_example_workflows/demo_measure_intensity_on_vectors
- file: 02_interactive_usage/interactive_usage
sections:
@@ -41,7 +42,7 @@ parts:
- file: 02_interactive_usage/02_example_workflows/demo_surface_reconstruction_interactive
- file: 02_interactive_usage/02_example_workflows/demo_spherical_harmonics_interactive
- file: 02_interactive_usage/02_example_workflows/demo_measure_curvature
- - file: 02_interactive_usage/02_example_workflows/demo_visualize_features
+ - file: 02_interactive_usage/02_example_workflows/visualize_measurements_in_viewer
- caption: Glossary
chapters:
diff --git a/setup.cfg b/setup.cfg
index 50917df0..ff608532 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -34,10 +34,9 @@ install_requires =
dask
distributed
joblib
- matplotlib
mpmath
napari
- napari-matplotlib==0.2.0
+ napari-matplotlib>=1.1.0
napari-process-points-and-surfaces>=0.4.0
napari-segment-blobs-and-things-with-membranes
napari-tools-menu>=0.1.15
diff --git a/src/napari_stress/__init__.py b/src/napari_stress/__init__.py
index 7fcf1403..949ffe32 100644
--- a/src/napari_stress/__init__.py
+++ b/src/napari_stress/__init__.py
@@ -24,7 +24,5 @@
from ._sample_data.sample_data import get_droplet_point_cloud, get_droplet_point_cloud_4d, get_droplet_4d
-from ._plotting.features_histogram import FeaturesHistogramWidget
-
from . import types
from . import _vectors as vectors
diff --git a/src/napari_stress/_plotting/features_histogram.py b/src/napari_stress/_plotting/features_histogram.py
deleted file mode 100644
index 78b274cc..00000000
--- a/src/napari_stress/_plotting/features_histogram.py
+++ /dev/null
@@ -1,336 +0,0 @@
-import napari
-from magicgui import magicgui
-import numpy as np
-import pandas as pd
-from napari_matplotlib import HistogramWidget
-from napari_matplotlib.util import Interval
-from matplotlib.widgets import RectangleSelector
-from matplotlib.patches import Rectangle
-from magicgui.widgets import ComboBox
-from typing import List, Optional, Tuple
-from qtpy.QtWidgets import (QFileDialog, QHBoxLayout,
- QPushButton, QDoubleSpinBox,
- QSpacerItem, QSizePolicy, QGridLayout,
- QLabel)
-from scipy import stats
-from napari_tools_menu import register_dock_widget
-
-@register_dock_widget(menu="Visualization > Visualize pointcloud features (n-Stress)")
-class FeaturesHistogramWidget(HistogramWidget):
- """Plot widget to display histogram of selected layer features."""
-
- n_layers_input = Interval(1, 1)
- # All layers that have a .features attributes
- input_layer_types = (
- napari.layers.Labels,
- napari.layers.Points,
- napari.layers.Shapes,
- napari.layers.Tracks,
- napari.layers.Vectors,
- )
-
- def __init__(self, napari_viewer: napari.viewer.Viewer):
- super().__init__(napari_viewer)
- self._key_selection_widget = magicgui(
- self._set_axis_keys,
- x_axis_key={"choices": self._get_valid_axis_keys},
- n_bins={"value": 50, "widget_type": "SpinBox"},
- call_button="Update",
- )
- self._export_button = magicgui(
- self.export,
- call_button='Export plot as csv'
- )
-
- # controllers for rectangle selector
- self.left_edit = QDoubleSpinBox()
- self.left_edit.setMaximum(1)
- self.left_edit.setMinimum(0)
- self.left_edit.setDecimals(2)
- self.left_edit.setSingleStep(0.01)
- self.right_edit = QDoubleSpinBox()
- self.right_edit.setMaximum(1)
- self.right_edit.setMinimum(0)
- self.right_edit.setDecimals(2)
- self.right_edit.setSingleStep(0.01)
- container_edits = QGridLayout()
- container_edits.addItem(QSpacerItem(100, 0, QSizePolicy.Expanding), 0, 0)
- container_edits.addItem(QSpacerItem(100, 0, QSizePolicy.Expanding), 1, 0)
- container_edits.addWidget(QLabel('Upper percentile'), 0, 1)
- container_edits.addWidget(QLabel('Lower percentile'), 1, 1)
- container_edits.addWidget(self.left_edit, 1, 2)
- container_edits.addWidget(self.right_edit, 0, 2)
- self.layout().addLayout(container_edits)
-
- self.left_edit.valueChanged.connect(self._on_percentile_select)
- self.right_edit.valueChanged.connect(self._on_percentile_select)
-
- # buttons to switch between histogram/CDF
- self.enable_histogram = QPushButton('Histogram')
- self.enable_histogram.setCheckable(True)
- self.enable_histogram.setChecked(True)
- self.enable_cdf = QPushButton('CDF')
- self.enable_cdf.setCheckable(True)
- self.enable_histogram.clicked.connect(self._draw)
- self.enable_cdf.clicked.connect(self._draw)
-
- container = QHBoxLayout()
- container.addWidget(self.enable_histogram)
- container.addWidget(self.enable_cdf)
- self.layout().addLayout(container)
-
- self.layout().addWidget(self._key_selection_widget.native)
- self.layout().addWidget(self._export_button.native)
-
- self.viewer = napari_viewer
-
- # create a second y-axis in the plot
- self.axes2 = self.axes.twinx() # for cdf
- self.axes3 = self.axes.twinx() # for selector rectangle
- self.axes3.get_xaxis().set_visible(False)
- self.axes3.get_yaxis().set_visible(False)
-
- # hook up rectangle selector
- self.rectangle_selector = RectangleSelector(
- self.axes3,
- self._on_area_select,
- useblit=True,
- props=dict(edgecolor="white", fill=False),
- minspanx=5,
- minspany=5,
- spancoords="pixels",
- interactive=False,
- )
- self.highlight_rectangle = None
- self.highlight_layer = None
- self.cdf_histogram = None
-
- @property
- def x_axis_key(self) -> Optional[str]:
- """Key to access x axis data from the FeaturesTable."""
- return self._x_axis_key
-
- @x_axis_key.setter
- def x_axis_key(self, key: Optional[str]) -> None:
- self._x_axis_key = key
- self._draw()
-
- @property
- def n_bins(self) -> Optional[str]:
- """Key to access y axis data from the FeaturesTable."""
- return self._n_bins
-
- # @n_bins.setter
- # def n_bins(self, key: Optional[str]) -> None:
- # # self._y_axis_key = key
- # self._draw()
-
- def _set_axis_keys(self, x_axis_key: str, n_bins: int) -> None:
- """Set both axis keys and then redraw the plot."""
- self._x_axis_key = x_axis_key
- self._n_bins = n_bins
- self._draw()
-
- self.viewer.layers[self.layers[0].name].refresh_colors(True)
- self.viewer.layers[self.layers[0].name].face_color = x_axis_key
-
- def _get_valid_axis_keys(
- self, combo_widget: Optional[ComboBox] = None
- ) -> List[str]:
- """
- Get the valid axis keys from the layer FeatureTable.
-
- Returns
- -------
- axis_keys : List[str]
- The valid axis keys in the FeatureTable. If the table is empty
- or there isn't a table, returns an empty list.
- """
- if len(self.layers) == 0 or not (hasattr(self.layers[0], "features")):
- return []
- else:
- return self.layers[0].features.keys()
-
- def _get_data(self) -> Tuple[List[np.ndarray], str, int]:
- """Get the plot data.
-
- Returns
- -------
- data : List[np.ndarray]
- List contains X and Y columns from the FeatureTable. Returns
- an empty array if nothing to plot.
- x_axis_name : str
- The title to display on the x axis. Returns
- an empty string if nothing to plot.
- y_axis_name: int
- The title to display on the y axis. Returns
- an empty string if nothing to plot.
- """
- if not hasattr(self.layers[0], "features"):
- # if the selected layer doesn't have a featuretable,
- # skip draw
- return [], "", ""
-
- feature_table = self.layers[0].features
-
- if (
- (len(feature_table) == 0)
- or (self.x_axis_key is None)
- ):
- return [], "", 0
-
- data_x = feature_table[self.x_axis_key]
- bins = np.linspace(np.min(data_x), np.max(data_x), self.n_bins+1)
- # data_y = feature_table[self.y_axis_key]
- data = [data_x, bins]
-
- x_axis_name = self.x_axis_key.replace("_", " ")
- y_axis_name = 'Occurrences [#]'
-
- return data, x_axis_name, y_axis_name
-
- def _on_update_layers(self) -> None:
- """This is called when the layer selection changes by
- ``self.update_layers()``.
-
- """
- if hasattr(self, "_key_selection_widget"):
- self._key_selection_widget.reset_choices()
-
- # reset the axis keys
- self._x_axis_key = None
- self._n_bins = None
-
- def _on_area_select(self, eclick, erelease):
- """Triggered when user clicks within axes"""
- # get click event coordinates
- x1, _ = eclick.xdata, eclick.ydata
- x2, _ = erelease.xdata, erelease.ydata
-
- y1 = 0
- y2 = self.axes.get_ylim()[1]
- self._draw_highlight_rectangle(x1, x2, y1, y2)
-
- if self.cdf_histogram is not None:
- percentile_left = self.cdf_histogram.cdf(min(x1, x2))
- percentile_right = self.cdf_histogram.cdf(max(x1, x2))
- self.left_edit.setValue(percentile_left)
- self.right_edit.setValue(percentile_right)
-
- def _on_percentile_select(self):
- """If values in percentile comboboxes are changed."""
- percentile_left = self.left_edit.value()
- percentile_right = self.right_edit.value()
-
- if percentile_left > percentile_right:
- percentile_left = percentile_right
- self.left_edit.setValue(percentile_left)
-
- left = self.cdf_histogram.ppf(percentile_left)
- right = self.cdf_histogram.ppf(percentile_right)
- self._draw_highlight_rectangle(left, right, 0, self.axes.get_ylim()[1])
-
-
-
- def _draw_highlight_rectangle(self, x1, x2, y1=0, y2=1):
- """
- Draw the rectangle the highlights points in the viewer.
-
- Parameters
- ----------
- x1 : float
- left border
- x2 : float
- right border
- y1 : float
- lower border
- y2 : float
- upper border
-
- Returns
- -------
- None.
-
- """
- # put highlight rectangle in histogram plot
- if self.highlight_rectangle is None:
- self.highlight_rectangle = Rectangle(
- (min(x1,x2), 0), np.abs(x1-x2), np.abs(y1-y2),
- alpha=0.35, facecolor='white')
- self.axes3.add_patch(self.highlight_rectangle)
-
- else:
- self.highlight_rectangle.set_x(x1)
- self.highlight_rectangle.set_width(np.abs(x1-x2))
-
- self.canvas.draw()
-
- # Get points that correspond to selected points in plot
- left = min(x1, x2)
- right = max(x1, x2)
- shown_range = self.layers[0].features[self.x_axis_key]
- shown_range = np.asarray((shown_range > left) * (shown_range < right))
- self.viewer.layers[self.layers[0].name].refresh_colors(True)
-
- # highlight in viewer
- colors_highlight = np.ones((self.layers[0].data.shape[0], 4))
- colors_highlight[np.argwhere(shown_range == False), :3] = 0
- self.viewer.layers[self.layers[0].name].edge_color = colors_highlight
-
- def draw(self) -> None:
- """Clear the axes and histogram the currently selected layer/slice."""
- data, x_axis_name, y_axis_name = self._get_data()
-
- if len(data) == 0:
- # don't plot if there isn't data
- return
-
- self.N, bins = np.histogram(data[0], data[1])
-
- colormapping = self.layers[0].face_colormap
- self.bins_norm = (bins - bins.min())/(bins.max() - bins.min())
- colors = colormapping.map(self.bins_norm)
-
- if self.enable_histogram.isChecked():
- self.N, bins, patches = self.axes.hist(data[0],
- bins=data[1],
- edgecolor='white',
- linewidth=0.3,
- label=self.layers[0].name)
- # Set histogram style:
- for idx, patch in enumerate(patches):
- patch.set_facecolor(colors[idx])
- else:
- self.axes.clear()
-
- if self.enable_cdf.isChecked():
- self.cdf_histogram = stats.rv_histogram((self.N, bins))
- self.axes2.step(bins, self.cdf_histogram.cdf(data[1]), color='black',
- where='pre', zorder=1)
- else:
- self.axes2.clear()
-
- # set ax labels
- self.axes.set_xlabel(x_axis_name)
- self.axes.set_ylabel(y_axis_name)
- self.axes2.set_ylabel('Cumulative density')
-
- # make sure that rectangle axes has correct x-range
- self.axes3.set_xlim(bins[0], bins[-1])
-
- self.canvas.draw()
-
- def export(self) -> None:
- """Export plotted data as csv."""
- if not self.axes.has_data():
- print('No data plotted')
- return
- # Not including last bin because len(bins) = len(N) + 1
- # https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.hist.html
- df_to_save = pd.DataFrame({self.axes.get_xlabel(): self.bins_norm[:-1],
- self.axes.get_ylabel(): self.N})
- fname = QFileDialog.getSaveFileName(self, 'Save plotted data',
- 'c:\\',
- "Csv files (*.csv)")
- df_to_save.to_csv(fname[0])
- return
diff --git a/src/napari_stress/_tests/test_plotting.py b/src/napari_stress/_tests/test_plotting.py
deleted file mode 100644
index a15d1fa1..00000000
--- a/src/napari_stress/_tests/test_plotting.py
+++ /dev/null
@@ -1,33 +0,0 @@
-def test_plotting(make_napari_viewer):
- from napari_stress import FeaturesHistogramWidget
- from napari_stress import get_droplet_point_cloud
- from napari_stress._spherical_harmonics.spherical_harmonics_napari \
- import fit_spherical_harmonics
-
- # Load sample data
- data = get_droplet_point_cloud()[0][0]
-
- # Calculate spherical harmonics
- layer_data_tuple = fit_spherical_harmonics(data)
-
- viewer = make_napari_viewer()
- # Add sample points to viewer
- viewer.add_points(layer_data_tuple[0], **layer_data_tuple[1])
- # Add plot widget to viewer
- plot_widget = FeaturesHistogramWidget(viewer)
- viewer.window.add_dock_widget(plot_widget)
- # Run plot widget
- plot_widget._key_selection_widget()
- # Check if plot has data
- assert plot_widget.axes.has_data()
-
- plot_widget.enable_cdf.setChecked(True)
- plot_widget._key_selection_widget()
-
- # check the data highlighting
- plot_widget._draw_highlight_rectangle(0,1,0,1)
-
-
-if __name__ == '__main__':
- import napari
- test_plotting(napari.Viewer)
diff --git a/src/napari_stress/napari.yaml b/src/napari_stress/napari.yaml
index 58e078c3..7203bdec 100644
--- a/src/napari_stress/napari.yaml
+++ b/src/napari_stress/napari.yaml
@@ -92,11 +92,6 @@ contributions:
python_name: napari_stress._vectors:absolute_move_points_along_vector
title: Move points along vector (absolute)
- # Plotting
- - id: napari-stress.features_histogram
- python_name: napari_stress:FeaturesHistogramWidget
- title: Visualize features (histogram)
-
- id: napari-stress.fit_lsq_ellipsoid
python_name: napari_stress.approximation:least_squares_ellipsoid
title: Fit least squares ellipsoid
@@ -182,10 +177,6 @@ contributions:
autogenerate: true
display_name: Pairwise point distance
- # Plotting
- - command: napari-stress.features_histogram
- display_name: Visualize features (histogram)
-
# Vectors
- command: napari-stress.normal_vectors_on_pointcloud
autogenerate: true