diff --git a/src/napari_psf_extractor/extractor.py b/src/napari_psf_extractor/extractor.py index 0677245..0d91d1e 100644 --- a/src/napari_psf_extractor/extractor.py +++ b/src/napari_psf_extractor/extractor.py @@ -3,8 +3,8 @@ import psf_extractor as psfe import trackpy -from napari_psf_extractor.utils import crop_to_bbox from napari_psf_extractor.plotting import plot_mass_range +from napari_psf_extractor.utils import crop_to_bbox def get_features_plot_data(plot_widget, mip, dx, dy, mass): @@ -37,7 +37,7 @@ def get_features_plot_data(plot_widget, mip, dx, dy, mass): # Plot features features_init = trackpy.locate(mip, diameter=[dy, dx]).reset_index(drop=True) - plot_mass_range(mip=mip, ax=ax, mass=mass, features=features_init) + feature_count = plot_mass_range(mip=mip, ax=ax, mass=mass, features=features_init) plot_widget.canvas.draw() # Fetch plot matplotlib data from buffer @@ -57,7 +57,7 @@ def get_features_plot_data(plot_widget, mip, dx, dy, mass): data = cv2.resize(data, dsize=(mip.shape[1], mip.shape[0]), interpolation=cv2.INTER_NEAREST) - return data, features_init + return data, features_init, feature_count def extract_psf(min_mass, max_mass, stack, features, wx, wy, wz): diff --git a/src/napari_psf_extractor/features.py b/src/napari_psf_extractor/features.py index ce78e3e..dc467dd 100644 --- a/src/napari_psf_extractor/features.py +++ b/src/napari_psf_extractor/features.py @@ -5,6 +5,7 @@ from napari._qt.qthreading import thread_worker from napari.utils.notifications import show_error from psf_extractor.plotting import fire +from qtpy.QtWidgets import QLabel from napari_psf_extractor.extractor import get_features_plot_data @@ -16,8 +17,10 @@ def __init__(self, widget): self.lock = threading.Lock() self.data = None + self.count = None self.features_init = None - self.features_layer = None + self.layer = None + self.label = QLabel(f"Features found: {self.count}") @thread_worker def update_factory(self): @@ -29,7 +32,7 @@ def update_factory(self): pass if self.widget.mip is not None and isinstance(self.widget.mip, np.ndarray): - self.data, self.features_init = get_features_plot_data( + self.data, self.features_init, self.count = get_features_plot_data( self.widget.plot_fig, self.widget.mip, self.widget.dx, self.widget.dy, @@ -66,10 +69,11 @@ def callback(self, curr_range): # Create features layer if it doesn't exist if not self.layer_exists("Features"): cmap = napari.utils.Colormap(fire.colors, display_name=fire.name) - self.features_layer = self.widget.viewer.add_image(data=self.data, colormap=cmap, name='Features') + self.layer = self.widget.viewer.add_image(data=self.data, colormap=cmap, name='Features') # Update features layer - self.features_layer.data = self.data + self.layer.data = self.data + self.label.setText(f"Features found: {self.count}") self.widget.status.stop_animation() self.lock.release() diff --git a/src/napari_psf_extractor/plotting.py b/src/napari_psf_extractor/plotting.py index 16e7f29..f0157bd 100644 --- a/src/napari_psf_extractor/plotting.py +++ b/src/napari_psf_extractor/plotting.py @@ -4,6 +4,26 @@ def plot_mass_range(ax, mip, mass, features): + """ + Plot the features in the mass range [mass[0], mass[1]]. + + Parameters + ---------- + ax : matplotlib.axes.Axes + The axes to plot on. + mip : numpy.ndarray + The maximum intensity projection of the image stack. + mass : tuple + The mass range to plot. + features : pandas.DataFrame + The features to plot. + + Returns + ------- + int + The number of features found. + """ + # Enhance contrast in MIP (by taking the log) s = 1 / mip[mip != 0].min() # scaling factor (such that log(min) = 0 mip_log = np.log(s * mip, @@ -25,3 +45,5 @@ def plot_mass_range(ax, mip, mass, features): ax.imshow(background, cmap=fire) ax.plot(df['x'], df['y'], ls='', color='#00ff00', marker='o', ms=7, mfc='none', mew=1) + + return len(df) diff --git a/src/napari_psf_extractor/widget.py b/src/napari_psf_extractor/widget.py index af3574e..1afcd80 100644 --- a/src/napari_psf_extractor/widget.py +++ b/src/napari_psf_extractor/widget.py @@ -1,16 +1,16 @@ from typing import TYPE_CHECKING import numpy as np -from matplotlib import pyplot as plt +import psf_extractor as psfe from magicgui import magicgui +from matplotlib import pyplot as plt from napari.utils.notifications import show_error -import psf_extractor as psfe from qtpy.QtWidgets import QWidget, QVBoxLayout, QPushButton, QFileDialog from napari_psf_extractor.components.sliders import RangeSlider +from napari_psf_extractor.components.statusbar import StatusMessage from napari_psf_extractor.extractor import extract_psf from napari_psf_extractor.features import Features -from napari_psf_extractor.components.statusbar import StatusMessage from napari_psf_extractor.utils import normalize # Hide napari imports from type support and autocompletion @@ -93,6 +93,8 @@ def param_setter( self.layout().addWidget(param_setter.native) self.layout().addWidget(self.mass_slider) + self.layout().addWidget(self.features.label) + self.layout().addStretch(1) self.layout().addWidget(self.save_button) # --------------- @@ -135,6 +137,7 @@ def reset(self): self.mass_slider.hide() self.save_button.hide() + self.features.label.hide() def refresh(self): """ @@ -142,6 +145,7 @@ def refresh(self): """ if self.state == 0: self.mass_slider.reset() + self.features.label.show() self.mass_slider.show() self.save_button.show()