small-fish-gui 2.0.2__py3-none-any.whl → 2.0.3__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 +2 -2
- small_fish_gui/batch/integrity.py +2 -2
- small_fish_gui/batch/pipeline.py +46 -11
- small_fish_gui/batch/prompt.py +102 -41
- small_fish_gui/batch/update.py +26 -13
- small_fish_gui/batch/utils.py +1 -1
- small_fish_gui/gui/__init__.py +1 -0
- small_fish_gui/gui/_napari_widgets.py +418 -6
- small_fish_gui/gui/layout.py +332 -112
- small_fish_gui/gui/napari_visualiser.py +107 -22
- small_fish_gui/gui/prompts.py +161 -48
- small_fish_gui/gui/testing.ipynb +231 -24
- small_fish_gui/gui/tooltips.py +7 -1
- small_fish_gui/hints.py +23 -7
- small_fish_gui/interface/__init__.py +7 -1
- small_fish_gui/interface/default_settings.py +118 -0
- small_fish_gui/interface/image.py +43 -11
- small_fish_gui/interface/settings.json +50 -0
- small_fish_gui/interface/testing.ipynb +4354 -0
- small_fish_gui/interface/user_settings.py +96 -0
- small_fish_gui/main_menu.py +13 -1
- small_fish_gui/pipeline/{_signaltonoise.py → _bigfish_wrapers.py} +59 -7
- small_fish_gui/pipeline/_colocalisation.py +23 -24
- small_fish_gui/pipeline/_preprocess.py +46 -32
- small_fish_gui/pipeline/actions.py +48 -5
- small_fish_gui/pipeline/detection.py +71 -141
- small_fish_gui/pipeline/segmentation.py +360 -268
- small_fish_gui/pipeline/spots.py +3 -3
- small_fish_gui/pipeline/utils.py +5 -1
- small_fish_gui/README.md → small_fish_gui-2.0.3.dist-info/METADATA +35 -0
- small_fish_gui-2.0.3.dist-info/RECORD +46 -0
- {small_fish_gui-2.0.2.dist-info → small_fish_gui-2.0.3.dist-info}/WHEEL +1 -1
- small_fish_gui/.github/workflows/python-publish.yml +0 -39
- small_fish_gui/LICENSE +0 -24
- small_fish_gui/batch/values.txt +0 -65
- small_fish_gui/default_values.py +0 -51
- small_fish_gui/gui/screenshot/general_help_screenshot.png +0 -0
- small_fish_gui/gui/screenshot/mapping_help_screenshot.png +0 -0
- small_fish_gui/gui/screenshot/segmentation_help_screenshot.png +0 -0
- small_fish_gui/illustrations/DetectionVitrine_filtre.png +0 -0
- small_fish_gui/illustrations/DetectionVitrine_signal.png +0 -0
- small_fish_gui/illustrations/FocciVitrine.png +0 -0
- small_fish_gui/illustrations/FocciVitrine_no_spots.png +0 -0
- small_fish_gui/illustrations/Segmentation2D.png +0 -0
- small_fish_gui/illustrations/Segmentation2D_with_labels.png +0 -0
- small_fish_gui/logo.png +0 -0
- small_fish_gui/pipeline/testing.ipynb +0 -3636
- small_fish_gui/requirements.txt +0 -19
- small_fish_gui-2.0.2.dist-info/METADATA +0 -75
- small_fish_gui-2.0.2.dist-info/RECORD +0 -59
- {small_fish_gui-2.0.2.dist-info → small_fish_gui-2.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,9 +8,12 @@ import napari
|
|
|
8
8
|
from magicgui import widgets
|
|
9
9
|
|
|
10
10
|
from bigfish.stack import check_parameter
|
|
11
|
+
from napari.layers import Image, Points
|
|
11
12
|
from ._napari_widgets import CellLabelEraser, SegmentationReseter, ChangesPropagater, FreeLabelPicker
|
|
12
13
|
from ._napari_widgets import ClusterIDSetter, ClusterMerger, ClusterUpdater, ClusterCreator
|
|
13
14
|
from ._napari_widgets import initialize_all_cluster_wizards
|
|
15
|
+
from ._napari_widgets import SpotDetector, DenseRegionDeconvolver, BackgroundRemover
|
|
16
|
+
from ._napari_widgets import NapariWidget
|
|
14
17
|
from ..utils import compute_anisotropy_coef
|
|
15
18
|
|
|
16
19
|
def correct_spots(
|
|
@@ -90,7 +93,7 @@ def correct_spots(
|
|
|
90
93
|
"cluster_id" : clusters[:,dim+1],
|
|
91
94
|
"end" : [True] * len(clusters_coordinates)
|
|
92
95
|
},
|
|
93
|
-
feature_defaults= {"spot_number" : 0, "cluster_id" : -2, "end" : True} # napari features default will not work with np.
|
|
96
|
+
feature_defaults= {"spot_number" : 0, "cluster_id" : -2, "end" : True} # napari features default will not work with np.nan passing -2 instead.
|
|
94
97
|
)
|
|
95
98
|
|
|
96
99
|
if type(cell_label) != type(None) and not np.array_equal(nucleus_label, cell_label) : Viewer.add_labels(cell_label, scale=scale, opacity= 0.2, blending= 'additive')
|
|
@@ -160,7 +163,7 @@ def show_segmentation(
|
|
|
160
163
|
cyto_image : np.ndarray = None,
|
|
161
164
|
cyto_label : np.ndarray = None,
|
|
162
165
|
anisotrpy : float = 1,
|
|
163
|
-
) :
|
|
166
|
+
) :
|
|
164
167
|
dim = nuc_image.ndim
|
|
165
168
|
|
|
166
169
|
if type(cyto_image) != type(None) :
|
|
@@ -226,44 +229,126 @@ def show_segmentation(
|
|
|
226
229
|
|
|
227
230
|
return new_nuc_label, new_cyto_label
|
|
228
231
|
|
|
229
|
-
def
|
|
232
|
+
def interactive_detection(
|
|
230
233
|
image : np.ndarray,
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
interactive_threshold : bool,
|
|
235
|
+
do_background_removal : bool,
|
|
236
|
+
dense_region_deconvolution : bool,
|
|
233
237
|
voxel_size : tuple,
|
|
238
|
+
**kwargs
|
|
234
239
|
) :
|
|
235
240
|
|
|
236
241
|
"""
|
|
237
242
|
To view code for spot selection have a look at magicgui instance created with `detection._create_threshold_slider` which is then passed to this napari wrapper as 'threshold_slider' argument.
|
|
243
|
+
|
|
244
|
+
note : spots,threshold
|
|
238
245
|
"""
|
|
239
246
|
|
|
247
|
+
assert any([interactive_threshold, do_background_removal, dense_region_deconvolution]), "Wrong code path"
|
|
248
|
+
|
|
249
|
+
print("Preparing Napari viewer...")
|
|
240
250
|
Viewer = napari.Viewer(title= "Small fish - Threshold selector", ndisplay=2, show=True)
|
|
241
251
|
scale = compute_anisotropy_coef(voxel_size)
|
|
242
|
-
Viewer.add_image(
|
|
252
|
+
image_layer = Viewer.add_image(
|
|
243
253
|
data= image,
|
|
244
|
-
contrast_limits= [image.min(), image.max()],
|
|
245
254
|
name= "raw signal",
|
|
246
255
|
colormap= 'green',
|
|
247
256
|
scale= scale,
|
|
248
257
|
blending= 'additive'
|
|
249
258
|
)
|
|
250
|
-
Viewer.add_image(
|
|
251
|
-
data= filtered_image,
|
|
252
|
-
contrast_limits= [filtered_image.min(), filtered_image.max()],
|
|
253
|
-
colormap= 'gray',
|
|
254
|
-
scale=scale,
|
|
255
|
-
blending='additive'
|
|
256
|
-
)
|
|
257
259
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
#Background remover
|
|
261
|
+
background_remover = _interactive_background_removal(image, voxel_size, **kwargs)
|
|
262
|
+
background_widgets = widgets.Container(widgets=[background_remover.widget, background_remover.reset_widget], labels=False)
|
|
263
|
+
Viewer.window.add_dock_widget(background_widgets, name='background_remover')
|
|
264
|
+
|
|
265
|
+
#Spot detection
|
|
266
|
+
spot_detector = _interactive_threshold_selection(image, voxel_size, background_remover_instance= background_remover, **kwargs)
|
|
267
|
+
|
|
268
|
+
Viewer.window.add_dock_widget(spot_detector.widget, name='threshold_selector')
|
|
269
|
+
spot_detector.widget() #First occurence with auto or entered threshold.
|
|
260
270
|
|
|
271
|
+
spots_layer = Viewer.layers['single spots']
|
|
272
|
+
|
|
273
|
+
if dense_region_deconvolution :
|
|
274
|
+
dense_region_deconvolver = _interactive_spot_decomposition(
|
|
275
|
+
image_layer,
|
|
276
|
+
voxel_size,
|
|
277
|
+
spots = spots_layer,
|
|
278
|
+
**kwargs
|
|
279
|
+
)
|
|
280
|
+
Viewer.window.add_dock_widget(dense_region_deconvolver.widget, name='dense_region_deconvolver')
|
|
281
|
+
|
|
261
282
|
napari.run()
|
|
262
283
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
284
|
+
updated_parameters = {}
|
|
285
|
+
updated_parameters.update(spot_detector.get_detection_parameters())
|
|
286
|
+
if dense_region_deconvolution : updated_parameters.update(dense_region_deconvolver.get_detection_parameters())
|
|
287
|
+
signal = Viewer.layers['raw signal'].data
|
|
288
|
+
|
|
289
|
+
spots = Viewer.layers['single spots'].data.astype(np.int32)
|
|
290
|
+
|
|
291
|
+
return spots, signal, updated_parameters
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _interactive_threshold_selection(image : np.ndarray, voxel_size : tuple, **kwargs) -> NapariWidget :
|
|
296
|
+
|
|
297
|
+
key_error = "Missing keys for interactive threshold selection, required keys are : 'default_threshold', 'default_spot_radius','default_kernel_size','default_min_distance',"
|
|
298
|
+
if not all([key for key in kwargs.keys()]) : raise ValueError(key_error)
|
|
299
|
+
type_dict = {
|
|
300
|
+
'default_threshold' : (type(None), int),
|
|
301
|
+
'default_spot_radius' : (type(None), tuple),
|
|
302
|
+
'default_kernel_size' : (type(None), tuple),
|
|
303
|
+
'default_min_distance' : (type(None), tuple),
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
for key, item in type_dict.items() :
|
|
307
|
+
if not isinstance(kwargs[key], item) : raise TypeError(f"Expected type {item} for {key} argument, got {type(kwargs[key])}")
|
|
308
|
+
|
|
309
|
+
spot_detector = SpotDetector(
|
|
310
|
+
image=image,
|
|
311
|
+
default_threshold= kwargs["default_threshold"],
|
|
312
|
+
default_kernel_size=kwargs["default_kernel_size"],
|
|
313
|
+
default_min_distance=kwargs["default_min_distance"],
|
|
314
|
+
default_spot_size=kwargs["default_spot_radius"],
|
|
315
|
+
voxel_size=voxel_size,
|
|
316
|
+
background_remover_instance= kwargs['background_remover_instance']
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return spot_detector
|
|
320
|
+
|
|
321
|
+
def _interactive_spot_decomposition(image : Image, voxel_size : tuple, **kwargs) -> NapariWidget :
|
|
322
|
+
key_error = "Missing keys for interactive threshold selection, required keys are : 'spots', 'deconvolution_spot_radius','deconvolution_kernel_size','alpha', 'beta', 'gamma'"
|
|
323
|
+
if not all([key for key in kwargs.keys()]) : raise ValueError(key_error)
|
|
324
|
+
type_dict = {
|
|
325
|
+
'spots' : Points,
|
|
326
|
+
'deconvolution_spot_radius' : (type(None), tuple),
|
|
327
|
+
'alpha' : float,
|
|
328
|
+
'beta' : float,
|
|
329
|
+
'gamma' : float,
|
|
330
|
+
'deconvolution_kernel_size' : (type(None), tuple),
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
dense_regions_deconvolver = DenseRegionDeconvolver(
|
|
334
|
+
image= image,
|
|
335
|
+
spots= kwargs['spots'],
|
|
336
|
+
alpha=kwargs['alpha'],
|
|
337
|
+
beta=kwargs['beta'],
|
|
338
|
+
gamma=kwargs['gamma'],
|
|
339
|
+
spot_radius=kwargs['deconvolution_spot_radius'],
|
|
340
|
+
kernel_size=kwargs['deconvolution_kernel_size'],
|
|
341
|
+
voxel_size=voxel_size
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return dense_regions_deconvolver
|
|
345
|
+
|
|
346
|
+
def _interactive_background_removal(image : np.ndarray, voxel_size : tuple, **kwargs) -> NapariWidget :
|
|
347
|
+
|
|
348
|
+
background_remover = BackgroundRemover(
|
|
349
|
+
signal= image,
|
|
350
|
+
voxel_size = voxel_size,
|
|
351
|
+
other_image = kwargs.get('other_image'),
|
|
352
|
+
)
|
|
268
353
|
|
|
269
|
-
return
|
|
354
|
+
return background_remover
|
small_fish_gui/gui/prompts.py
CHANGED
|
@@ -2,7 +2,7 @@ import FreeSimpleGUI as sg
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
import os
|
|
4
4
|
import numpy as np
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
|
|
7
7
|
from typing import Literal, Union
|
|
8
8
|
from .layout import (
|
|
@@ -13,10 +13,12 @@ from .layout import (
|
|
|
13
13
|
radio_layout,
|
|
14
14
|
colocalization_layout,
|
|
15
15
|
tuple_layout,
|
|
16
|
-
_detection_layout
|
|
16
|
+
_detection_layout,
|
|
17
|
+
_segmentation_layout
|
|
17
18
|
)
|
|
18
|
-
from ..interface import open_image, check_format, FormatError
|
|
19
|
+
from ..interface import open_image, check_format, FormatError, get_settings
|
|
19
20
|
|
|
21
|
+
default = get_settings()
|
|
20
22
|
|
|
21
23
|
def prompt(layout, add_ok_cancel=True, timeout=None, timeout_key='TIMEOUT_KEY', add_scrollbar=True) :
|
|
22
24
|
"""
|
|
@@ -24,30 +26,38 @@ def prompt(layout, add_ok_cancel=True, timeout=None, timeout_key='TIMEOUT_KEY',
|
|
|
24
26
|
"""
|
|
25
27
|
if add_ok_cancel : layout += [[sg.Button('Ok', bind_return_key=True), sg.Button('Cancel')]]
|
|
26
28
|
|
|
27
|
-
size = (
|
|
28
|
-
col_elmt = sg.Column(layout, scrollable=True, vertical_scroll_only=True, size=size, expand_x=True, expand_y=True)
|
|
29
|
+
size = (800,800)
|
|
30
|
+
col_elmt = sg.Column(layout, scrollable=True, vertical_scroll_only=True, size=size, size_subsample_height=1, expand_x=True, expand_y=True)
|
|
29
31
|
layout = [[col_elmt]]
|
|
30
32
|
|
|
31
|
-
window = sg.Window('small fish', layout=layout, margins=(10,10), size=size, resizable=True, location=None)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
window.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
window = sg.Window('small fish', layout=layout, margins=(10,10), size=size, resizable=True, location=None, enable_close_attempted_event=True)
|
|
34
|
+
|
|
35
|
+
while True :
|
|
36
|
+
event, values = window.read(timeout=timeout, timeout_key=timeout_key)
|
|
37
|
+
if event == sg.WIN_CLOSE_ATTEMPTED_EVENT :
|
|
38
|
+
answ = sg.popup_yes_no("Do you want to close Small Fish ?")
|
|
39
|
+
if answ == "Yes" :
|
|
40
|
+
window.close()
|
|
41
|
+
quit()
|
|
42
|
+
else :
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
elif event == 'Cancel' or event is None :
|
|
46
|
+
window.close()
|
|
47
|
+
return event,{}
|
|
48
|
+
else :
|
|
49
|
+
window.close()
|
|
50
|
+
return event, values
|
|
43
51
|
|
|
44
52
|
def input_image_prompt(
|
|
53
|
+
filename_preset : str,
|
|
45
54
|
is_3D_stack_preset=False,
|
|
46
55
|
multichannel_preset = False,
|
|
47
56
|
do_dense_regions_deconvolution_preset= False,
|
|
48
57
|
do_clustering_preset = False,
|
|
49
58
|
do_Napari_correction= False,
|
|
50
|
-
|
|
59
|
+
do_background_removal_preset = False,
|
|
60
|
+
) :
|
|
51
61
|
"""
|
|
52
62
|
Keys :
|
|
53
63
|
- 'image_path'
|
|
@@ -61,11 +71,17 @@ def input_image_prompt(
|
|
|
61
71
|
Returns Values
|
|
62
72
|
|
|
63
73
|
"""
|
|
64
|
-
layout_image_path =
|
|
65
|
-
layout_image_path +=
|
|
74
|
+
layout_image_path = [[sg.Text("Open an image", font="Bold 15")]]
|
|
75
|
+
layout_image_path += path_layout(['image_path'], preset=filename_preset)
|
|
76
|
+
layout_image_path += bool_layout(['3D stack', 'Multichannel stack',],keys= ['is_3D_stack', 'is_multichannel'], preset= [is_3D_stack_preset, multichannel_preset])
|
|
66
77
|
|
|
67
78
|
if type(do_dense_regions_deconvolution_preset) != type(None) and type(do_clustering_preset) != type(None) and type(do_Napari_correction) != type(None):
|
|
68
|
-
layout_image_path += bool_layout(
|
|
79
|
+
layout_image_path += bool_layout(
|
|
80
|
+
['Dense regions deconvolution', 'Compute clusters', 'Open results in Napari',],
|
|
81
|
+
keys = ['do_dense_regions_deconvolution', 'do_cluster_computation', 'show_napari_corrector'],
|
|
82
|
+
preset= [do_dense_regions_deconvolution_preset, do_clustering_preset, do_Napari_correction],
|
|
83
|
+
header= "Pipeline settings"
|
|
84
|
+
)
|
|
69
85
|
|
|
70
86
|
event, values = prompt(layout_image_path, add_scrollbar=False)
|
|
71
87
|
|
|
@@ -79,7 +95,7 @@ def input_image_prompt(
|
|
|
79
95
|
try :
|
|
80
96
|
image = open_image(im_path)
|
|
81
97
|
check_format(image, is_3D_stack, is_multichannel)
|
|
82
|
-
values
|
|
98
|
+
values['image'] = image
|
|
83
99
|
except FormatError as error:
|
|
84
100
|
sg.popup("Inconsistency between image format and options selected.\n Image shape : {0}".format(image.shape))
|
|
85
101
|
except OSError as error :
|
|
@@ -95,19 +111,16 @@ def output_image_prompt(filename) :
|
|
|
95
111
|
relaunch = False
|
|
96
112
|
layout = path_layout(['folder'], look_for_dir= True, header= "Output parameters :")
|
|
97
113
|
layout += parameters_layout(["filename"], default_values= [filename + "_quantification"], size=25)
|
|
98
|
-
layout += bool_layout(['csv','Excel'
|
|
114
|
+
layout += bool_layout(['csv','Excel'])
|
|
99
115
|
|
|
100
116
|
event,values= prompt(layout)
|
|
101
117
|
if event == ('Cancel') : return None
|
|
102
118
|
|
|
103
119
|
values['filename'] = values['filename'].replace(".xlsx","")
|
|
104
|
-
values['filename'] = values['filename'].replace(".feather","")
|
|
105
120
|
excel_filename = values['filename'] + ".xlsx"
|
|
106
|
-
feather_filename = values['filename'] + ".feather"
|
|
107
|
-
|
|
108
121
|
|
|
109
|
-
if not values['Excel'] and not values['
|
|
110
|
-
sg.popup("Please check at least one box : Excel/
|
|
122
|
+
if not values['Excel'] and not values['csv'] :
|
|
123
|
+
sg.popup("Please check at least one box : Excel/csv")
|
|
111
124
|
relaunch = True
|
|
112
125
|
elif not os.path.isdir(values['folder']) :
|
|
113
126
|
sg.popup("Incorrect folder")
|
|
@@ -117,11 +130,6 @@ def output_image_prompt(filename) :
|
|
|
117
130
|
pass
|
|
118
131
|
else :
|
|
119
132
|
relaunch = True
|
|
120
|
-
elif os.path.isfile(values['folder'] + feather_filename) and values['Feather']:
|
|
121
|
-
if ask_replace_file(feather_filename) :
|
|
122
|
-
pass
|
|
123
|
-
else :
|
|
124
|
-
relaunch = True
|
|
125
133
|
|
|
126
134
|
if not relaunch : break
|
|
127
135
|
|
|
@@ -157,6 +165,61 @@ def detection_parameters_promt(
|
|
|
157
165
|
else : values['dim'] = 2
|
|
158
166
|
return values
|
|
159
167
|
|
|
168
|
+
def segmentation_prompt(**segmentation_parameters) :
|
|
169
|
+
|
|
170
|
+
layout, event_dict = _segmentation_layout(**segmentation_parameters)
|
|
171
|
+
|
|
172
|
+
layout += [[sg.Button("Ok", bind_return_key=True), sg.Button("Cancel")]]
|
|
173
|
+
layout = [[sg.Column(layout, scrollable=True, vertical_scroll_only=True, size_subsample_height=2, expand_x=True, expand_y=True)]]
|
|
174
|
+
|
|
175
|
+
window = sg.Window('small fish', layout=layout, margins=(10,10), size=(800,800), resizable=True, location=None, enable_close_attempted_event=True)
|
|
176
|
+
while True :
|
|
177
|
+
event, values = window.read(timeout=300, timeout_key="timeout")
|
|
178
|
+
|
|
179
|
+
if event == sg.WIN_CLOSE_ATTEMPTED_EVENT :
|
|
180
|
+
answ = sg.popup_yes_no("Do you want to close Small Fish ?")
|
|
181
|
+
if answ == "Yes" :
|
|
182
|
+
window.close()
|
|
183
|
+
quit()
|
|
184
|
+
else :
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
elif event == 'Cancel' or event is None :
|
|
188
|
+
window.close()
|
|
189
|
+
return event,{}
|
|
190
|
+
|
|
191
|
+
elif event == "timeout" :
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
elif event == "Ok" :
|
|
195
|
+
window.close()
|
|
196
|
+
return event, values
|
|
197
|
+
|
|
198
|
+
elif event == "segment_only_nuclei" :
|
|
199
|
+
if event_dict['segment_only_nuclei'].get() : #user wants to segment only nuclei
|
|
200
|
+
event_dict['cytoplasm_column'].update(visible=False)
|
|
201
|
+
else :
|
|
202
|
+
event_dict['cytoplasm_column'].update(visible=True)
|
|
203
|
+
|
|
204
|
+
elif "_radio_2D" in event :
|
|
205
|
+
object_key = event.split("_radio_2D")[0]
|
|
206
|
+
|
|
207
|
+
for elmnt_to_enable in event_dict[object_key + "_radio_2D"] :
|
|
208
|
+
elmnt_to_enable.update(disabled=False)
|
|
209
|
+
for elmnt_to_disable in event_dict[object_key + "_radio_3D"] :
|
|
210
|
+
elmnt_to_disable.update(disabled=True)
|
|
211
|
+
|
|
212
|
+
elif "_radio_3D" in event :
|
|
213
|
+
object_key = event.split("_radio_3D")[0]
|
|
214
|
+
|
|
215
|
+
for elmnt_to_enable in event_dict[object_key + "_radio_3D"] :
|
|
216
|
+
elmnt_to_enable.update(disabled=False)
|
|
217
|
+
for elmnt_to_disable in event_dict[object_key + "_radio_2D"] :
|
|
218
|
+
elmnt_to_disable.update(disabled=True)
|
|
219
|
+
|
|
220
|
+
else :
|
|
221
|
+
raise(AssertionError(f"Not supported event : {event} in segmentation prompt."))
|
|
222
|
+
|
|
160
223
|
def ask_replace_file(filename:str) :
|
|
161
224
|
layout = [
|
|
162
225
|
[sg.Text("{0} already exists, replace ?")],
|
|
@@ -199,14 +262,17 @@ def _sumup_df(results: pd.DataFrame) :
|
|
|
199
262
|
COLUMNS = ['acquisition_id','name','threshold', 'spot_number', 'cell_number', 'filename', 'channel_to_compute']
|
|
200
263
|
|
|
201
264
|
if len(results) > 0 :
|
|
202
|
-
if 'channel_to_compute' not in results : results['channel_to_compute'] = np.
|
|
265
|
+
if 'channel_to_compute' not in results : results['channel_to_compute'] = np.nan
|
|
203
266
|
res = results.loc[:,COLUMNS]
|
|
204
267
|
else :
|
|
205
268
|
res = pd.DataFrame(columns= COLUMNS)
|
|
206
269
|
|
|
207
270
|
return res
|
|
208
271
|
|
|
209
|
-
def hub_prompt(
|
|
272
|
+
def hub_prompt(
|
|
273
|
+
fov_results : pd.DataFrame,
|
|
274
|
+
do_segmentation=False
|
|
275
|
+
) -> 'Union[Literal["Add detection", "Compute colocalisation", "Batch detection", "Rename acquisition", "Save results", "Delete acquisitions", "Reset segmentation", "Reset results", "Segment cells"], dict[Literal["result_table", ""]]]':
|
|
210
276
|
|
|
211
277
|
sumup_df = _sumup_df(fov_results)
|
|
212
278
|
|
|
@@ -216,14 +282,14 @@ def hub_prompt(fov_results : pd.DataFrame, do_segmentation=False) -> 'Union[Lite
|
|
|
216
282
|
segmentation_object = sg.Text('No segmentation in memory', font='8', text_color= 'red')
|
|
217
283
|
|
|
218
284
|
layout = [
|
|
219
|
-
[sg.Text('RESULTS', font= 'bold 13')],
|
|
285
|
+
[sg.Text('RESULTS', font= 'bold 13'), sg.Stretch(), sg.Button('⚙️',font="Arial 15", button_color="gray", key="settings",)],
|
|
220
286
|
[sg.Table(values= list(sumup_df.values), headings= list(sumup_df.columns), row_height=20, num_rows= 5, vertical_scroll_only=False, key= "result_table"), segmentation_object],
|
|
221
287
|
[sg.Button('Segment cells'), sg.Button('Add detection'), sg.Button('Compute colocalisation'), sg.Button('Batch detection')],
|
|
222
288
|
[sg.Button('Save results', button_color= 'green'), sg.Button('Save segmentation', button_color= 'green'), sg.Button('Load segmentation', button_color= 'green')],
|
|
223
289
|
[sg.Button('Rename acquisition', button_color= 'gray'), sg.Button('Delete acquisitions',button_color= 'gray'), sg.Button('Reset segmentation',button_color= 'gray'), sg.Button('Reset all',button_color= 'gray'), sg.Button('Open wiki',button_color= 'yellow', key='wiki')],
|
|
224
290
|
]
|
|
225
291
|
|
|
226
|
-
window = sg.Window('small fish', layout= layout, margins= (10,10), location=None)
|
|
292
|
+
window = sg.Window('small fish', layout= layout, margins= (10,10), location=None, enable_close_attempted_event=True)
|
|
227
293
|
|
|
228
294
|
while True :
|
|
229
295
|
event, values = window.read()
|
|
@@ -232,41 +298,88 @@ def hub_prompt(fov_results : pd.DataFrame, do_segmentation=False) -> 'Union[Lite
|
|
|
232
298
|
window.close()
|
|
233
299
|
return event, values
|
|
234
300
|
|
|
235
|
-
|
|
236
|
-
|
|
301
|
+
|
|
302
|
+
def coloc_prompt(spot_list : list, **default_values) :
|
|
303
|
+
layout, element_dict = colocalization_layout(spot_list, **default_values)
|
|
237
304
|
|
|
238
305
|
layout = [[sg.Col(
|
|
239
306
|
layout,
|
|
240
307
|
expand_x=True,
|
|
241
308
|
expand_y=True,
|
|
242
309
|
vertical_scroll_only=True,
|
|
243
|
-
scrollable=True
|
|
310
|
+
scrollable=True,
|
|
244
311
|
)
|
|
245
312
|
]]
|
|
246
|
-
window = sg.Window('small fish', layout=layout, margins=(10,10), resizable=
|
|
313
|
+
window = sg.Window('small fish', layout=layout, margins=(10,10), resizable=False, size=(800,800), location=None, auto_size_buttons=True)
|
|
247
314
|
while True :
|
|
248
315
|
event, values = window.read(timeout=100, timeout_key="timeout")
|
|
249
316
|
|
|
250
317
|
if event == None : quit()
|
|
251
318
|
elif event == "timeout" : pass
|
|
319
|
+
elif "radio_spots" in event :
|
|
320
|
+
spot_id = 1 if "1" in event else 2
|
|
321
|
+
is_memory = "memory" in event
|
|
322
|
+
for row in element_dict[f"options_spots{spot_id}_memory"].Rows :
|
|
323
|
+
for elmnt in row :
|
|
324
|
+
if not isinstance(elmnt, sg.Text): elmnt.update(disabled= not is_memory)
|
|
325
|
+
for row in element_dict[f"options_spots{spot_id}_load"].Rows :
|
|
326
|
+
for elmnt in row :
|
|
327
|
+
if not isinstance(elmnt, sg.Text): elmnt.update(disabled= is_memory)
|
|
328
|
+
|
|
252
329
|
elif event == 'Ok' :
|
|
253
|
-
|
|
254
|
-
if values["spots1_dropdown"] =="" :
|
|
330
|
+
if element_dict["radio_spots1_load"].get() :
|
|
255
331
|
spots1 = values["spots1_browse"]
|
|
256
332
|
else :
|
|
257
333
|
spots1 = values["spots1_dropdown"]
|
|
258
334
|
|
|
259
|
-
if
|
|
335
|
+
if element_dict["radio_spots2_load"].get() :
|
|
260
336
|
spots2 = values["spots2_browse"]
|
|
261
337
|
else :
|
|
262
338
|
spots2 = values["spots2_dropdown"]
|
|
263
|
-
|
|
264
339
|
|
|
265
|
-
|
|
266
|
-
|
|
340
|
+
if element_dict["radio_spots1_load"].get() and element_dict["radio_spots2_load"].get() :
|
|
341
|
+
sucess, voxel_size = _check_voxel_size_equality(
|
|
342
|
+
voxel_size_1 =(values[f"z_voxel_size_spot1"], values[f"y_voxel_size_spot1"], values[f"x_voxel_size_spot1"]),
|
|
343
|
+
voxel_size_2 =(values[f"z_voxel_size_spot2"], values[f"y_voxel_size_spot2"], values[f"x_voxel_size_spot2"]),
|
|
344
|
+
)
|
|
345
|
+
elif element_dict["radio_spots1_load"].get() :
|
|
346
|
+
sucess, voxel_size = _check_voxel_size_equality(
|
|
347
|
+
voxel_size_1 =(values[f"z_voxel_size_spot1"], values[f"y_voxel_size_spot1"], values[f"x_voxel_size_spot1"]),
|
|
348
|
+
voxel_size_2 =(values[f"z_voxel_size_spot1"], values[f"y_voxel_size_spot1"], values[f"x_voxel_size_spot1"]),
|
|
349
|
+
)
|
|
350
|
+
elif element_dict["radio_spots2_load"].get() :
|
|
351
|
+
sucess, voxel_size = _check_voxel_size_equality(
|
|
352
|
+
voxel_size_1 =(values[f"z_voxel_size_spot2"], values[f"y_voxel_size_spot2"], values[f"x_voxel_size_spot2"]),
|
|
353
|
+
voxel_size_2 =(values[f"z_voxel_size_spot2"], values[f"y_voxel_size_spot2"], values[f"x_voxel_size_spot2"]),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
else :
|
|
357
|
+
voxel_size = (1,1,1)
|
|
358
|
+
sucess = True
|
|
359
|
+
|
|
360
|
+
if not sucess :
|
|
361
|
+
pass
|
|
362
|
+
else :
|
|
363
|
+
window.close()
|
|
364
|
+
return values['colocalisation distance'], voxel_size, spots1, spots2, values
|
|
267
365
|
else :
|
|
268
366
|
window.close()
|
|
269
|
-
return None,None,None,None
|
|
367
|
+
return None,None,None,None, {}
|
|
368
|
+
|
|
369
|
+
def _check_voxel_size_equality(
|
|
370
|
+
voxel_size_1 : tuple,
|
|
371
|
+
voxel_size_2 : tuple
|
|
372
|
+
) :
|
|
373
|
+
try :
|
|
374
|
+
voxel_size_1 = tuple([int(voxel) for voxel in voxel_size_1])
|
|
375
|
+
voxel_size_2 = tuple([int(voxel) for voxel in voxel_size_2])
|
|
376
|
+
if voxel_size_1 != voxel_size_2 :
|
|
377
|
+
raise ValueError("voxel sizes must be identical for spots 1 and spots 2.")
|
|
378
|
+
except ValueError as e :
|
|
379
|
+
sg.popup(str(e))
|
|
380
|
+
return False, voxel_size_1
|
|
381
|
+
else :
|
|
382
|
+
return True, voxel_size_1
|
|
270
383
|
|
|
271
384
|
def rename_prompt() :
|
|
272
385
|
layout = parameters_layout(['name'], header= "Rename acquisitions", size=12)
|