Skip to content

Commit fc7ca4d

Browse files
Merge pull request #138 from computational-cell-analytics/convenience
Convenience
2 parents d0f284a + 3b0fad1 commit fc7ca4d

File tree

5 files changed

+114
-14
lines changed

5 files changed

+114
-14
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"synapse_net.export_to_imod_objects = synapse_net.tools.cli:imod_object_cli",
1919
"synapse_net.run_supervised_training = synapse_net.training.supervised_training:main",
2020
"synapse_net.run_domain_adaptation = synapse_net.training.domain_adaptation:main",
21+
"synapse_net.visualize_vesicle_pools = synapse_net.tools.cli:pool_visualization_cli",
2122
],
2223
"napari.manifest": [
2324
"synapse_net = synapse_net:napari.yaml",

synapse_net/tools/cli.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from ..inference.inference import _get_model_registry, get_model, get_model_training_resolution, run_segmentation
99
from ..inference.scalable_segmentation import scalable_segmentation
1010
from ..inference.util import inference_helper, parse_tiling
11+
from .pool_visualization import _visualize_vesicle_pools
1112

1213

1314
def imod_point_cli():
@@ -99,6 +100,25 @@ def imod_object_cli():
99100
)
100101

101102

103+
def pool_visualization_cli():
104+
parser = argparse.ArgumentParser(description="Load tomogram data, vesicle pools and additional segmentations for viualization.") # noqa
105+
parser.add_argument(
106+
"--input_path", "-i", required=True,
107+
help="The filepath to the mrc file containing the tomogram data."
108+
)
109+
parser.add_argument(
110+
"--vesicle_path", "-v", required=True, help="The filepath to the tif file containing the vesicle segmentation."
111+
)
112+
parser.add_argument(
113+
"--table_path", "-t", required=True, help="The filepath to the table with the vesicle pool assignments."
114+
)
115+
parser.add_argument(
116+
"-s", "--segmentation_paths", nargs="+", help="Filepaths for additional segmentations."
117+
)
118+
args = parser.parse_args()
119+
_visualize_vesicle_pools(args.input_path, args.vesicle_path, args.table_path, args.segmentation_paths)
120+
121+
102122
# TODO: handle kwargs
103123
def segmentation_cli():
104124
parser = argparse.ArgumentParser(description="Run segmentation.")
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from pathlib import Path
2+
3+
import imageio.v3 as imageio
4+
import napari
5+
import pandas as pd
6+
7+
from ..file_utils import read_mrc
8+
9+
10+
def _create_pools(vesicles, table):
11+
label_ids, pool_colors = table.label.values, table.color.values
12+
13+
pools = vesicles
14+
colormap = {label_id: color for label_id, color in zip(label_ids, pool_colors)}
15+
colormap[None] = [0, 0, 0, 0]
16+
17+
return pools, colormap
18+
19+
20+
def _visualize_vesicle_pools(input_path, vesicle_path, table_path, segmentation_paths):
21+
# Load the tomogram data, including scale information.
22+
data, voxel_size = read_mrc(input_path)
23+
axes = "zyx" if data.ndim == 3 else "yx"
24+
scale = tuple(float(voxel_size[ax]) for ax in axes)
25+
print("Loading data with scale", scale, "nanometer")
26+
27+
# Load the vesicle layer.
28+
vesicles = imageio.imread(vesicle_path)
29+
30+
# Load the table with the pool assignments.
31+
# Create and add the pool layer.
32+
table = pd.read_excel(table_path)
33+
pools, colormap = _create_pools(vesicles, table)
34+
35+
viewer = napari.Viewer()
36+
viewer.add_image(data, scale=scale)
37+
viewer.add_labels(vesicles, scale=scale)
38+
viewer.add_labels(pools, scale=scale, name="pools", colormap=colormap)
39+
40+
# Add the additional segmentations.
41+
if segmentation_paths is not None:
42+
for seg_path in segmentation_paths:
43+
name = Path(seg_path).stem
44+
seg = imageio.imread(seg_path)
45+
viewer.add_labels(seg, name=name, scale=scale)
46+
47+
# FIXME something is wrong here.
48+
# Add the scale bar.
49+
# @magicgui(call_button="Add Scale Bar")
50+
# def add_scale_bar(v: napari.Viewer):
51+
# v.scale_bar.visible = True
52+
# v.scale_bar.unit = "nm"
53+
# viewer.window.add_dock_widget(add_scale_bar)
54+
55+
napari.run()

synapse_net/tools/size_filter_widget.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import napari.viewer
44

55
import numpy as np
6+
import nifty.tools as nt
67

78
from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QPushButton, QSpinBox, QLabel
89
from skimage.measure import regionprops, label
@@ -34,9 +35,9 @@ def __init__(self):
3435
threshold_layout.addWidget(self.size_threshold_widget)
3536
layout.addLayout(threshold_layout)
3637

