synapse_net.tools.distance_measure_widget

  1import napari
  2import napari.layers
  3import numpy as np
  4import pandas as pd
  5
  6from napari.utils.notifications import show_info
  7from qtpy.QtWidgets import QWidget, QVBoxLayout, QPushButton
  8
  9from .base_widget import BaseWidget
 10from .. import distance_measurements
 11
 12
 13class DistanceMeasureWidget(BaseWidget):
 14    def __init__(self):
 15        super().__init__()
 16
 17        self.viewer = napari.current_viewer()
 18        layout = QVBoxLayout()
 19
 20        self.image_selector_name1 = "Segmentation"
 21        self.image_selector_name2 = "Object"
 22        # Create the image selection dropdown.
 23        self.segmentation1_selector_widget = self._create_layer_selector(self.image_selector_name1, layer_type="Labels")
 24        self.segmentation2_selector_widget = self._create_layer_selector(self.image_selector_name2, layer_type="Labels")
 25
 26        # Create advanced settings.
 27        self.settings = self._create_settings_widget()
 28
 29        # Create and connect buttons.
 30        self.measure_button1 = QPushButton("Measure Distances")
 31        self.measure_button1.clicked.connect(self.on_measure_seg_to_object)
 32
 33        self.measure_button2 = QPushButton("Measure Pairwise Distances")
 34        self.measure_button2.clicked.connect(self.on_measure_pairwise)
 35
 36        # Add the widgets to the layout.
 37        layout.addWidget(self.segmentation1_selector_widget)
 38        layout.addWidget(self.segmentation2_selector_widget)
 39        layout.addWidget(self.settings)
 40        layout.addWidget(self.measure_button1)
 41        layout.addWidget(self.measure_button2)
 42
 43        self.setLayout(layout)
 44
 45    def _to_table_data(self, distances, seg_ids, endpoints1=None, endpoints2=None):
 46        assert len(distances) == len(seg_ids), f"{distances.shape}, {seg_ids.shape}"
 47        if seg_ids.ndim == 2:
 48            table_data = {"label1": seg_ids[:, 0], "label2": seg_ids[:, 1], "distance": distances}
 49        else:
 50            table_data = {"label": seg_ids, "distance": distances}
 51        if endpoints1 is not None:
 52            axis_names = "zyx" if endpoints1.shape[1] == 3 else "yx"
 53            table_data.update({f"begin-{ax}": endpoints1[:, i] for i, ax in enumerate(axis_names)})
 54            table_data.update({f"end-{ax}": endpoints2[:, i] for i, ax in enumerate(axis_names)})
 55        return pd.DataFrame(table_data)
 56
 57    def _add_lines_and_table(self, lines, table_data, name):
 58        line_layer = self.viewer.add_shapes(
 59            lines,
 60            name=name,
 61            shape_type="line",
 62            edge_width=2,
 63            edge_color="red",
 64            blending="additive",
 65        )
 66        self._add_properties_and_table(line_layer, table_data, self.save_path.text())
 67
 68    def on_measure_seg_to_object(self):
 69        segmentation = self._get_layer_selector_data(self.image_selector_name1)
 70        object_data = self._get_layer_selector_data(self.image_selector_name2)
 71
 72        # Get the resolution / voxel size.
 73        metadata = self._get_layer_selector_data(self.image_selector_name1, return_metadata=True)
 74        resolution = self._handle_resolution(metadata, self.voxel_size_param, segmentation.ndim)
 75
 76        (distances,
 77         endpoints1,
 78         endpoints2,
 79         seg_ids) = distance_measurements.measure_segmentation_to_object_distances(
 80            segmentation=segmentation, segmented_object=object_data, distance_type="boundary",
 81            resolution=resolution
 82        )
 83        lines, _ = distance_measurements.create_object_distance_lines(
 84            distances=distances,
 85            endpoints1=endpoints1,
 86            endpoints2=endpoints2,
 87            seg_ids=seg_ids,
 88        )
 89        table_data = self._to_table_data(distances, seg_ids, endpoints1, endpoints2)
 90        structure_layer_name = self._get_layer_selector_layer(self.image_selector_name2).name
 91        self._add_lines_and_table(lines, table_data, name="distances-to-" + structure_layer_name)
 92
 93    def on_measure_pairwise(self):
 94        segmentation = self._get_layer_selector_data(self.image_selector_name1)
 95        if segmentation is None:
 96            show_info("Please choose a segmentation.")
 97            return
 98
 99        metadata = self._get_layer_selector_data(self.image_selector_name1, return_metadata=True)
