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.
@@ -0,0 +1,338 @@
1
+ import PySimpleGUI as sg
2
+ import pandas as pd
3
+ import os
4
+ from .layout import path_layout, parameters_layout, bool_layout, tuple_layout, combo_layout, add_header
5
+ from ..interface import open_image, check_format, FormatError
6
+ from .help_module import ask_help
7
+
8
+ def prompt(layout, add_ok_cancel=True, timeout=None, timeout_key='TIMEOUT_KEY') :
9
+ """
10
+ Default event : 'Ok', 'Cancel'
11
+ """
12
+ if add_ok_cancel : layout += [[sg.Button('Ok'), sg.Button('Cancel')]]
13
+
14
+
15
+ window = sg.Window('small fish', layout=layout, margins=(10,10))
16
+ event, values = window.read(timeout=timeout, timeout_key=timeout_key)
17
+ if event == None :
18
+ window.close()
19
+ quit()
20
+
21
+ elif event == 'Cancel' :
22
+ window.close()
23
+ return event,{}
24
+ else :
25
+ window.close()
26
+ return event, values
27
+
28
+ def prompt_with_help(layout, help =None) :
29
+ layout += [[]]
30
+ layout += [[sg.Button('Help')]]
31
+ layout += [[sg.Button('Ok'), sg.Button('Cancel')]]
32
+
33
+ window = sg.Window('small fish', layout=layout)
34
+ while True :
35
+ event, values = window.read()
36
+ if event == None :
37
+ window.close()
38
+ quit()
39
+
40
+ elif event == 'Ok':
41
+ window.close()
42
+ return event, values
43
+ elif event == 'Help' :
44
+ ask_help(chapter= help)
45
+
46
+ else:
47
+ window.close()
48
+ return event,{}
49
+
50
+ def input_image_prompt(
51
+ is_3D_stack_preset=False,
52
+ time_stack_preset=False,
53
+ multichannel_preset = False,
54
+ do_dense_regions_deconvolution_preset= False,
55
+ do_clustering_preset = False,
56
+ do_segmentation_preset= False,
57
+ do_Napari_correction= False,
58
+ ask_for_segmentation= True
59
+ ) :
60
+ """
61
+ Keys :
62
+ - 'image path'
63
+ - '3D stack'
64
+ - 'time stack'
65
+ - 'multichannel'
66
+ - 'Dense regions deconvolution'
67
+ - 'Segmentation'
68
+ - 'Napari correction'
69
+
70
+ Returns Values
71
+
72
+ """
73
+ layout_image_path = path_layout(['image path'], header= "Image")
74
+ layout_image_path += bool_layout(['3D stack', 'time stack', 'multichannel'], preset= [is_3D_stack_preset, time_stack_preset, multichannel_preset])
75
+
76
+ if ask_for_segmentation :
77
+ layout_image_path += bool_layout(['Dense regions deconvolution', 'Cluster computation', 'Segmentation', 'Napari correction'], preset= [do_dense_regions_deconvolution_preset, do_clustering_preset, do_segmentation_preset, do_Napari_correction], header= "Pipeline settings")
78
+ else :
79
+ layout_image_path += bool_layout(['Dense regions deconvolution', 'Cluster computation', 'Napari correction'], preset= [do_dense_regions_deconvolution_preset, do_clustering_preset, do_Napari_correction], header= "Pipeline settings")
80
+
81
+ event, values = prompt_with_help(layout_image_path, help= 'general')
82
+
83
+ if event == 'Cancel' :
84
+ return None
85
+
86
+ im_path = values['image path']
87
+ is_3D_stack = values['3D stack']
88
+ is_time_stack = values['time stack']
89
+ is_multichannel = values['multichannel']
90
+ if not ask_for_segmentation : values['Segmentation'] = False
91
+
92
+ if is_time_stack :
93
+ sg.popup("Sorry time stack images are not yet supported.")
94
+ return values
95
+
96
+ try :
97
+ image = open_image(im_path)
98
+ check_format(image, is_3D_stack, is_time_stack, is_multichannel)
99
+ values.update({'image' : image})
100
+ except FormatError as error:
101
+ sg.popup("Inconsistency between image format and options selected.\n Image shape : {0}".format(image.shape))
102
+ except OSError as error :
103
+ sg.popup('Image format not supported.')
104
+ except ValueError as error :
105
+ sg.popup('Image format not supported.')
106
+
107
+
108
+ return values
109
+
110
+ def output_image_prompt(filename) :
111
+ while True :
112
+ relaunch = False
113
+ layout = path_layout(['folder'], look_for_dir= True, header= "Output parameters :")
114
+ layout += parameters_layout(["filename"], default_values= [filename + "_quantification"], size=25)
115
+ layout += bool_layout(['Excel', 'Feather'])
116
+ layout.append([sg.Button('Cancel')])
117
+
118
+ event,values= prompt(layout)
119
+
120
+ values['filename'] = values['filename'].replace(".xlsx","")
121
+ values['filename'] = values['filename'].replace(".feather","")
122
+ excel_filename = values['filename'] + ".xlsx"
123
+ feather_filename = values['filename'] + ".feather"
124
+
125
+ if not values['Excel'] and not values['Feather'] :
126
+ sg.popup("Please check at least one box : Excel/Feather")
127
+ relaunch = True
128
+ elif not os.path.isdir(values['folder']) :
129
+ sg.popup("Incorrect folder")
130
+ relaunch = True
131
+ elif os.path.isfile(values['folder'] + excel_filename) and values['Excel']:
132
+ if ask_replace_file(excel_filename) :
133
+ pass
134
+ else :
135
+ relaunch = True
136
+ elif os.path.isfile(values['folder'] + feather_filename) and values['Feather']:
137
+ if ask_replace_file(feather_filename) :
138
+ pass
139
+ else :
140
+ relaunch = True
141
+
142
+ if not relaunch : break
143
+
144
+ if event == ('Cancel') : return None
145
+
146
+ else : return values
147
+
148
+ def detection_parameters_promt(is_3D_stack, is_multichannel, do_dense_region_deconvolution, do_clustering, do_segmentation, segmentation_done, default_dict: dict) :
149
+ """
150
+
151
+ keys :
152
+ - 'threshold'
153
+ - 'threshold penalty
154
+ - 'time step'
155
+ - 'channel to compute'
156
+ - 'alpha'
157
+ - 'beta'
158
+ - 'gamma'
159
+ - 'voxel_size_{(z,y,x)}'
160
+ - 'spot_size{(z,y,x)}'
161
+ - 'log_kernel_size{(z,y,x)}'
162
+ - 'minimum_distance{(z,y,x)}'
163
+ - 'cluster size'
164
+ - 'min number of spots'
165
+
166
+ Returns Values
167
+
168
+ """
169
+ if is_3D_stack : dim = 3
170
+ else : dim = 2
171
+
172
+ #Detection
173
+ detection_parameters = ['threshold', 'threshold penalty']
174
+ default_detection = [default_dict.setdefault('threshold',''), default_dict.setdefault('threshold penalty', '1')]
175
+ opt= [True, True]
176
+ if is_multichannel :
177
+ detection_parameters += ['channel to compute']
178
+ opt += [False]
179
+ default_detection += [default_dict.setdefault('channel to compute', '')]
180
+ layout = [[sg.Text("Green parameters", text_color= 'green'), sg.Text(" are optional parameters.")]]
181
+ layout += parameters_layout(detection_parameters, header= 'Detection', opt=opt, default_values=default_detection)
182
+
183
+ if dim == 2 : tuple_shape = ('y','x')
184
+ else : tuple_shape = ('z','y','x')
185
+ opt = {'voxel_size' : False, 'spot_size' : False, 'log_kernel_size' : True, 'minimum_distance' : True}
186
+ unit = {'voxel_size' : 'nm', 'minimum_distance' : 'nm', 'spot_size' : 'radius(nm)', 'log_kernel_size' : 'px'}
187
+
188
+ layout += tuple_layout(opt=opt, unit=unit, default_dict=default_dict, voxel_size= tuple_shape, spot_size= tuple_shape, log_kernel_size= tuple_shape, minimum_distance= tuple_shape)
189
+
190
+ #Deconvolution
191
+ if do_dense_region_deconvolution :
192
+ default_dense_regions_deconvolution = [default_dict.setdefault('alpha',0.5), default_dict.setdefault('beta',1)]
193
+ layout += parameters_layout(['alpha', 'beta',], default_values= default_dense_regions_deconvolution, header= 'Dense regions deconvolution')
194
+ layout += parameters_layout(['gamma'], unit= 'px', default_values= [default_dict.setdefault('gamma',5)])
195
+ layout += tuple_layout(opt= {"deconvolution_kernel" : True}, unit= {"deconvolution_kernel" : 'px'}, default_dict=default_dict, deconvolution_kernel = tuple_shape)
196
+
197
+ #Clustering
198
+ if do_clustering :
199
+ layout += parameters_layout(['cluster size'], unit="radius(nm)", default_values=[default_dict.setdefault('cluster size',400)])
200
+ layout += parameters_layout(['min number of spots'], default_values=[default_dict.setdefault('min number of spots', 5)])
201
+
202
+ if (do_segmentation and is_multichannel) or (is_multichannel and segmentation_done):
203
+ default_segmentation = [default_dict.setdefault('nucleus channel signal', default_dict.setdefault('nucleus channel',0))]
204
+ layout += parameters_layout(['nucleus channel signal'], default_values=default_segmentation) + [[sg.Text(" channel from which signal will be measured for nucleus features.")]]
205
+
206
+ event, values = prompt_with_help(layout, help='detection')
207
+ if event == 'Cancel' : return None
208
+ if is_3D_stack : values['dim'] = 3
209
+ else : values['dim'] = 2
210
+ return values
211
+
212
+ def post_analysis_prompt() :
213
+ answer = events(['Save results','add_detection', 'colocalisation', 'open results in napari'])
214
+
215
+ return answer
216
+
217
+ def events(event_list) :
218
+ """
219
+ Return event chose from user
220
+ """
221
+
222
+ layout = [
223
+ [sg.Button(event) for event in event_list]
224
+ ]
225
+
226
+ event, values = prompt(layout, add_ok_cancel= False)
227
+ return event
228
+
229
+ def ask_replace_file(filename:str) :
230
+ layout = [
231
+ [sg.Text("{0} already exists, replace ?")],
232
+ [sg.Button('Yes'), sg.Button('No')]
233
+ ]
234
+
235
+ event, values = prompt(layout, add_ok_cancel= False)
236
+
237
+ return event == 'Yes'
238
+
239
+ def ask_cancel_segmentation() :
240
+ layout = [
241
+ [sg.Text("Cancel segmentation ?")],
242
+ [sg.Button('Yes'), sg.Button('No')]
243
+ ]
244
+
245
+ event, values = prompt(layout, add_ok_cancel= False)
246
+
247
+ return event == 'Yes'
248
+
249
+ def ask_quit_small_fish() :
250
+ layout = [
251
+ [sg.Text("Quit small fish ?")],
252
+ [sg.Button('Yes'), sg.Button('No')]
253
+ ]
254
+
255
+ event, values = prompt(layout, add_ok_cancel= False)
256
+
257
+ return event == 'Yes'
258
+
259
+ def _error_popup(error:Exception) :
260
+ sg.popup('Error : ' + str(error))
261
+ raise error
262
+
263
+ def _warning_popup(warning:str) :
264
+ sg.popup('Warning : ' + warning)
265
+
266
+ def _sumup_df(results: pd.DataFrame) :
267
+
268
+ if len(results) > 0 :
269
+ res = results.loc[:,['acquisition_id', 'spot_number', 'cell_number', 'filename', 'channel to compute']]
270
+ else :
271
+ res = pd.DataFrame(columns= ['acquisition_id', 'spot_number', 'cell_number', 'filename', 'channel to compute'])
272
+
273
+ return res
274
+
275
+ def hub_prompt(fov_results, do_segmentation=False) :
276
+
277
+ sumup_df = _sumup_df(fov_results)
278
+
279
+ if do_segmentation :
280
+ segmentation_object = sg.Text('Segmentation was performed', font='8', text_color= 'green')
281
+ else :
282
+ segmentation_object = sg.Text('Segmentation was not performed', font='8', text_color= 'red')
283
+
284
+ layout = [
285
+ [sg.Text('RESULTS', font= 'bold 13')],
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],
287
+ [sg.Button('Add detection'), sg.Button('Compute colocalisation'), sg.Button('Batch detection')],
288
+ # [sg.Button('Save results', button_color= 'green'), sg.Button('Delete acquisitions',button_color= 'gray'), sg.Button('Reset segmentation',button_color= 'gray'), sg.Button('Reset results',button_color= 'gray')]
289
+ [sg.Button('Save results', button_color= 'green'), sg.Button('Reset results',button_color= 'gray')]
290
+ ]
291
+
292
+ window = sg.Window('small fish', layout= layout, margins= (10,10))
293
+
294
+ while True :
295
+ event, values = window.read()
296
+ if event == None : quit()
297
+ elif event == 'Help' : pass
298
+ else :
299
+ window.close()
300
+ return event, values
301
+
302
+ def coloc_prompt() :
303
+ layout = [
304
+ [parameters_layout(['colocalisation distance'], header= 'Colocalisation', default_values= 0)]
305
+ ]
306
+
307
+ event, values = prompt_with_help(layout)
308
+
309
+ if event == 'Ok' :
310
+ return values['colocalisation distance']
311
+ else : return False
312
+
313
+ def ask_detection_confirmation(used_threshold) :
314
+ layout = [
315
+ [sg.Text("Proceed with current detection ?", font= 'bold 10')],
316
+ [sg.Text("Threshold : {0}".format(used_threshold))],
317
+ [sg.Button("Ok"), sg.Button("Restart detection")]
318
+ ]
319
+
320
+ event, value = prompt(layout, add_ok_cancel=False)
321
+
322
+ if event == 'Restart detection' :
323
+ return False
324
+ else :
325
+ return True
326
+
327
+ def ask_cancel_detection() :
328
+ layout =[
329
+ [sg.Text("Cancel new detection and return to main window ?", font= 'bold 10')],
330
+ [sg.Button("Yes"), sg.Button("No")]
331
+ ]
332
+
333
+ event, value = prompt(layout, add_ok_cancel=False)
334
+
335
+ if event == 'No' :
336
+ return False
337
+ else :
338
+ return True
@@ -0,0 +1,4 @@
1
+ import PySimpleGUI as sg
2
+ import small_fish.gui.prompts as p
3
+
4
+ p.hub_prompt([{},{},{"cell_number" : 100, "spot_number" : 1000},{},{},{'cell_number' : 10},{}])
@@ -0,0 +1,10 @@
1
+ """
2
+ This code handles the exchange between the computer and the code. That is to say opening and saving data.
3
+ """
4
+
5
+ from .image import open_image
6
+ from .image import get_filename
7
+ from .image import check_format
8
+ from .image import FormatError
9
+
10
+ from .output import write_results
@@ -0,0 +1,38 @@
1
+ from bigfish.stack import read_image
2
+ from czifile import imread
3
+ from ..utils import check_parameter
4
+ import re
5
+
6
+ class FormatError(Exception):
7
+ pass
8
+
9
+
10
+ def open_image(full_path:str) :
11
+ if full_path.endswith('.czi') : im = imread(full_path)
12
+ else : im = read_image(full_path)
13
+
14
+ reshape = []
15
+ for axis in im.shape :
16
+ if axis != 1 : reshape.append(axis)
17
+ im = im.reshape(reshape)
18
+
19
+ return im
20
+
21
+
22
+ def check_format(image, is_3D, is_time_stack, is_multichannel) :
23
+ shape = list(image.shape)
24
+ dim = image.ndim - (shape[image.ndim - 1] == 1)
25
+ if not dim == (2 + is_3D + is_time_stack + is_multichannel) :
26
+ raise FormatError("Inconsistency in image format and parameters.")
27
+
28
+
29
+
30
+ def get_filename(full_path: str) :
31
+ check_parameter(full_path=str)
32
+
33
+ pattern = r'.*\/(.+)\..*$'
34
+ if not full_path.startswith('/') : full_path = '/' + full_path
35
+ re_match = re.findall(pattern, full_path)
36
+ if len(re_match) == 0 : raise ValueError("Could not read filename from image full path.")
37
+ if len(re_match) == 1 : return re_match[0]
38
+ else : raise AssertionError("Several filenames read from path")
@@ -0,0 +1,42 @@
1
+ import os
2
+ import pandas as pd
3
+ from bigfish.stack import check_parameter
4
+
5
+
6
+
7
+ def _cast_spot_to_tuple(spot) :
8
+ return tuple([coord for coord in spot])
9
+
10
+ def _cast_spots_to_tuple(spots) :
11
+ return tuple(list(map(_cast_spot_to_tuple, spots)))
12
+
13
+ def write_results(dataframe: pd.DataFrame, path:str, filename:str, do_excel= True, do_feather= False) :
14
+ check_parameter(dataframe= pd.DataFrame, path= str, filename = str, do_excel = bool, do_feather = bool)
15
+
16
+ if len(dataframe) == 0 : return True
17
+ if not do_excel and not do_feather :
18
+ return False
19
+
20
+ if not path.endswith('/') : path +='/'
21
+ assert os.path.isdir(path)
22
+
23
+
24
+ new_filename = filename
25
+ i= 1
26
+ while new_filename + '.xlsx' in os.listdir(path) or new_filename + '.feather' in os.listdir(path) :
27
+ new_filename = filename + '_{0}'.format(i)
28
+ i+=1
29
+
30
+ if 'image' in dataframe.columns :
31
+ dataframe = dataframe.drop(['image'], axis=1)
32
+
33
+ if 'spots' in dataframe.columns :
34
+ dataframe = dataframe.drop(['spots'], axis= 1)
35
+
36
+ if 'clusters' in dataframe.columns :
37
+ dataframe = dataframe.drop(['clusters'], axis= 1)
38
+
39
+ if do_excel : dataframe.reset_index(drop=True).to_excel(path + filename + '.xlsx')
40
+ if do_feather : dataframe.reset_index(drop=True).to_feather(path + filename + '.feather')
41
+
42
+ return True
@@ -0,0 +1,2 @@
1
+ def ask_user_parameters() :
2
+ pass
@@ -0,0 +1,8 @@
1
+ import imageio.v3 as iio
2
+
3
+ path = '/home/flo/Documents/IGH projects/SohaQuantif/SCC/input/230723 n1 b-cat bac APC IF fitc ires neo smfish cy3 without puromycin-01.tif'
4
+
5
+ props = iio.improps(path)
6
+ meta = iio.immeta(path)
7
+ print(props)
8
+ print(meta['channels'], meta['slices'], meta['unit'], meta['hyperstack'], meta['spacing'])