37-
# Add a boolean field for processing binary masks.
38-
self.is_mask = False
39-
layout.addWidget(self._add_boolean_param("is_mask", self.is_mask, title="Binary Mask"))
38+
# Add a boolean field for applying a label operation before size filtering.
39+
self.apply_label = True
40+
layout.addWidget(self._add_boolean_param("apply_label", self.apply_label, title="Remove Disconnected Pieces"))
4041

4142
# Add an optional output layer name. If not given the segmentation will be over-written.
4243
self.output_layer_param, _ = self._add_string_param("output_layer", "", title="Output Layer", layout=layout)
@@ -48,19 +49,36 @@ def __init__(self):
4849
# Add the widgets to the layout.
4950
self.setLayout(layout)
5051

52+
def _filter_mask(self, segmentation, size_threshold):
53+
segmentation = label(segmentation).astype(segmentation.dtype)
54+
props = regionprops(segmentation)
55+
filter_ids = [prop.label for prop in props if prop.area < size_threshold]
56+
segmentation[np.isin(segmentation, filter_ids)] = 0
57+
segmentation = (segmentation > 0).astype(segmentation.dtype)
58+
return segmentation
59+
60+
def _filter_segmentation(self, segmentation, size_threshold, apply_label):
61+
dtype = segmentation.dtype
62+
if apply_label:
63+
original_segmentation = segmentation.copy()
64+
segmentation = label(segmentation)
65+
props = regionprops(segmentation, original_segmentation)
66+
else:
67+
props = regionprops(segmentation)
68+
filter_ids = [prop.label for prop in props if prop.area < size_threshold]
69+
segmentation[np.isin(segmentation, filter_ids)] = 0
70+
if apply_label:
71+
mapping = {prop.label: int(prop.max_intensity) for prop in props if prop.label not in filter_ids}
72+
mapping[0] = 0
73+
segmentation = nt.takeDict(mapping, segmentation)
74+
return segmentation.astype(dtype)
75+
5176
def on_size_filter(self):
5277
size_threshold = self.size_threshold_widget.value()
5378
seg_layer = self._get_layer_selector_layer(self.segmentation_selector_name)
5479
segmentation = seg_layer.data.copy()
5580

56-
if self.is_mask:
57-
segmentation = label(segmentation).astype(segmentation.dtype)
58-
59-
props = regionprops(segmentation)
60-
filter_ids = [prop.label for prop in props if prop.area < size_threshold]
61-
segmentation[np.isin(segmentation, filter_ids)] = 0
62-
if self.is_mask:
63-
segmentation = (segmentation > 0).astype(segmentation.dtype)
81+
segmentation = self._filter_segmentation(segmentation, size_threshold, self.apply_label)
6482

6583
# Write or overwrite segmentation layer.
6684
layer_name = self.output_layer_param.text()

synapse_net/tools/vesicle_pool_widget.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ def _update_pool_colors(self, pool_name, pool_color):
108108
next_color = pool_color
109109
self.pool_colors[pool_name] = next_color
110110

111+
def _add_colors(self, pool_properties, vesicle_colors):
112+
colors = np.array([vesicle_colors[label_id] for label_id in pool_properties.label.values])
113+
pool_properties["color"] = colors
114+
return pool_properties
115+
111116
def _compute_vesicle_pool(
112117
self,
113118
segmentation: np.ndarray,
@@ -136,9 +141,9 @@ def _compute_vesicle_pool(
136141
show_info("ERROR: Neither distances nor vesicle morphology were found.")
137142
return
138143
elif distances is None and morphology is not None: # Only morphology props were found.
139-
merged_df = pd.DataFrame(morphology).drop(columns=["index"])
144+
merged_df = pd.DataFrame(morphology).drop(columns=["index"], errors="ignore")
140145
elif distances is not None and morphology is None: # Only distances were found.
141-
merged_df = pd.DataFrame(distances).drop(columns=["index"])
146+
merged_df = pd.DataFrame(distances).drop(columns=["index"], errors="ignore")
142147
else: # Both were found.
143148
distance_ids = distances.get("label", [])
144149
morphology_ids = morphology.get("label", [])
@@ -190,7 +195,7 @@ def _compute_vesicle_pool(
190195
# Overwrite the intersection of the two pool assignments with the new pool.
191196
pool_intersections = np.intersect1d(pool_vesicle_ids, old_pool_ids)
192197
old_pool_ids = [item for item in old_pool_ids if item not in pool_intersections]
193-
pool_properties = pool_properties[~pool_properties['label'].isin(pool_intersections)]
198+
pool_properties = pool_properties[~pool_properties["label"].isin(pool_intersections)]
194199

195200
pool_assignments = sorted(pool_vesicle_ids + old_pool_ids)
196201

@@ -242,6 +247,7 @@ def _compute_vesicle_pool(
242247
else:
243248
pool_layer = self.viewer.add_labels(vesicle_pools, name=pool_layer_name, colormap=vesicle_colors)
244249

250+
pool_properties = self._add_colors(pool_properties, vesicle_colors)
245251
self._add_properties_and_table(pool_layer, pool_properties, save_path=self.save_path.text())
246252
pool_layer.refresh()
247253

0 commit comments

Comments
 (0)