100        resolution = self._handle_resolution(metadata, self.voxel_size_param, segmentation.ndim)
101
102        (distances,
103         endpoints1,
104         endpoints2,
105         seg_ids) = distance_measurements.measure_pairwise_object_distances(
106            segmentation=segmentation, distance_type="boundary", resolution=resolution
107        )
108        lines, properties = distance_measurements.create_pairwise_distance_lines(
109            distances=distances, endpoints1=endpoints1, endpoints2=endpoints2, seg_ids=seg_ids.tolist(),
110        )
111        table_data = self._to_table_data(
112            distances=properties["distance"],
113            seg_ids=np.concatenate([properties["id_a"][:, None], properties["id_b"][:, None]], axis=1)
114        )
115        self._add_lines_and_table(lines, table_data, name="pairwise-distances")
116
117    def _create_settings_widget(self):
118        setting_values = QWidget()
119        setting_values.setLayout(QVBoxLayout())
120
121        self.save_path, layout = self._add_path_param(name="Save Table", select_type="file", value="")
122        setting_values.layout().addLayout(layout)
123
124        self.voxel_size_param, layout = self._add_float_param(
125            "voxel_size", 0.0, min_val=0.0, max_val=100.0,
126        )
127        setting_values.layout().addLayout(layout)
128
129        settings = self._make_collapsible(widget=setting_values, title="Advanced Settings")
130        return settings
class DistanceMeasureWidget(synapse_net.tools.base_widget.BaseWidget):
 14class DistanceMeasureWidget(BaseWidget):
 15    def __init__(self):
 16        super().__init__()
 17
 18        self.viewer = napari.current_viewer()
 19        layout = QVBoxLayout()
 20
 21        self.image_selector_name1 = "Segmentation"
 22        self.image_selector_name2 = "Object"
 23        # Create the image selection dropdown.
 24        self.segmentation1_selector_widget = self._create_layer_selector(self.image_selector_name1, layer_type="Labels")
 25        self.segmentation2_selector_widget = self._create_layer_selector(self.image_selector_name2, layer_type="Labels")
 26
 27        # Create advanced settings.
 28        self.settings = self._create_settings_widget()
 29
 30        # Create and connect buttons.
 31        self.measure_button1 = QPushButton("Measure Distances")
 32        self.measure_button1.clicked.connect(self.on_measure_seg_to_object)
 33
 34        self.measure_button2 = QPushButton("Measure Pairwise Distances")
 35        self.measure_button2.clicked.connect(self.on_measure_pairwise)
 36
 37        # Add the widgets to the layout.
 38        layout.addWidget(self.segmentation1_selector_widget)
 39        layout.addWidget(self.segmentation2_selector_widget)
 40        layout.addWidget(self.settings)
 41        layout.addWidget(self.measure_button1)
 42        layout.addWidget(self.measure_button2)
 43
 44        self.setLayout(layout)
 45
 46    def _to_table_data(self, distances, seg_ids, endpoints1=None, endpoints2=None):
 47        assert len(distances) == len(seg_ids), f"{distances.shape}, {seg_ids.shape}"
 48        if seg_ids.ndim == 2:
 49            table_data = {"label1": seg_ids[:, 0], "label2": seg_ids[:, 1], "distance": distances}
 50        else:
 51            table_data = {"label": seg_ids, "distance": distances}
 52        if endpoints1 is not None:
 53            axis_names = "zyx" if endpoints1.shape[1] == 3 else "yx"
 54            table_data.update({f"begin-{ax}": endpoints1[:, i] for i, ax in enumerate(axis_names)})
 55            table_data.update({f"end-{ax}": endpoints2[:, i] for i, ax in enumerate(axis_names)})
 56        return pd.DataFrame(table_data)
 57
 58    def _add_lines_and_table(self, lines, table_data, name):
 59        line_layer = self.viewer.add_shapes(
 60            lines,
 61            name=name,
 62            shape_type="line",
 63            edge_width=2,
 64            edge_color="red",
 65            blending="additive",
 66        )
 67        self._add_properties_and_table(line_layer, table_data, self.save_path.text())
 68
 69    def on_measure_seg_to_object(self):
 70        segmentation = self._get_layer_selector_data(self.image_selector_name1)
 71        object_data = self._get_layer_selector_data(self.image_selector_name2)
 72
 73        # Get the resolution / voxel size.
 74        metadata = self._get_layer_selector_data(self.image_selector_name1, return_metadata=True)
 75        resolution = self._handle_resolution(metadata, self.voxel_size_param, segmentation.ndim)
 76
 77        (distances,
 78         endpoints1,
 79         endpoints2,
 80         seg_ids) = distance_measurements.measure_segmentation_to_object_distances(
 81            segmentation=segmentation, segmented_object=object_data, distance_type="boundary",
 82            resolution=resolution
 83        )
 84        lines, _ = distance_measurements.create_object_distance_lines(
 85            distances=distances,
 86            endpoints1=endpoints1,
 87            endpoints2=endpoints2,
 88            seg_ids=seg_ids,
 89        )
 90        table_data = self._to_table_data(distances, seg_ids, endpoints1, endpoints2)
 91        structure_layer_name = self._get_layer_selector_layer(self.image_selector_name2).name
 92        self._add_lines_and_table(lines, table_data, name="distances-to-" + structure_layer_name)
 93
 94    def on_measure_pairwise(self):
 95        segmentation = self._get_layer_selector_data(self.image_selector_name1)
 96        if segmentation is None:
 97            show_info("Please choose a segmentation.")
 98            return
 99
