Source code for napari_locan.widgets.widget_select

"""
Select localizations from SMLM dataset.

A QWidget plugin to select localizations in current SMLM dataset based on a
filter specification.
A new SMLM dataset will be created.
"""

from __future__ import annotations

import logging

import locan as lc
from napari.utils import progress
from napari.viewer import Viewer
from qtpy.QtWidgets import (
    QCheckBox,
    QComboBox,
    QDoubleSpinBox,
    QHBoxLayout,
    QLabel,
    QPlainTextEdit,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

from napari_locan import filter_specifications, smlm_data
from napari_locan.data_model.filter_specifications import FilterSpecifications
from napari_locan.data_model.smlm_data import SmlmData

logger = logging.getLogger(__name__)


[docs] class SelectQWidget(QWidget): # type: ignore[misc] def __init__( self, napari_viewer: Viewer, smlm_data: SmlmData = smlm_data, filter_specifications: FilterSpecifications = filter_specifications, ): super().__init__() self.viewer = napari_viewer self.smlm_data = smlm_data self.filter_specifications = filter_specifications if self.filter_specifications.dataset is None: self.filter_specifications.append_item(dataset={}) self._add_loc_property_selector() self._add_selection_tools() self._add_condition_text() self._add_buttons() self._connect_loc_property_selector() self._connect_selection_tools() self._connect_condition_text() self._connect_buttons() self._init_widget_values() self._set_layout() def _add_loc_property_selector(self) -> None: self._loc_property_label = QLabel("Localization property:") self._loc_property_combobox = QComboBox() self._loc_property_combobox.setToolTip( "Choose localization property for selected SMLM dataset and selected filter specifications." ) # condition excludes smlm_data.locdata to be None in what comes: if self.smlm_data.index != -1 and bool(self.smlm_data.locdata): self._loc_property_combobox.addItems( self.smlm_data.locdata.data.columns # type: ignore ) self._loc_property_selector_layout = QHBoxLayout() self._loc_property_selector_layout.addWidget(self._loc_property_label) self._loc_property_selector_layout.addWidget(self._loc_property_combobox) def _connect_loc_property_selector(self) -> None: self.smlm_data.index_changed_signal.connect( self._loc_property_combobox_slot_for_smlm_data_index ) self.filter_specifications.index_changed_signal.connect( self._loc_property_combobox_slot_for_filter_specifications_index ) self._loc_property_combobox.currentIndexChanged.connect( self._loc_property_combobox_on_changed ) def _add_selection_tools(self) -> None: self._lower_bound_label = QLabel("Min:") self._lower_bound_spinbox = QDoubleSpinBox() self._upper_bound_label = QLabel("Max:") self._upper_bound_spinbox = QDoubleSpinBox() self._apply_checkbox = QCheckBox("Apply") self._apply_checkbox.setToolTip("Set selection to current settings.") self._selection_tools_layout = QHBoxLayout() self._selection_tools_layout.addWidget(self._lower_bound_label) self._selection_tools_layout.addWidget(self._lower_bound_spinbox) self._selection_tools_layout.addWidget(self._upper_bound_label) self._selection_tools_layout.addWidget(self._upper_bound_spinbox) self._selection_tools_layout.addWidget(self._apply_checkbox) def _connect_selection_tools(self) -> None: self._lower_bound_spinbox.valueChanged.connect( self._lower_bound_spinbox_on_changed ) self._upper_bound_spinbox.valueChanged.connect( self._upper_bound_spinbox_on_changed ) self._apply_checkbox.stateChanged.connect(self._apply_checkbox_on_changed) def _add_condition_text(self) -> None: self._condition_text_label = QLabel("Condition:") self._condition_text_edit = QPlainTextEdit() self._condition_text_edit.setReadOnly(True) self._condition_text_layout = QVBoxLayout() self._condition_text_layout.addWidget(self._condition_text_label) self._condition_text_layout.addWidget(self._condition_text_edit) def _connect_condition_text(self) -> None: self.filter_specifications.index_changed_signal.connect( self._filter_specifications_index_on_changed ) def _add_buttons(self) -> None: self._select_button = QPushButton("Select") self._select_button.setToolTip( "Filter the selected SMLM data according to the condition and keep " "selection as new SMLM dataset." ) self._buttons_layout = QHBoxLayout() self._buttons_layout.addWidget(self._select_button) def _connect_buttons(self) -> None: self._select_button.clicked.connect(self._select_button_on_click) def _init_widget_values(self) -> None: try: self._loc_property_combobox.setCurrentIndex(0) except IndexError: self._loc_property_combobox.setCurrentIndex(-1) self._loc_property_combobox_on_changed() self._update_condition_text() def _set_layout(self) -> None: layout = QVBoxLayout() layout.addLayout(self._loc_property_selector_layout) layout.addLayout(self._selection_tools_layout) layout.addLayout(self._condition_text_layout) layout.addLayout(self._buttons_layout) self.setLayout(layout) def _loc_property_combobox_slot_for_smlm_data_index(self, index: int) -> None: key_index = self._loc_property_combobox.currentIndex() self._loc_property_combobox.clear() if index != -1: self._loc_property_combobox.addItems( self.smlm_data.locdata.data.columns # type: ignore ) if key_index == -1: if bool(self.smlm_data.locdata): self._loc_property_combobox.setCurrentIndex(0) else: self._loc_property_combobox.setCurrentIndex(-1) else: self._loc_property_combobox.setCurrentIndex(key_index) if self.filter_specifications.dataset is not None: self._loc_property_combobox.addItems( self.filter_specifications.dataset.keys() ) def _loc_property_combobox_slot_for_filter_specifications_index(self) -> None: if self.filter_specifications.index == -1: self._loc_property_combobox.clear() else: self._loc_property_combobox_slot_for_smlm_data_index( index=self.smlm_data.index ) def _loc_property_combobox_on_changed(self) -> None: if self._loc_property_combobox.currentIndex() == -1: self._lower_bound_spinbox.setHidden(True) self._upper_bound_spinbox.setHidden(True) self._apply_checkbox.setHidden(True) else: self._lower_bound_spinbox.setHidden(False) self._upper_bound_spinbox.setHidden(False) self._apply_checkbox.setHidden(False) loc_property = self._loc_property_combobox.currentText() min_, max_ = self._get_spinbox_boundaries() self._lower_bound_spinbox.setRange(min_, max_) self._upper_bound_spinbox.setRange(min_, max_) if loc_property in self.filter_specifications.dataset: # type: ignore[operator] self._lower_bound_spinbox.setValue( self.filter_specifications.dataset[loc_property].lower_bound # type: ignore[index] ) self._upper_bound_spinbox.setValue( self.filter_specifications.dataset[loc_property].upper_bound # type: ignore[index] ) self._apply_checkbox.setChecked( self.filter_specifications.dataset[loc_property].activate # type: ignore[index] ) else: self._lower_bound_spinbox.setValue( self.smlm_data.locdata.data[loc_property].min() # type: ignore[union-attr] ) self._upper_bound_spinbox.setValue( self.smlm_data.locdata.data[loc_property].max() # type: ignore[union-attr] ) self._apply_checkbox.setChecked(False) def _get_spinbox_boundaries(self) -> tuple[float, float]: loc_property = self._loc_property_combobox.currentText() if self.smlm_data.locdata is None: min_, max_ = 0, 0 else: min_value = self.smlm_data.locdata.data[loc_property].min() max_value = self.smlm_data.locdata.data[loc_property].max() min_ = min_value * 10 if min_value < 0 else 0 max_ = max_value * 10 return min_, max_ def _filter_specifications_index_on_changed(self) -> None: if self.filter_specifications.index == -1: self._loc_property_combobox.setCurrentIndex(-1) else: if ( self._loc_property_combobox.currentText() not in self.filter_specifications.dataset # type: ignore[operator] ): self._loc_property_combobox.setCurrentIndex(0) else: loc_property = self._loc_property_combobox.currentText() self._lower_bound_spinbox.setValue( self.filter_specifications.dataset[loc_property].lower_bound # type: ignore[index] ) self._upper_bound_spinbox.setValue( self.filter_specifications.dataset[loc_property].upper_bound # type: ignore[index] ) self._apply_checkbox.setChecked( self.filter_specifications.dataset[loc_property].activate # type: ignore[index] ) self._update_condition_text() def _apply_checkbox_on_changed(self) -> None: loc_property = self._loc_property_combobox.currentText() if self._loc_property_combobox.currentIndex() != -1: try: self.filter_specifications.dataset[ # type: ignore[index] loc_property ].activate = self._apply_checkbox.isChecked() except KeyError: selector = lc.Selector( loc_property=loc_property, activate=self._apply_checkbox.isChecked(), lower_bound=self._lower_bound_spinbox.value(), upper_bound=self._upper_bound_spinbox.value(), ) self.filter_specifications.dataset[loc_property] = selector # type: ignore[index] self._update_condition_text() def _lower_bound_spinbox_on_changed(self) -> None: loc_property = self._loc_property_combobox.currentText() if self._loc_property_combobox.currentIndex() != -1: try: self.filter_specifications.dataset[ # type: ignore[index] loc_property ].lower_bound = self._lower_bound_spinbox.value() except KeyError: selector = lc.Selector( loc_property=loc_property, activate=self._apply_checkbox.isChecked(), lower_bound=self._lower_bound_spinbox.value(), upper_bound=self._upper_bound_spinbox.value(), ) self.filter_specifications.dataset[loc_property] = selector # type: ignore[index] self._update_condition_text() def _upper_bound_spinbox_on_changed(self) -> None: loc_property = self._loc_property_combobox.currentText() if self._loc_property_combobox.currentIndex() != -1: try: self.filter_specifications.dataset[ # type: ignore[index] loc_property ].upper_bound = self._upper_bound_spinbox.value() except KeyError: selector = lc.Selector( loc_property=loc_property, activate=self._apply_checkbox.isChecked(), lower_bound=self._lower_bound_spinbox.value(), upper_bound=self._upper_bound_spinbox.value(), ) self.filter_specifications.dataset[loc_property] = selector # type: ignore[index] self._update_condition_text() def _update_condition_text(self) -> None: if self.filter_specifications.index != -1: text = self.filter_specifications.filter_condition self._condition_text_edit.setPlainText(text) else: self._condition_text_edit.setPlainText("") def _select_button_on_click(self) -> None: locdata = self.smlm_data.locdata if locdata is None: raise ValueError("There is no SMLM data available.") elif bool(locdata) is False: raise ValueError("Locdata is empty.") if not self.filter_specifications.filter_condition: raise ValueError("Filter condition cannot be an empty string.") with progress() as progress_bar: progress_bar.set_description("Selecting:") new_locdata = lc.select_by_condition( locdata=locdata, condition=self.filter_specifications.filter_condition ) self.smlm_data.append_item( locdata=new_locdata, locdata_name=new_locdata.meta.identifier + "-selection", )