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.
Files changed (51) hide show
  1. small_fish_gui/__init__.py +2 -2
  2. small_fish_gui/batch/integrity.py +2 -2
  3. small_fish_gui/batch/pipeline.py +46 -11
  4. small_fish_gui/batch/prompt.py +102 -41
  5. small_fish_gui/batch/update.py +26 -13
  6. small_fish_gui/batch/utils.py +1 -1
  7. small_fish_gui/gui/__init__.py +1 -0
  8. small_fish_gui/gui/_napari_widgets.py +418 -6
  9. small_fish_gui/gui/layout.py +332 -112
  10. small_fish_gui/gui/napari_visualiser.py +107 -22
  11. small_fish_gui/gui/prompts.py +161 -48
  12. small_fish_gui/gui/testing.ipynb +231 -24
  13. small_fish_gui/gui/tooltips.py +7 -1
  14. small_fish_gui/hints.py +23 -7
  15. small_fish_gui/interface/__init__.py +7 -1
  16. small_fish_gui/interface/default_settings.py +118 -0
  17. small_fish_gui/interface/image.py +43 -11
  18. small_fish_gui/interface/settings.json +50 -0
  19. small_fish_gui/interface/testing.ipynb +4354 -0
  20. small_fish_gui/interface/user_settings.py +96 -0
  21. small_fish_gui/main_menu.py +13 -1
  22. small_fish_gui/pipeline/{_signaltonoise.py → _bigfish_wrapers.py} +59 -7
  23. small_fish_gui/pipeline/_colocalisation.py +23 -24
  24. small_fish_gui/pipeline/_preprocess.py +46 -32
  25. small_fish_gui/pipeline/actions.py +48 -5
  26. small_fish_gui/pipeline/detection.py +71 -141
  27. small_fish_gui/pipeline/segmentation.py +360 -268
  28. small_fish_gui/pipeline/spots.py +3 -3
  29. small_fish_gui/pipeline/utils.py +5 -1
  30. small_fish_gui/README.md → small_fish_gui-2.0.3.dist-info/METADATA +35 -0
  31. small_fish_gui-2.0.3.dist-info/RECORD +46 -0
  32. {small_fish_gui-2.0.2.dist-info → small_fish_gui-2.0.3.dist-info}/WHEEL +1 -1
  33. small_fish_gui/.github/workflows/python-publish.yml +0 -39
  34. small_fish_gui/LICENSE +0 -24
  35. small_fish_gui/batch/values.txt +0 -65
  36. small_fish_gui/default_values.py +0 -51
  37. small_fish_gui/gui/screenshot/general_help_screenshot.png +0 -0
  38. small_fish_gui/gui/screenshot/mapping_help_screenshot.png +0 -0
  39. small_fish_gui/gui/screenshot/segmentation_help_screenshot.png +0 -0
  40. small_fish_gui/illustrations/DetectionVitrine_filtre.png +0 -0
  41. small_fish_gui/illustrations/DetectionVitrine_signal.png +0 -0
  42. small_fish_gui/illustrations/FocciVitrine.png +0 -0
  43. small_fish_gui/illustrations/FocciVitrine_no_spots.png +0 -0
  44. small_fish_gui/illustrations/Segmentation2D.png +0 -0
  45. small_fish_gui/illustrations/Segmentation2D_with_labels.png +0 -0
  46. small_fish_gui/logo.png +0 -0
  47. small_fish_gui/pipeline/testing.ipynb +0 -3636
  48. small_fish_gui/requirements.txt +0 -19
  49. small_fish_gui-2.0.2.dist-info/METADATA +0 -75
  50. small_fish_gui-2.0.2.dist-info/RECORD +0 -59
  51. {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.NaN passing -2 instead.
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 threshold_selection(
232
+ def interactive_detection(
230
233
  image : np.ndarray,
231
- filtered_image : np.ndarray,
232
- threshold_slider,
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
- Viewer.window.add_dock_widget(threshold_slider, name='threshold_selector')
259
- threshold_slider() #First occurence with auto or entered threshold.
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
- spots = Viewer.layers['single spots'].data.astype(int)
264
- if len(spots) == 0 :
265
- pass
266
- else :
267
- threshold = Viewer.layers['single spots'].properties.get('threshold')[0]
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 spots, threshold
354
+ return background_remover
@@ -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
- import small_fish_gui.default_values as default
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 = (400,500)
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
- event, values = window.read(timeout=timeout, timeout_key=timeout_key)
33
- if event == None :
34
- window.close()
35
- quit()
36
-
37
- elif event == 'Cancel' :
38
- window.close()
39
- return event,{}
40
- else :
41
- window.close()
42
- return event, values
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 = path_layout(['image_path'], header= "Image")
65
- layout_image_path += bool_layout(['3D stack', 'Multichannel stack'],keys= ['is_3D_stack', 'is_multichannel'], preset= [is_3D_stack_preset, multichannel_preset])
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(['Dense regions deconvolution', 'Compute clusters', 'Open results in Napari'], keys = ['do_dense_regions_deconvolution', 'do_cluster_computation', 'show_napari_corrector'], preset= [do_dense_regions_deconvolution_preset, do_clustering_preset, do_Napari_correction], header= "Pipeline settings")
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.update({'image' : image})
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', 'Feather'])
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['Feather'] and not values['csv'] :
110
- sg.popup("Please check at least one box : Excel/Feather/csv")
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.NaN
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(fov_results : pd.DataFrame, do_segmentation=False) -> 'Union[Literal["Add detection", "Compute colocalisation", "Batch detection", "Rename acquisition", "Save results", "Delete acquisitions", "Reset segmentation", "Reset results", "Segment cells"], dict[Literal["result_table", ""]]]':
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
- def coloc_prompt(spot_list : list) :
236
- layout = colocalization_layout(spot_list)
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=True, location=None, auto_size_buttons=True)
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 values["spots2_dropdown"] =="" :
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
- window.close()
266
- return values['colocalisation distance'], [values['voxel_size_z'], values['voxel_size_y'], values['voxel_size_x']], spots1, spots2
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)