100        metadata = self._get_layer_selector_data(self.image_selector_name1, return_metadata=True)
101        resolution = self._handle_resolution(metadata, self.voxel_size_param, segmentation.ndim)
102
103        (distances,
104         endpoints1,
105         endpoints2,
106         seg_ids) = distance_measurements.measure_pairwise_object_distances(
107            segmentation=segmentation, distance_type="boundary", resolution=resolution
108        )
109        lines, properties = distance_measurements.create_pairwise_distance_lines(
110            distances=distances, endpoints1=endpoints1, endpoints2=endpoints2, seg_ids=seg_ids.tolist(),
111        )
112        table_data = self._to_table_data(
113            distances=properties["distance"],
114            seg_ids=np.concatenate([properties["id_a"][:, None], properties["id_b"][:, None]], axis=1)
115        )
116        self._add_lines_and_table(lines, table_data, name="pairwise-distances")
117
118    def _create_settings_widget(self):
119        setting_values = QWidget()
120        setting_values.setLayout(QVBoxLayout())
121
122        self.save_path, layout = self._add_path_param(name="Save Table", select_type="file", value="")
123        setting_values.layout().addLayout(layout)
124
125        self.voxel_size_param, layout = self._add_float_param(
126            "voxel_size", 0.0, min_val=0.0, max_val=100.0,
127        )
128        setting_values.layout().addLayout(layout)
129
130        settings = self._make_collapsible(widget=setting_values, title="Advanced Settings")
131        return settings

QWidget(parent: typing.Optional[QWidget] = None, flags: Union[Qt.WindowFlags, Qt.WindowType] = Qt.WindowFlags())

viewer
image_selector_name1
image_selector_name2
segmentation1_selector_widget
segmentation2_selector_widget
settings
measure_button1
measure_button2
def on_measure_seg_to_object(self):
69    def on_measure_seg_to_object(self):
70        segmentation = self._get_layer_selector_data(self.image_selector_name1)
71        object_data = self._get_layer_selector_data(self.image_selector_name2)
72
73        # Get the resolution / voxel size.
74        metadata = self._get_layer_selector_data(self.image_selector_name1, return_metadata=True)
75        resolution = self._handle_resolution(metadata, self.voxel_size_param, segmentation.ndim)
76
77        (distances,
78         endpoints1,
79         endpoints2,
80         seg_ids) = distance_measurements.measure_segmentation_to_object_distances(
81            segmentation=segmentation, segmented_object=object_data, distance_type="boundary",
82            resolution=resolution
83        )
84        lines, _ = distance_measurements.create_object_distance_lines(
85            distances=distances,
86            endpoints1=endpoints1,
87            endpoints2=endpoints2,
88            seg_ids=seg_ids,
89        )
90        table_data = self._to_table_data(distances, seg_ids, endpoints1, endpoints2)
91        structure_layer_name = self._get_layer_selector_layer(self.image_selector_name2).name
92        self._add_lines_and_table(lines, table_data, name="distances-to-" + structure_layer_name)
def on_measure_pairwise(self):
 94    def on_measure_pairwise(self):
 95        segmentation = self._get_layer_selector_data(self.image_selector_name1)
 96        if segmentation is None:
 97            show_info("Please choose a segmentation.")
 98            return
 99
100        metadata = self._get_layer_selector_data(self.image_selector_name1, return_metadata=True)
101        resolution = self._handle_resolution(metadata, self.voxel_size_param, segmentation.ndim)
102
103        (distances,
104         endpoints1,
105         endpoints2,
106         seg_ids) = distance_measurements.measure_pairwise_object_distances(
107            segmentation=segmentation, distance_type="boundary", resolution=resolution
108        )
109        lines, properties = distance_measurements.create_pairwise_distance_lines(
110            distances=distances, endpoints1=endpoints1, endpoints2=endpoints2, seg_ids=seg_ids.tolist(),
111        )
112        table_data = self._to_table_data(
113            distances=properties["distance"],
114            seg_ids=np.concatenate([properties["id_a"][:, None], properties["id_b"][:, None]], axis=1)
115        )
116        self._add_lines_and_table(lines, table_data, name="pairwise-distances")