-
Notifications
You must be signed in to change notification settings - Fork 106
Expand file tree
/
Copy pathannotator_2d.py
More file actions
280 lines (225 loc) · 9.92 KB
/
Copy pathannotator_2d.py
File metadata and controls
280 lines (225 loc) · 9.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import warnings
from typing import Optional, Tuple
import napari
import numpy as np
from magicgui import magicgui
from napari import Viewer
from segment_anything import SamPredictor
from .. import instance_segmentation, util
from ..precompute_state import cache_amg_state
from ..visualization import project_embeddings_for_visualization
from . import util as vutil
from .gui_utils import show_wrong_file_warning
@magicgui(call_button="Segment Object [S]")
def _segment_widget(v: Viewer) -> None:
# get the current box and point prompts
boxes = vutil.prompt_layer_to_boxes(v.layers["box_prompts"])
points, labels = vutil.prompt_layer_to_points(v.layers["prompts"])
shape = v.layers["current_object"].data.shape
if IMAGE_EMBEDDINGS["original_size"] is None: # tiled prediction
seg = vutil.prompt_segmentation(
PREDICTOR, points, labels, boxes, shape, image_embeddings=IMAGE_EMBEDDINGS, multiple_box_prompts=True
)
else: # normal prediction and we have set the precomputed embeddings already
seg = vutil.prompt_segmentation(PREDICTOR, points, labels, boxes, shape, multiple_box_prompts=True)
# no prompts were given or prompts were invalid, skip segmentation
if seg is None:
print("You either haven't provided any prompts or invalid prompts. The segmentation will be skipped.")
return
v.layers["current_object"].data = seg
v.layers["current_object"].refresh()
def _changed_param(amg, **params):
if amg is None:
return None
for name, val in params.items():
if hasattr(amg, f"_{name}") and getattr(amg, f"_{name}") != val:
return name
return None
@magicgui(call_button="Automatic Segmentation")
def _autosegment_widget(
v: Viewer,
pred_iou_thresh: float = 0.88,
stability_score_thresh: float = 0.95,
min_object_size: int = 100,
with_background: bool = True,
) -> None:
global AMG
is_tiled = IMAGE_EMBEDDINGS["input_size"] is None
if AMG is None:
AMG = instance_segmentation.get_amg(PREDICTOR, is_tiled)
print("initializing autosegment")
if not AMG.is_initialized:
AMG.initialize(v.layers["raw"].data, image_embeddings=IMAGE_EMBEDDINGS, verbose=True)
seg = AMG.generate(pred_iou_thresh=pred_iou_thresh, stability_score_thresh=stability_score_thresh)
shape = v.layers["raw"].data.shape[:2]
seg = instance_segmentation.mask_data_to_segmentation(
seg, shape, with_background=True, min_object_size=min_object_size
)
assert isinstance(seg, np.ndarray)
v.layers["auto_segmentation"].data = seg
v.layers["auto_segmentation"].refresh()
def _get_shape(raw):
if raw.ndim == 2:
shape = raw.shape
elif raw.ndim == 3 and raw.shape[-1] == 3:
shape = raw.shape[:2]
else:
raise ValueError(f"Invalid input image of shape {raw.shape}. Expect either 2D grayscale or 3D RGB image.")
return shape
def _initialize_viewer(raw, segmentation_result, tile_shape, show_embeddings):
v = Viewer()
#
# initialize the viewer and add layers
#
v.add_image(raw, name="raw")
shape = _get_shape(raw)
v.add_labels(data=np.zeros(shape, dtype="uint32"), name="auto_segmentation")
if segmentation_result is None:
v.add_labels(data=np.zeros(shape, dtype="uint32"), name="committed_objects")
else:
v.add_labels(segmentation_result, name="committed_objects")
v.layers["committed_objects"].new_colormap() # randomize colors so it is easy to see when object committed
v.add_labels(data=np.zeros(shape, dtype="uint32"), name="current_object")
# show the PCA of the image embeddings
if show_embeddings:
embedding_vis, scale = project_embeddings_for_visualization(IMAGE_EMBEDDINGS)
v.add_image(embedding_vis, name="embeddings", scale=scale)
labels = ["positive", "negative"]
prompts = v.add_points(
data=[[0.0, 0.0], [0.0, 0.0]], # FIXME workaround
name="prompts",
properties={"label": labels},
edge_color="label",
edge_color_cycle=vutil.LABEL_COLOR_CYCLE,
symbol="o",
face_color="transparent",
edge_width=0.5,
size=12,
ndim=2,
)
prompts.edge_color_mode = "cycle"
v.add_shapes(
face_color="transparent", edge_color="green", edge_width=4, name="box_prompts"
)
#
# add the widgets
#
prompt_widget = vutil.create_prompt_menu(prompts, labels)
v.window.add_dock_widget(prompt_widget)
v.window.add_dock_widget(_autosegment_widget)
v.window.add_dock_widget(_segment_widget)
v.window.add_dock_widget(vutil._commit_segmentation_widget)
v.window.add_dock_widget(vutil._clear_widget)
#
# key bindings
#
@v.bind_key("s")
def _segmet(v):
_segment_widget(v)
@v.bind_key("c")
def _commit(v):
vutil._commit_segmentation_widget(v)
@v.bind_key("t")
def _toggle_label(event=None):
vutil.toggle_label(prompts)
@v.bind_key("Shift-C")
def clear_prompts(v):
vutil.clear_annotations(v)
return v
def _update_viewer(v, raw, show_embeddings, segmentation_result):
if show_embeddings or segmentation_result is not None:
raise NotImplementedError
# update the image layer
v.layers["raw"].data = raw
shape = _get_shape(raw)
# update the segmentation layers
v.layers["auto_segmentation"].data = np.zeros(shape, dtype="uint32")
v.layers["committed_objects"].data = np.zeros(shape, dtype="uint32")
v.layers["current_object"].data = np.zeros(shape, dtype="uint32")
def annotator_2d(
raw: np.ndarray,
embedding_path: Optional[str] = None,
show_embeddings: bool = False,
segmentation_result: Optional[np.ndarray] = None,
model_type: str = util._DEFAULT_MODEL,
tile_shape: Optional[Tuple[int, int]] = None,
halo: Optional[Tuple[int, int]] = None,
return_viewer: bool = False,
v: Optional[Viewer] = None,
predictor: Optional[SamPredictor] = None,
precompute_amg_state: bool = False,
) -> Optional[Viewer]:
"""The 2d annotation tool.
Args:
raw: The image data.
embedding_path: Filepath where to save the embeddings.
show_embeddings: Show PCA visualization of the image embeddings.
This can be helpful to judge how well Segment Anything works for your data,
and which objects can be segmented.
segmentation_result: An initial segmentation to load.
This can be used to correct segmentations with Segment Anything or to save and load progress.
The segmentation will be loaded as the 'committed_objects' layer.
model_type: The Segment Anything model to use. For details on the available models check out
https://computational-cell-analytics.github.io/micro-sam/micro_sam.html#finetuned-models.
tile_shape: Shape of tiles for tiled embedding prediction.
If `None` then the whole image is passed to Segment Anything.
halo: Shape of the overlap between tiles, which is needed to segment objects on tile boarders.
return_viewer: Whether to return the napari viewer to further modify it before starting the tool.
v: The viewer to which the SegmentAnything functionality should be added.
This enables using a pre-initialized viewer, for example in `sam_annotator.image_series_annotator`.
predictor: The Segment Anything model. Passing this enables using fully custom models.
If you pass `predictor` then `model_type` will be ignored.
precompute_amg_state: Whether to precompute the state for automatic mask generation.
This will take more time when precomputing embeddings, but will then make
automatic mask generation much faster.
Returns:
The napari viewer, only returned if `return_viewer=True`.
"""
# for access to the predictor and the image embeddings in the widgets
global PREDICTOR, IMAGE_EMBEDDINGS, AMG
AMG = None
if predictor is None:
PREDICTOR = util.get_sam_model(model_type=model_type)
else:
PREDICTOR = predictor
IMAGE_EMBEDDINGS = util.precompute_image_embeddings(
PREDICTOR, raw, save_path=embedding_path, ndim=2, tile_shape=tile_shape, halo=halo,
wrong_file_callback=show_wrong_file_warning
)
if precompute_amg_state and (embedding_path is not None):
AMG = cache_amg_state(PREDICTOR, raw, IMAGE_EMBEDDINGS, embedding_path)
# we set the pre-computed image embeddings if we don't use tiling
# (if we use tiling we cannot directly set it because the tile will be chosen dynamically)
if tile_shape is None:
util.set_precomputed(PREDICTOR, IMAGE_EMBEDDINGS)
# viewer is freshly initialized
if v is None:
v = _initialize_viewer(raw, segmentation_result, tile_shape, show_embeddings)
# we use an existing viewer and just update all the layers
else:
_update_viewer(v, raw, show_embeddings, segmentation_result)
#
# start the viewer
#
vutil.clear_annotations(v, clear_segmentations=False)
if return_viewer:
return v
napari.run()
def main():
"""@private"""
parser = vutil._initialize_parser(description="Run interactive segmentation for an image.")
parser.add_argument("--precompute_amg_state", action="store_true")
args = parser.parse_args()
raw = util.load_image_data(args.input, key=args.key)
if args.segmentation_result is None:
segmentation_result = None
else:
segmentation_result = util.load_image_data(args.segmentation_result, key=args.segmentation_key)
if args.embedding_path is None:
warnings.warn("You have not passed an embedding_path. Restarting the annotator may take a long time.")
annotator_2d(
raw, embedding_path=args.embedding_path,
show_embeddings=args.show_embeddings, segmentation_result=segmentation_result,
model_type=args.model_type, tile_shape=args.tile_shape, halo=args.halo,
precompute_amg_state=args.precompute_amg_state,
)