small-fish-gui 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- small_fish_gui/__init__.py +41 -0
- small_fish_gui/__main__.py +15 -0
- small_fish_gui/gui/__init__.py +29 -0
- small_fish_gui/gui/animation.py +30 -0
- small_fish_gui/gui/general_help_screenshot.png +0 -0
- small_fish_gui/gui/help_module.py +256 -0
- small_fish_gui/gui/layout.py +184 -0
- small_fish_gui/gui/mapping_help_screenshot.png +0 -0
- small_fish_gui/gui/prompts.py +338 -0
- small_fish_gui/gui/segmentation_help_screenshot.png +0 -0
- small_fish_gui/gui/test.py +4 -0
- small_fish_gui/interface/__init__.py +10 -0
- small_fish_gui/interface/image.py +38 -0
- small_fish_gui/interface/output.py +42 -0
- small_fish_gui/interface/parameters.py +2 -0
- small_fish_gui/interface/testing.py +8 -0
- small_fish_gui/pipeline/_colocalisation.py +266 -0
- small_fish_gui/pipeline/_custom_errors.py +2 -0
- small_fish_gui/pipeline/_detection_visualisation.py +139 -0
- small_fish_gui/pipeline/_preprocess.py +272 -0
- small_fish_gui/pipeline/_segmentation.py +334 -0
- small_fish_gui/pipeline/_signaltonoise.py +219 -0
- small_fish_gui/pipeline/actions.py +127 -0
- small_fish_gui/pipeline/detection.py +640 -0
- small_fish_gui/pipeline/main.py +80 -0
- small_fish_gui/pipeline/test.py +116 -0
- small_fish_gui/start.py +7 -0
- small_fish_gui/utils.py +55 -0
- small_fish_gui-1.0.0.dist-info/METADATA +57 -0
- small_fish_gui-1.0.0.dist-info/RECORD +32 -0
- small_fish_gui-1.0.0.dist-info/WHEEL +4 -0
- small_fish_gui-1.0.0.dist-info/licenses/LICENSE +24 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contains cellpose wrappers to segmentate images.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from cellpose.core import use_gpu
|
|
6
|
+
from skimage.measure import label
|
|
7
|
+
from ..gui.layout import _segmentation_layout
|
|
8
|
+
from ..gui import prompt, prompt_with_help, ask_cancel_segmentation
|
|
9
|
+
|
|
10
|
+
import cellpose.models as models
|
|
11
|
+
import numpy as np
|
|
12
|
+
import bigfish.multistack as multistack
|
|
13
|
+
import bigfish.stack as stack
|
|
14
|
+
import bigfish.plot as plot
|
|
15
|
+
import PySimpleGUI as sg
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
def launch_segmentation(image: np.ndarray, user_parameters: dict) :
|
|
19
|
+
"""
|
|
20
|
+
Ask user for necessary parameters and perform cell segmentation (cytoplasm + nucleus) with cellpose.
|
|
21
|
+
|
|
22
|
+
Input
|
|
23
|
+
-----
|
|
24
|
+
Image : np.ndarray[c,z,y,x]
|
|
25
|
+
Image to use for segmentation.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
cytoplasm_label, nucleus_label
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
while True : # Loop if show_segmentation
|
|
33
|
+
#Default parameters
|
|
34
|
+
cyto_model_name = user_parameters.setdefault('cyto_model_name', 'cyto2')
|
|
35
|
+
cyto_size = user_parameters.setdefault('cytoplasm diameter', 180)
|
|
36
|
+
cytoplasm_channel = user_parameters.setdefault('cytoplasm channel', 0)
|
|
37
|
+
nucleus_model_name = user_parameters.setdefault('nucleus_model_name', 'nuclei')
|
|
38
|
+
nucleus_size = user_parameters.setdefault('nucleus diameter', 130)
|
|
39
|
+
nucleus_channel = user_parameters.setdefault('nucleus channel', 0)
|
|
40
|
+
path = os.getcwd()
|
|
41
|
+
show_segmentation = user_parameters.setdefault('show segmentation', False)
|
|
42
|
+
segment_only_nuclei = user_parameters.setdefault('Segment only nuclei', False)
|
|
43
|
+
filename = user_parameters['filename']
|
|
44
|
+
available_channels = list(range(image.shape[0]))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
#Ask user for parameters
|
|
48
|
+
#if incorrect parameters --> set relaunch to True
|
|
49
|
+
while True :
|
|
50
|
+
layout = _segmentation_layout(
|
|
51
|
+
cytoplasm_model_preset = cyto_model_name,
|
|
52
|
+
cytoplasm_channel_preset= cytoplasm_channel,
|
|
53
|
+
nucleus_model_preset = nucleus_model_name,
|
|
54
|
+
nucleus_channel_preset= nucleus_channel,
|
|
55
|
+
cyto_diameter_preset= cyto_size,
|
|
56
|
+
nucleus_diameter_preset= nucleus_size,
|
|
57
|
+
saving_path_preset= path,
|
|
58
|
+
show_segmentation_preset=show_segmentation,
|
|
59
|
+
segment_only_nuclei_preset=segment_only_nuclei,
|
|
60
|
+
filename_preset=filename,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
event, values = prompt_with_help(layout, help='segmentation')
|
|
64
|
+
if event == 'Cancel' :
|
|
65
|
+
cancel_segmentation = ask_cancel_segmentation()
|
|
66
|
+
|
|
67
|
+
if cancel_segmentation :
|
|
68
|
+
return None, None, user_parameters
|
|
69
|
+
else :
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
#Extract parameters
|
|
73
|
+
values = _cast_segmentation_parameters(values)
|
|
74
|
+
do_only_nuc = values['Segment only nuclei']
|
|
75
|
+
cyto_model_name = values['cyto_model_name']
|
|
76
|
+
cyto_size = values['cytoplasm diameter']
|
|
77
|
+
cytoplasm_channel = values['cytoplasm channel']
|
|
78
|
+
nucleus_model_name = values['nucleus_model_name']
|
|
79
|
+
nucleus_size = values['nucleus diameter']
|
|
80
|
+
nucleus_channel = values['nucleus channel']
|
|
81
|
+
path = values['saving path'] if values['saving path'] != '' else None
|
|
82
|
+
show_segmentation = values['show segmentation']
|
|
83
|
+
filename = values['filename'] if type(path) != type(None) else None
|
|
84
|
+
channels = [cytoplasm_channel, nucleus_channel]
|
|
85
|
+
|
|
86
|
+
relaunch= False
|
|
87
|
+
#Checking integrity of parameters
|
|
88
|
+
if type(cyto_model_name) != str and not do_only_nuc:
|
|
89
|
+
sg.popup('Invalid cytoplasm model name.')
|
|
90
|
+
values['cyto_model_name'] = user_parameters.setdefault('cyto_model_name', 'cyto2')
|
|
91
|
+
relaunch= True
|
|
92
|
+
if cytoplasm_channel not in available_channels and not do_only_nuc:
|
|
93
|
+
sg.popup('For given input image please select channel in {0}\ncytoplasm channel : {1}'.format(available_channels, cytoplasm_channel))
|
|
94
|
+
relaunch= True
|
|
95
|
+
values['cytoplasm channel'] = user_parameters.setdefault('cytoplasm channel',0)
|
|
96
|
+
|
|
97
|
+
if type(cyto_size) not in [int, float] and not do_only_nuc:
|
|
98
|
+
sg.popup("Incorrect cytoplasm size.")
|
|
99
|
+
relaunch= True
|
|
100
|
+
values['cytoplasm diameter'] = user_parameters.setdefault('diameter', 30)
|
|
101
|
+
|
|
102
|
+
if type(nucleus_model_name) != str :
|
|
103
|
+
sg.popup('Invalid nucleus model name.')
|
|
104
|
+
values['nucleus_model_name'] = user_parameters.setdefault('nucleus_model_name', 'nuclei')
|
|
105
|
+
relaunch= True
|
|
106
|
+
if nucleus_channel not in available_channels :
|
|
107
|
+
sg.popup('For given input image please select channel in {0}\nnucleus channel : {1}'.format(available_channels, nucleus_channel))
|
|
108
|
+
relaunch= True
|
|
109
|
+
values['nucleus channel'] = user_parameters.setdefault('nucleus_channel', 0)
|
|
110
|
+
if type(nucleus_size) not in [int, float] :
|
|
111
|
+
sg.popup("Incorrect nucleus size.")
|
|
112
|
+
relaunch= True
|
|
113
|
+
values['nucleus diameter'] = user_parameters.setdefault('nucleus diameter', 30)
|
|
114
|
+
|
|
115
|
+
user_parameters.update(values)
|
|
116
|
+
if not relaunch : break
|
|
117
|
+
|
|
118
|
+
#Launching segmentation
|
|
119
|
+
waiting_layout = [
|
|
120
|
+
[sg.Text("Running segmentation...")]
|
|
121
|
+
]
|
|
122
|
+
window = sg.Window(
|
|
123
|
+
title= 'small_fish',
|
|
124
|
+
layout= waiting_layout,
|
|
125
|
+
grab_anywhere= True,
|
|
126
|
+
no_titlebar= False
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
window.read(timeout= 30, close= False)
|
|
130
|
+
|
|
131
|
+
try :
|
|
132
|
+
if type(path) != type(None) and filename != '':
|
|
133
|
+
output_path = path + '/' + filename
|
|
134
|
+
nuc_path = output_path + "_nucleus_segmentation"
|
|
135
|
+
cyto_path = output_path + "_cytoplasm_segmentation"
|
|
136
|
+
else :
|
|
137
|
+
output_path = None
|
|
138
|
+
nuc_path = None
|
|
139
|
+
cyto_path = None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
cytoplasm_label, nucleus_label = cell_segmentation(
|
|
143
|
+
image,
|
|
144
|
+
cyto_model_name= cyto_model_name,
|
|
145
|
+
cyto_diameter= cyto_size,
|
|
146
|
+
nucleus_model_name= nucleus_model_name,
|
|
147
|
+
nucleus_diameter= nucleus_size,
|
|
148
|
+
channels=channels,
|
|
149
|
+
do_only_nuc=do_only_nuc
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
finally : window.close()
|
|
153
|
+
if show_segmentation or type(output_path) != type(None) :
|
|
154
|
+
nuc_proj = image[nucleus_channel]
|
|
155
|
+
im_proj = image[cytoplasm_channel]
|
|
156
|
+
if im_proj.ndim == 3 :
|
|
157
|
+
im_proj = stack.maximum_projection(im_proj)
|
|
158
|
+
if nuc_proj.ndim == 3 :
|
|
159
|
+
nuc_proj = stack.maximum_projection(nuc_proj)
|
|
160
|
+
plot.plot_segmentation_boundary(nuc_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=show_segmentation, path_output=None, title= "Nucleus segmentation (blue)", remove_frame=False,)
|
|
161
|
+
if type(nuc_path) != type(None) : plot.plot_segmentation_boundary(nuc_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=False, path_output=nuc_path, title= "Nucleus segmentation (blue)", remove_frame=True,)
|
|
162
|
+
if not do_only_nuc :
|
|
163
|
+
plot.plot_segmentation_boundary(im_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=show_segmentation, path_output=cyto_path, title="Cytoplasm Segmentation (red)", remove_frame=False)
|
|
164
|
+
if type(cyto_path) != type(None) : plot.plot_segmentation_boundary(im_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=False, path_output=cyto_path, title="Cytoplasm Segmentation (red)", remove_frame=True)
|
|
165
|
+
if show_segmentation :
|
|
166
|
+
layout = [
|
|
167
|
+
[sg.Text("Proceed with current segmentation ?")],
|
|
168
|
+
[sg.Button("Yes"), sg.Button("No")]
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
event, values = prompt(layout=layout, add_ok_cancel=False)
|
|
172
|
+
if event == "No" :
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
if cytoplasm_label.max() == 0 : #No cell segmented
|
|
176
|
+
layout = [
|
|
177
|
+
[sg.Text("No cell segmented. Proceed anyway ?")],
|
|
178
|
+
[sg.Button("Yes"), sg.Button("No")]
|
|
179
|
+
]
|
|
180
|
+
event, values = prompt(layout=layout, add_ok_cancel=False)
|
|
181
|
+
if event == "Yes" :
|
|
182
|
+
return None, None, user_parameters
|
|
183
|
+
else :
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
user_parameters.update(values)
|
|
189
|
+
return cytoplasm_label, nucleus_label, user_parameters
|
|
190
|
+
|
|
191
|
+
def cell_segmentation(image, cyto_model_name, nucleus_model_name, channels, cyto_diameter, nucleus_diameter, do_only_nuc=False) :
|
|
192
|
+
|
|
193
|
+
nuc_channel = channels[1]
|
|
194
|
+
if not do_only_nuc :
|
|
195
|
+
cyto_channel = channels[0]
|
|
196
|
+
if image[cyto_channel].ndim >= 3 :
|
|
197
|
+
cyto = stack.maximum_projection(image[cyto_channel])
|
|
198
|
+
else :
|
|
199
|
+
cyto = image[cyto_channel]
|
|
200
|
+
|
|
201
|
+
if image[nuc_channel].ndim >= 3 :
|
|
202
|
+
nuc = stack.maximum_projection(image[nuc_channel])
|
|
203
|
+
else :
|
|
204
|
+
nuc = image[nuc_channel]
|
|
205
|
+
|
|
206
|
+
if not do_only_nuc :
|
|
207
|
+
image = np.zeros(shape=(2,) + cyto.shape)
|
|
208
|
+
image[0] = cyto
|
|
209
|
+
image[1] = nuc
|
|
210
|
+
image = np.moveaxis(image, source=(0,1,2), destination=(2,0,1))
|
|
211
|
+
|
|
212
|
+
nuc_label = _segmentate_object(nuc, nucleus_model_name, nucleus_diameter, [0,0])
|
|
213
|
+
if not do_only_nuc :
|
|
214
|
+
cytoplasm_label = _segmentate_object(image, cyto_model_name, cyto_diameter, [1,2])
|
|
215
|
+
nuc_label, cytoplasm_label = multistack.match_nuc_cell(nuc_label=nuc_label, cell_label=cytoplasm_label, single_nuc=True, cell_alone=False)
|
|
216
|
+
else :
|
|
217
|
+
cytoplasm_label = nuc_label
|
|
218
|
+
|
|
219
|
+
return cytoplasm_label, nuc_label
|
|
220
|
+
|
|
221
|
+
def _segmentate_object(im, model_name, object_size_px, channels = [0,0]) :
|
|
222
|
+
|
|
223
|
+
model = models.CellposeModel(
|
|
224
|
+
gpu= use_gpu(),
|
|
225
|
+
model_type= model_name,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
label = model.eval(
|
|
229
|
+
im,
|
|
230
|
+
diameter= object_size_px,
|
|
231
|
+
channels= channels,
|
|
232
|
+
do_3D= False,
|
|
233
|
+
)[0]
|
|
234
|
+
label = np.array(label, dtype= np.int64)
|
|
235
|
+
label = remove_disjoint(label)
|
|
236
|
+
|
|
237
|
+
return label
|
|
238
|
+
|
|
239
|
+
def _cast_segmentation_parameters(values) :
|
|
240
|
+
|
|
241
|
+
if values['cyto_model_name'] == '' :
|
|
242
|
+
values['cyto_model_name'] = None
|
|
243
|
+
|
|
244
|
+
if values['nucleus_model_name'] == '' :
|
|
245
|
+
values['nucleus_model_name'] = None
|
|
246
|
+
|
|
247
|
+
try : #cytoplasm channel
|
|
248
|
+
values['cytoplasm channel'] = int(values['cytoplasm channel'])
|
|
249
|
+
except ValueError :
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
try : #nucleus channel
|
|
253
|
+
values['nucleus channel'] = int(values['nucleus channel'])
|
|
254
|
+
except ValueError :
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
try : #object size
|
|
258
|
+
values['cytoplasm diameter'] = float(values['cytoplasm diameter'])
|
|
259
|
+
except ValueError :
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
try : #object size
|
|
263
|
+
values['nucleus diameter'] = float(values['nucleus diameter'])
|
|
264
|
+
except ValueError :
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
return values
|
|
268
|
+
|
|
269
|
+
def remove_disjoint(image):
|
|
270
|
+
"""
|
|
271
|
+
*CODE FROM BIG-FISH (LICENCE IN __INIT__.PY) IMPORTED TO AVOID IMPORT ERROR WHEN BIGFISH SEGMENTATION MODULE INITIALISES : try to import deprecated module for python 3.8
|
|
272
|
+
|
|
273
|
+
For each instances with disconnected parts, keep the larger one.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
image : np.ndarray, np.int, np.uint or bool
|
|
278
|
+
Labelled image with shape (z, y, x) or (y, x).
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
image_cleaned : np.ndarray, np.int or np.uint
|
|
283
|
+
Cleaned image with shape (z, y, x) or (y, x).
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
# check parameters
|
|
287
|
+
stack.check_array(
|
|
288
|
+
image,
|
|
289
|
+
ndim=[2, 3],
|
|
290
|
+
dtype=[np.uint8, np.uint16, np.int32, np.int64, bool])
|
|
291
|
+
|
|
292
|
+
# handle boolean array
|
|
293
|
+
cast_to_bool = False
|
|
294
|
+
if image.dtype == bool:
|
|
295
|
+
cast_to_bool = bool
|
|
296
|
+
image = image.astype(np.uint8)
|
|
297
|
+
|
|
298
|
+
# initialize cleaned labels
|
|
299
|
+
image_cleaned = np.zeros_like(image)
|
|
300
|
+
|
|
301
|
+
# loop over instances
|
|
302
|
+
max_label = image.max()
|
|
303
|
+
for i in range(1, max_label + 1):
|
|
304
|
+
|
|
305
|
+
# get instance mask
|
|
306
|
+
mask = image == i
|
|
307
|
+
|
|
308
|
+
# check if an instance is labelled with this value
|
|
309
|
+
if mask.sum() == 0:
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
# get an index for each disconnected part of the instance
|
|
313
|
+
labelled_mask = label(mask)
|
|
314
|
+
indices = sorted(list(set(labelled_mask.ravel())))
|
|
315
|
+
if 0 in indices:
|
|
316
|
+
indices = indices[1:]
|
|
317
|
+
|
|
318
|
+
# keep the largest part of the instance
|
|
319
|
+
max_area = 0
|
|
320
|
+
mask_instance = None
|
|
321
|
+
for j in indices:
|
|
322
|
+
mask_part_j = labelled_mask == j
|
|
323
|
+
area_j = mask_part_j.sum()
|
|
324
|
+
if area_j > max_area:
|
|
325
|
+
max_area = area_j
|
|
326
|
+
mask_instance = mask_part_j
|
|
327
|
+
|
|
328
|
+
# add instance in the final label
|
|
329
|
+
image_cleaned[mask_instance] = i
|
|
330
|
+
|
|
331
|
+
if cast_to_bool:
|
|
332
|
+
image_cleaned = image_cleaned.astype(bool)
|
|
333
|
+
|
|
334
|
+
return image_cleaned
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Signal to noise wrapper from BigFish code.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# ### SNR ###
|
|
7
|
+
import bigfish.stack as stack
|
|
8
|
+
import numpy as np
|
|
9
|
+
from bigfish.detection.utils import get_object_radius_pixel, get_spot_volume, get_spot_surface
|
|
10
|
+
|
|
11
|
+
def compute_snr_spots(image, spots, voxel_size, spot_radius):
|
|
12
|
+
"""
|
|
13
|
+
Modified version of bigfish.detection.utils compute_snr_spots :
|
|
14
|
+
# Author: Arthur Imbert <arthur.imbert.pro@gmail.com>
|
|
15
|
+
# License: BSD 3 clause
|
|
16
|
+
|
|
17
|
+
Compute signal-to-noise ratio (SNR) based on spot coordinates.
|
|
18
|
+
|
|
19
|
+
.. math::
|
|
20
|
+
|
|
21
|
+
\\mbox{SNR} = \\frac{\\mbox{max(spot signal)} -
|
|
22
|
+
\\mbox{mean(background)}}{\\mbox{std(background)}}
|
|
23
|
+
|
|
24
|
+
Background is a region twice larger surrounding the spot region. Only the
|
|
25
|
+
y and x dimensions are taking into account to compute the SNR.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
image : np.ndarray
|
|
30
|
+
Image with shape (z, y, x) or (y, x).
|
|
31
|
+
spots : np.ndarray
|
|
32
|
+
Coordinate of the spots, with shape (nb_spots, 3) or (nb_spots, 2).
|
|
33
|
+
One coordinate per dimension (zyx or yx coordinates).
|
|
34
|
+
voxel_size : int, float, Tuple(int, float), List(int, float) or None
|
|
35
|
+
Size of a voxel, in nanometer. One value per spatial dimension (zyx or
|
|
36
|
+
yx dimensions). If it's a scalar, the same value is applied to every
|
|
37
|
+
dimensions. Not used if 'log_kernel_size' and 'minimum_distance' are
|
|
38
|
+
provided.
|
|
39
|
+
spot_radius : int, float, Tuple(int, float), List(int, float) or None
|
|
40
|
+
Radius of the spot, in nanometer. One value per spatial dimension (zyx
|
|
41
|
+
or yx dimensions). If it's a scalar, the same radius is applied to
|
|
42
|
+
every dimensions. Not used if 'log_kernel_size' and 'minimum_distance'
|
|
43
|
+
are provided.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
res : dict
|
|
48
|
+
Median signal-to-noise ratio computed for every spots.
|
|
49
|
+
+
|
|
50
|
+
'snr_median' : np.median(snr_spots),
|
|
51
|
+
'snr_mean' : np.mean(snr_spots),
|
|
52
|
+
'snr_std' : np.std(snr_spots),
|
|
53
|
+
'cell_medianbackground_mean' : np.mean(median_background_list),
|
|
54
|
+
'cell_medianbackground_std' : np.std(median_background_list),
|
|
55
|
+
'cell_meanbackground_mean' : np.mean(mean_background_list),
|
|
56
|
+
'cell_meanbackground_std' : np.std(mean_background_list),
|
|
57
|
+
'cell_stdbackground_mean' : np.mean(std_background_list),
|
|
58
|
+
'cell_stdbackground_std' : np.std(std_background_list)
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
# check parameters
|
|
62
|
+
stack.check_array(
|
|
63
|
+
image,
|
|
64
|
+
ndim=[2, 3],
|
|
65
|
+
dtype=[np.uint8, np.uint16, np.float32, np.float64])
|
|
66
|
+
stack.check_range_value(image, min_=0)
|
|
67
|
+
stack.check_array(
|
|
68
|
+
spots,
|
|
69
|
+
ndim=2,
|
|
70
|
+
dtype=[np.float32, np.float64, np.int32, np.int64])
|
|
71
|
+
stack.check_parameter(
|
|
72
|
+
voxel_size=(int, float, tuple, list),
|
|
73
|
+
spot_radius=(int, float, tuple, list))
|
|
74
|
+
|
|
75
|
+
# check consistency between parameters
|
|
76
|
+
ndim = image.ndim
|
|
77
|
+
if ndim != spots.shape[1]:
|
|
78
|
+
raise ValueError("Provided image has {0} dimensions but spots are "
|
|
79
|
+
"detected in {1} dimensions."
|
|
80
|
+
.format(ndim, spots.shape[1]))
|
|
81
|
+
if isinstance(voxel_size, (tuple, list)):
|
|
82
|
+
if len(voxel_size) != ndim:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"'voxel_size' must be a scalar or a sequence with {0} "
|
|
85
|
+
"elements.".format(ndim))
|
|
86
|
+
else:
|
|
87
|
+
voxel_size = (voxel_size,) * ndim
|
|
88
|
+
if isinstance(spot_radius, (tuple, list)):
|
|
89
|
+
if len(spot_radius) != ndim:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
"'spot_radius' must be a scalar or a sequence with {0} "
|
|
92
|
+
"elements.".format(ndim))
|
|
93
|
+
else:
|
|
94
|
+
spot_radius = (spot_radius,) * ndim
|
|
95
|
+
|
|
96
|
+
# cast spots coordinates if needed
|
|
97
|
+
if spots.dtype == np.float64:
|
|
98
|
+
spots = np.round(spots).astype(np.int64)
|
|
99
|
+
|
|
100
|
+
# cast image if needed
|
|
101
|
+
image_to_process = image.copy().astype(np.float64)
|
|
102
|
+
|
|
103
|
+
# clip coordinate if needed
|
|
104
|
+
if ndim == 3:
|
|
105
|
+
spots[:, 0] = np.clip(spots[:, 0], 0, image_to_process.shape[0] - 1)
|
|
106
|
+
spots[:, 1] = np.clip(spots[:, 1], 0, image_to_process.shape[1] - 1)
|
|
107
|
+
spots[:, 2] = np.clip(spots[:, 2], 0, image_to_process.shape[2] - 1)
|
|
108
|
+
else:
|
|
109
|
+
spots[:, 0] = np.clip(spots[:, 0], 0, image_to_process.shape[0] - 1)
|
|
110
|
+
spots[:, 1] = np.clip(spots[:, 1], 0, image_to_process.shape[1] - 1)
|
|
111
|
+
|
|
112
|
+
# compute radius used to crop spot image
|
|
113
|
+
radius_pixel = get_object_radius_pixel(
|
|
114
|
+
voxel_size_nm=voxel_size,
|
|
115
|
+
object_radius_nm=spot_radius,
|
|
116
|
+
ndim=ndim)
|
|
117
|
+
radius_signal_ = [np.sqrt(ndim) * r for r in radius_pixel]
|
|
118
|
+
radius_signal_ = tuple(radius_signal_)
|
|
119
|
+
|
|
120
|
+
# compute the neighbourhood radius
|
|
121
|
+
radius_background_ = tuple(i * 2 for i in radius_signal_)
|
|
122
|
+
|
|
123
|
+
# ceil radii
|
|
124
|
+
radius_signal = np.ceil(radius_signal_).astype(int)
|
|
125
|
+
radius_background = np.ceil(radius_background_).astype(int)
|
|
126
|
+
|
|
127
|
+
# loop over spots
|
|
128
|
+
snr_spots = []
|
|
129
|
+
median_background_list = []
|
|
130
|
+
mean_background_list = []
|
|
131
|
+
std_background_list = []
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
for spot in spots:
|
|
135
|
+
|
|
136
|
+
# extract spot images
|
|
137
|
+
spot_y = spot[ndim - 2]
|
|
138
|
+
spot_x = spot[ndim - 1]
|
|
139
|
+
radius_signal_yx = radius_signal[-1]
|
|
140
|
+
radius_background_yx = radius_background[-1]
|
|
141
|
+
edge_background_yx = radius_background_yx - radius_signal_yx
|
|
142
|
+
if ndim == 3:
|
|
143
|
+
spot_z = spot[0]
|
|
144
|
+
radius_background_z = radius_background[0]
|
|
145
|
+
max_signal = image_to_process[spot_z, spot_y, spot_x]
|
|
146
|
+
spot_background_, _ = get_spot_volume(
|
|
147
|
+
image_to_process, spot_z, spot_y, spot_x,
|
|
148
|
+
radius_background_z, radius_background_yx)
|
|
149
|
+
spot_background = spot_background_.copy()
|
|
150
|
+
|
|
151
|
+
# discard spot if cropped at the border (along y and x dimensions)
|
|
152
|
+
expected_size = (2 * radius_background_yx + 1) ** 2
|
|
153
|
+
actual_size = spot_background.shape[1] * spot_background.shape[2]
|
|
154
|
+
if expected_size != actual_size:
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
# remove signal from background crop
|
|
158
|
+
spot_background[:,
|
|
159
|
+
edge_background_yx:-edge_background_yx,
|
|
160
|
+
edge_background_yx:-edge_background_yx] = -1
|
|
161
|
+
spot_background = spot_background[spot_background >= 0]
|
|
162
|
+
|
|
163
|
+
else:
|
|
164
|
+
max_signal = image_to_process[spot_y, spot_x]
|
|
165
|
+
spot_background_, _ = get_spot_surface(
|
|
166
|
+
image_to_process, spot_y, spot_x, radius_background_yx)
|
|
167
|
+
spot_background = spot_background_.copy()
|
|
168
|
+
|
|
169
|
+
# discard spot if cropped at the border
|
|
170
|
+
expected_size = (2 * radius_background_yx + 1) ** 2
|
|
171
|
+
if expected_size != spot_background.size:
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
# remove signal from background crop
|
|
175
|
+
spot_background[edge_background_yx:-edge_background_yx,
|
|
176
|
+
edge_background_yx:-edge_background_yx] = -1
|
|
177
|
+
spot_background = spot_background[spot_background >= 0]
|
|
178
|
+
|
|
179
|
+
# compute mean background
|
|
180
|
+
median_background = np.median(spot_background)
|
|
181
|
+
mean_background = np.mean(spot_background)
|
|
182
|
+
|
|
183
|
+
# compute standard deviation background
|
|
184
|
+
std_background = np.std(spot_background)
|
|
185
|
+
|
|
186
|
+
# compute SNR
|
|
187
|
+
snr = (max_signal - mean_background) / std_background
|
|
188
|
+
snr_spots.append(snr)
|
|
189
|
+
median_background_list.append(median_background)
|
|
190
|
+
mean_background_list.append(mean_background)
|
|
191
|
+
std_background_list.append(std_background)
|
|
192
|
+
|
|
193
|
+
# average SNR
|
|
194
|
+
if len(snr_spots) == 0:
|
|
195
|
+
res = {
|
|
196
|
+
'snr_median' : 0,
|
|
197
|
+
'snr_mean' : 0,
|
|
198
|
+
'snr_std' : 0,
|
|
199
|
+
'cell_medianbackground_mean' : 0,
|
|
200
|
+
'cell_medianbackground_std' : 0,
|
|
201
|
+
'cell_meanbackground_mean' : 0,
|
|
202
|
+
'cell_meanbackground_std' : 0,
|
|
203
|
+
'cell_stdbackground_mean' : 0,
|
|
204
|
+
'cell_stdbackground_std' : 0
|
|
205
|
+
}
|
|
206
|
+
else:
|
|
207
|
+
res = {
|
|
208
|
+
'snr_median' : np.median(snr_spots),
|
|
209
|
+
'snr_mean' : np.mean(snr_spots),
|
|
210
|
+
'snr_std' : np.std(snr_spots),
|
|
211
|
+
'cell_medianbackground_mean' : np.mean(median_background_list),
|
|
212
|
+
'cell_medianbackground_std' : np.std(median_background_list),
|
|
213
|
+
'cell_meanbackground_mean' : np.mean(mean_background_list),
|
|
214
|
+
'cell_meanbackground_std' : np.std(mean_background_list),
|
|
215
|
+
'cell_stdbackground_mean' : np.mean(std_background_list),
|
|
216
|
+
'cell_stdbackground_std' : np.std(std_background_list)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return res
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from ..gui.prompts import output_image_prompt, ask_detection_confirmation, ask_cancel_detection
|
|
2
|
+
from ..interface.output import write_results
|
|
3
|
+
from ._preprocess import map_channels, prepare_image_detection, reorder_shape, reorder_image_stack
|
|
4
|
+
from .detection import ask_input_parameters, initiate_detection, launch_detection, launch_features_computation, get_nucleus_signal
|
|
5
|
+
from ._segmentation import launch_segmentation
|
|
6
|
+
from ._colocalisation import initiate_colocalisation, launch_colocalisation
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import PySimpleGUI as sg
|
|
10
|
+
|
|
11
|
+
def add_detection(user_parameters, segmentation_done, acquisition_id, cytoplasm_label, nucleus_label) :
|
|
12
|
+
"""
|
|
13
|
+
#TODO : list all keys added to user_parameters when returned
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
new_results_df = pd.DataFrame()
|
|
17
|
+
new_cell_results_df = pd.DataFrame()
|
|
18
|
+
|
|
19
|
+
#Ask for image parameters
|
|
20
|
+
new_parameters = ask_input_parameters(ask_for_segmentation= not segmentation_done) #The image is open and stored inside user_parameters
|
|
21
|
+
if type(new_parameters) == type(None) : #if user clicks 'Cancel'
|
|
22
|
+
return new_results_df, new_cell_results_df, acquisition_id, user_parameters, segmentation_done,cytoplasm_label, nucleus_label
|
|
23
|
+
else :
|
|
24
|
+
user_parameters.update(new_parameters)
|
|
25
|
+
|
|
26
|
+
map = map_channels(user_parameters)
|
|
27
|
+
user_parameters['reordered_shape'] = reorder_shape(user_parameters['shape'], map)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
#Segmentation
|
|
31
|
+
if user_parameters['Segmentation'] and not segmentation_done:
|
|
32
|
+
im_seg = reorder_image_stack(map, user_parameters)
|
|
33
|
+
cytoplasm_label, nucleus_label, user_parameters = launch_segmentation(im_seg, user_parameters=user_parameters)
|
|
34
|
+
|
|
35
|
+
else :
|
|
36
|
+
cytoplasm_label, nucleus_label = None,None
|
|
37
|
+
|
|
38
|
+
if type(cytoplasm_label) == type(None) or type(nucleus_label) == type(None) :
|
|
39
|
+
user_parameters['Segmentation'] = False
|
|
40
|
+
segmentation_done = False
|
|
41
|
+
|
|
42
|
+
else : segmentation_done = True
|
|
43
|
+
|
|
44
|
+
#Detection
|
|
45
|
+
while True : # This loop allow user to try detection with different thresholds or parameters before launching features computation
|
|
46
|
+
detection_parameters = initiate_detection(
|
|
47
|
+
user_parameters,
|
|
48
|
+
segmentation_done,
|
|
49
|
+
map= map,
|
|
50
|
+
shape = user_parameters['image'].shape
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if type(detection_parameters) != type(None) :
|
|
54
|
+
user_parameters.update(detection_parameters)
|
|
55
|
+
else : #If user clicks cancel
|
|
56
|
+
cancel = ask_cancel_detection()
|
|
57
|
+
if cancel :
|
|
58
|
+
return new_results_df, new_cell_results_df, acquisition_id, user_parameters, segmentation_done, cytoplasm_label, nucleus_label
|
|
59
|
+
else : continue
|
|
60
|
+
|
|
61
|
+
acquisition_id += 1
|
|
62
|
+
image, other_image = prepare_image_detection(map, user_parameters)
|
|
63
|
+
nucleus_signal = get_nucleus_signal(image, other_image, user_parameters)
|
|
64
|
+
|
|
65
|
+
try : # Catch error raised if user enter a spot size too small compare to voxel size
|
|
66
|
+
user_parameters, frame_result, spots, clusters = launch_detection(
|
|
67
|
+
image,
|
|
68
|
+
other_image,
|
|
69
|
+
user_parameters,
|
|
70
|
+
cell_label=cytoplasm_label,
|
|
71
|
+
nucleus_label=nucleus_label
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
except ValueError as error :
|
|
75
|
+
if "The array should have an upper bound of 1" in str(error) :
|
|
76
|
+
sg.popup("Spot size too small for current voxel size.")
|
|
77
|
+
continue
|
|
78
|
+
else :
|
|
79
|
+
raise(error)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if user_parameters['Napari correction'] :
|
|
83
|
+
if ask_detection_confirmation(user_parameters.get('threshold')) : break
|
|
84
|
+
else :
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
#Features computation
|
|
88
|
+
new_results_df, new_cell_results_df = launch_features_computation(
|
|
89
|
+
acquisition_id=acquisition_id,
|
|
90
|
+
image=image,
|
|
91
|
+
nucleus_signal = nucleus_signal,
|
|
92
|
+
spots=spots,
|
|
93
|
+
clusters=clusters,
|
|
94
|
+
nucleus_label = nucleus_label,
|
|
95
|
+
cell_label= cytoplasm_label,
|
|
96
|
+
user_parameters=user_parameters,
|
|
97
|
+
frame_results=frame_result,
|
|
98
|
+
)
|
|
99
|
+
return new_results_df, new_cell_results_df, acquisition_id, user_parameters, segmentation_done, cytoplasm_label, nucleus_label
|
|
100
|
+
|
|
101
|
+
def save_results(result_df, cell_result_df, coloc_df) :
|
|
102
|
+
if len(result_df) != 0 :
|
|
103
|
+
dic = output_image_prompt(filename=result_df.iloc[0].at['filename'])
|
|
104
|
+
|
|
105
|
+
if isinstance(dic, dict) :
|
|
106
|
+
path = dic['folder']
|
|
107
|
+
filename = dic['filename']
|
|
108
|
+
do_excel = dic['Excel']
|
|
109
|
+
do_feather = dic['Feather']
|
|
110
|
+
sucess1 = write_results(result_df, path= path, filename=filename, do_excel= do_excel, do_feather= do_feather)
|
|
111
|
+
sucess2 = write_results(cell_result_df, path= path, filename=filename + '_cell_result', do_excel= do_excel, do_feather= do_feather)
|
|
112
|
+
sucess3 = write_results(coloc_df, path= path, filename=filename + '_coloc_result', do_excel= do_excel, do_feather= do_feather)
|
|
113
|
+
if sucess1 and sucess2 and sucess3 : sg.popup("Sucessfully saved at {0}.".format(path))
|
|
114
|
+
|
|
115
|
+
else :
|
|
116
|
+
dic = None
|
|
117
|
+
sg.popup('No results to save.')
|
|
118
|
+
|
|
119
|
+
def compute_colocalisation(result_tables, result_dataframe) :
|
|
120
|
+
colocalisation_distance = initiate_colocalisation(result_tables)
|
|
121
|
+
|
|
122
|
+
if colocalisation_distance == False :
|
|
123
|
+
res_coloc = pd.DataFrame() # popup handled in initiate_colocalisation
|
|
124
|
+
else :
|
|
125
|
+
res_coloc = launch_colocalisation(result_tables, result_dataframe=result_dataframe, colocalisation_distance=colocalisation_distance)
|
|
126
|
+
|
|
127
|
+
return res_coloc
|