small-fish-gui 1.4.0__tar.gz → 1.6.0__tar.gz

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 (53) hide show
  1. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/PKG-INFO +1 -1
  2. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/pyproject.toml +1 -1
  3. small_fish_gui-1.6.0/src/small_fish_gui/.readthedocs.yaml +21 -0
  4. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/README.md +2 -3
  5. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/__init__.py +1 -1
  6. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/__main__.py +3 -3
  7. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/prompt.py +6 -4
  8. small_fish_gui-1.6.0/src/small_fish_gui/docs/conf.py +1 -0
  9. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/gui/layout.py +21 -3
  10. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/gui/prompts.py +17 -11
  11. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/_colocalisation.py +9 -0
  12. small_fish_gui-1.6.0/src/small_fish_gui/pipeline/_napari_wrapper.py +198 -0
  13. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/_preprocess.py +24 -20
  14. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/_segmentation.py +130 -17
  15. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/actions.py +36 -2
  16. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/detection.py +17 -11
  17. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/main.py +10 -3
  18. small_fish_gui-1.6.0/src/small_fish_gui/pipeline/utils.py +16 -0
  19. small_fish_gui-1.4.0/src/small_fish_gui/pipeline/_napari_wrapper.py +0 -229
  20. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/LICENSE +0 -0
  21. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/README.md +0 -0
  22. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/.github/workflows/python-publish.yml +0 -0
  23. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/LICENSE +0 -0
  24. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/Segmentation example.jpg +0 -0
  25. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/__init__.py +0 -0
  26. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/input.py +0 -0
  27. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/integrity.py +0 -0
  28. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/output.py +0 -0
  29. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/pipeline.py +0 -0
  30. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/test.py +0 -0
  31. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/update.py +0 -0
  32. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/utils.py +0 -0
  33. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/values.py +0 -0
  34. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/batch/values.txt +0 -0
  35. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/gui/__init__.py +0 -0
  36. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/gui/animation.py +0 -0
  37. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/gui/general_help_screenshot.png +0 -0
  38. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/gui/help_module.py +0 -0
  39. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/gui/mapping_help_screenshot.png +0 -0
  40. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/gui/segmentation_help_screenshot.png +0 -0
  41. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/interface/__init__.py +0 -0
  42. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/interface/image.py +0 -0
  43. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/interface/output.py +0 -0
  44. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/interface/parameters.py +0 -0
  45. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/interface/testing.py +0 -0
  46. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/napari_detection_example.png +0 -0
  47. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/__init__.py +0 -0
  48. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/_custom_errors.py +0 -0
  49. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/_signaltonoise.py +0 -0
  50. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/spots.py +0 -0
  51. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/pipeline/test.py +0 -0
  52. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/requirements.txt +0 -0
  53. {small_fish_gui-1.4.0 → small_fish_gui-1.6.0}/src/small_fish_gui/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: small_fish_gui
3
- Version: 1.4.0
3
+ Version: 1.6.0
4
4
  Summary: Small Fish is a python application for the analysis of smFish images. It provides a ready to use graphical interface to combine famous python packages for cell analysis without any need for coding.
5
5
  Project-URL: Homepage, https://github.com/2Echoes/small_fish
6
6
  Project-URL: Issues, https://github.com/2Echoes/small_fish/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "small_fish_gui"
7
- version = "1.4.0"
7
+ version = "1.6.0"
8
8
  authors = [
9
9
  { name="Slimani Floric", email="floric.slimani@live.com" },
10
10
  ]
@@ -0,0 +1,21 @@
1
+ # .readthedocs.yaml
2
+ # Read the Docs configuration file
3
+ # See for details
4
+
5
+ # Required
6
+ version: 2
7
+
8
+ # Set the OS, Python version and other tools you might need
9
+ build:
10
+ os: ubuntu-22.04
11
+ tools:
12
+ python: "3.8"
13
+
14
+ # Build documentation in the "docs/" directory with Sphinx
15
+ sphinx:
16
+ configuration: docs/conf.py
17
+
18
+ # Optionally build your docs in additional formats such as PDF and ePub
19
+ formats:
20
+ - pdf
21
+ - epub
@@ -5,7 +5,7 @@ Cell segmentation (**2D**) is peformed using *cellpose* (published work) : https
5
5
 
6
6
  Spot detection is performed via *big-fish* (published work) : https://github.com/fish-quant/big-fish
7
7
 
8
- Time stacks are not yet supported.
8
+ **Time stacks are not supported.**
9
9
 
10
10
  ## What can you do with small fish ?
11
11
 
@@ -77,7 +77,7 @@ If you run into any problems I would recommend following the official cellpose i
77
77
 
78
78
 
79
79
  ### Training cellpose
80
- If you want to train your own cellpose model or import custom model from exterior source I recommend doing so from the cellpose GUI
80
+ If you want to train your own cellpose model or import custom model from exterior source I recommend doing so from the cellpose GUI. Note that Small fish uses mean projection to segment images in 2D, if you want to retrain a cellpose model to fit your data it is recommended to do so on mean or max projection of your data.
81
81
 
82
82
  To install the GUI run :
83
83
 
@@ -93,6 +93,5 @@ Note that for training it is recommended to first set up your GPU as training co
93
93
  ## Developpement
94
94
 
95
95
  Optional features to include in future versions :
96
- - batch processing
97
96
  - time stack (which would include cell tracking)
98
97
  - 3D segmentation
@@ -38,4 +38,4 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38
38
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
39
 
40
40
  """
41
- __version__ = "1.4.0"
41
+ __version__ = "1.6.0"
@@ -1,6 +1,8 @@
1
1
  import sys, subprocess
2
2
  import PySimpleGUI as sg
3
3
 
4
+ from small_fish_gui import __version__
5
+
4
6
  def main():
5
7
  import small_fish_gui.pipeline.main
6
8
 
@@ -10,9 +12,7 @@ def is_last_version() :
10
12
  latest_version = latest_version[:latest_version.find(')')]
11
13
  latest_version = latest_version.replace(' ','').split(',')[-1]
12
14
 
13
- current_version = str(subprocess.run([sys.executable, '-m', 'pip', 'show', '{}'.format('small_fish_gui')], capture_output=True, text=True))
14
- current_version = current_version[current_version.find('Version:')+8:]
15
- current_version = current_version[:current_version.find('\\n')].replace(' ','')
15
+ current_version = __version__
16
16
 
17
17
  return current_version == latest_version
18
18
 
@@ -35,7 +35,7 @@ def batch_promp(
35
35
  sanity_header = sg.Text("Dimension sanity", font=('bold',15), pad=(0,10))
36
36
  dimension_number_text = sg.Text("Dimension number : unknown")
37
37
 
38
-
38
+ #LAYOUT INIT
39
39
  #########################################
40
40
  ##### COLUMNS
41
41
  #########################################
@@ -182,8 +182,6 @@ def batch_promp(
182
182
  [tab_col, launch_col],
183
183
  [stream_output],
184
184
  ]
185
- stream_output.restore_stderr()
186
- stream_output.restore_stdout()
187
185
 
188
186
  window = sg.Window("small fish", layout=layout, size= (800,800), auto_size_buttons=True, auto_size_text=True, resizable=True)
189
187
 
@@ -332,7 +330,11 @@ def batch_promp(
332
330
  batch_name_input.update(value=values.get('batch_name'))
333
331
 
334
332
  elif event == "Cancel" :
335
- raise InterruptedError("cancel")
333
+ stream_output.restore_stderr()
334
+ stream_output.restore_stdout()
335
+ window.close()
336
+ return results_df, cell_results_df, acquisition_id, preset, False, None,None #Segmentation done : False, cell label : None, Nucleus label : None
337
+
336
338
 
337
339
  elif event == None :
338
340
  raise InterruptedError("closed")
@@ -0,0 +1 @@
1
+ #init conf file
@@ -95,7 +95,7 @@ def path_layout(keys= [],look_for_dir = False, header=None, preset=os.getcwd())
95
95
 
96
96
  max_length = len(max(keys, key=len))
97
97
  layout = [
98
- [sg.Text(pad_right(name, max_length, ' ')), Browse(key= name, initial_folder= preset)] for name in keys
98
+ [sg.Text(pad_right(name, max_length, ' ')), Browse(key= name, target=(555666777,2), initial_folder= preset), sg.Text('')] for name in keys
99
99
  ]
100
100
  if isinstance(header, str) :
101
101
  layout = [add_header(header)] + layout
@@ -152,7 +152,20 @@ def radio_layout(values, header=None) :
152
152
  layout = [add_header(header)] + layout
153
153
  return layout
154
154
 
155
- def _segmentation_layout(multichannel, cytoplasm_model_preset= 'cyto2', nucleus_model_preset= 'nuclei', cytoplasm_channel_preset=0, nucleus_channel_preset=0, cyto_diameter_preset=30, nucleus_diameter_preset= 30, show_segmentation_preset= False, segment_only_nuclei_preset=False, saving_path_preset=os.getcwd(), filename_preset='cell_segmentation.png',) :
155
+ def _segmentation_layout(
156
+ multichannel,
157
+ cytoplasm_model_preset= 'cyto3',
158
+ nucleus_model_preset= 'nuclei',
159
+ cytoplasm_channel_preset=0,
160
+ nucleus_channel_preset=0,
161
+ other_nucleus_image_preset = None,
162
+ cyto_diameter_preset=30,
163
+ nucleus_diameter_preset= 30,
164
+ show_segmentation_preset= False,
165
+ segment_only_nuclei_preset=False,
166
+ saving_path_preset=os.getcwd(),
167
+ filename_preset='cell_segmentation.png',
168
+ ) :
156
169
 
157
170
  USE_GPU = use_gpu()
158
171
 
@@ -160,7 +173,9 @@ def _segmentation_layout(multichannel, cytoplasm_model_preset= 'cyto2', nucleus_
160
173
  if len(models_list) == 0 : models_list = ['no model found']
161
174
 
162
175
  #Header : GPU availabality
163
- layout = [[sg.Text("GPU is currently "), sg.Text('ON', text_color= 'green') if USE_GPU else sg.Text('OFF', text_color= 'red')]]
176
+ layout = [
177
+ [sg.Text("GPU is currently "), sg.Text('ON', text_color= 'green') if USE_GPU else sg.Text('OFF', text_color= 'red')]
178
+ ]
164
179
 
165
180
  #cytoplasm parameters
166
181
  layout += [
@@ -170,6 +185,8 @@ def _segmentation_layout(multichannel, cytoplasm_model_preset= 'cyto2', nucleus_
170
185
  ]
171
186
 
172
187
  if multichannel : layout += parameters_layout(['cytoplasm channel'],default_values= [cytoplasm_channel_preset])
188
+
189
+
173
190
  layout += parameters_layout(['cytoplasm diameter'], unit= "px", default_values= [cyto_diameter_preset])
174
191
  #Nucleus parameters
175
192
  layout += [
@@ -179,6 +196,7 @@ def _segmentation_layout(multichannel, cytoplasm_model_preset= 'cyto2', nucleus_
179
196
  ]
180
197
 
181
198
  if multichannel : layout += parameters_layout(['nucleus channel'], default_values= [nucleus_channel_preset])
199
+ layout += path_layout(['other_nucleus_image'], preset=other_nucleus_image_preset)
182
200
  layout += parameters_layout([ 'nucleus diameter'],unit= "px", default_values= [nucleus_diameter_preset])
183
201
  layout += bool_layout(["Segment only nuclei"], preset=segment_only_nuclei_preset)
184
202
 
@@ -32,14 +32,14 @@ def prompt(layout, add_ok_cancel=True, timeout=None, timeout_key='TIMEOUT_KEY',
32
32
  window.close()
33
33
  return event, values
34
34
 
35
- def prompt_with_help(layout, help =None, add_scrollbar=True) :
35
+ def prompt_with_help(layout, help =None, add_scrollbar=True, vertical_scroll_only=True) :
36
36
  layout += [[]]
37
37
  layout += [[sg.Button('Help')]]
38
38
  layout += [[sg.Button('Ok'), sg.Button('Cancel')]]
39
39
 
40
40
  if add_scrollbar :
41
41
  size = (400,500)
42
- col_elmt = sg.Column(layout, scrollable=True, vertical_scroll_only=True, size=size)
42
+ col_elmt = sg.Column(layout, scrollable=True, vertical_scroll_only=vertical_scroll_only, size=size)
43
43
  layout = [[col_elmt]]
44
44
  else :
45
45
  size = (None,None)
@@ -280,11 +280,13 @@ def _warning_popup(warning:str) :
280
280
 
281
281
  def _sumup_df(results: pd.DataFrame) :
282
282
 
283
+ COLUMNS = ['acquisition_id','name','threshold', 'spot_number', 'cell_number', 'filename', 'channel to compute']
284
+
283
285
  if len(results) > 0 :
284
286
  if 'channel to compute' not in results : results['channel to compute'] = np.NaN
285
- res = results.loc[:,['acquisition_id', 'spot_number', 'cell_number', 'filename', 'channel to compute']]
287
+ res = results.loc[:,COLUMNS]
286
288
  else :
287
- res = pd.DataFrame(columns= ['acquisition_id', 'spot_number', 'cell_number', 'filename', 'channel to compute'])
289
+ res = pd.DataFrame(columns= COLUMNS)
288
290
 
289
291
  return res
290
292
 
@@ -293,15 +295,15 @@ def hub_prompt(fov_results, do_segmentation=False) :
293
295
  sumup_df = _sumup_df(fov_results)
294
296
 
295
297
  if do_segmentation :
296
- segmentation_object = sg.Text('Segmentation was performed', font='8', text_color= 'green')
298
+ segmentation_object = sg.Text('Segmentation in memory', font='8', text_color= 'green')
297
299
  else :
298
- segmentation_object = sg.Text('Segmentation was not performed', font='8', text_color= 'red')
300
+ segmentation_object = sg.Text('No segmentation in memory', font='8', text_color= 'red')
299
301
 
300
302
  layout = [
301
303
  [sg.Text('RESULTS', font= 'bold 13')],
302
304
  [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],
303
305
  [sg.Button('Add detection'), sg.Button('Compute colocalisation'), sg.Button('Batch detection')],
304
- [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')]
306
+ [sg.Button('Rename acquisition', button_color= 'green'), 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')]
305
307
  # [sg.Button('Save results', button_color= 'green'), sg.Button('Reset results',button_color= 'gray')]
306
308
  ]
307
309
 
@@ -316,16 +318,20 @@ def hub_prompt(fov_results, do_segmentation=False) :
316
318
  return event, values
317
319
 
318
320
  def coloc_prompt() :
319
- layout = [
320
- [parameters_layout(['colocalisation distance'], header= 'Colocalisation', default_values= 0)]
321
- ]
322
-
321
+ layout = parameters_layout(['colocalisation distance'], unit= 'nm', header= 'Colocalisation', default_values= 0)
323
322
  event, values = prompt_with_help(layout)
324
323
 
325
324
  if event == 'Ok' :
326
325
  return values['colocalisation distance']
327
326
  else : return False
328
327
 
328
+ def rename_prompt() :
329
+ layout = parameters_layout(['name'], header= "Rename acquisitions", size=12)
330
+ event, values = prompt_with_help(layout)
331
+ if event == 'Ok' :
332
+ return values['name']
333
+ else : return False
334
+
329
335
  def ask_detection_confirmation(used_threshold) :
330
336
  layout = [
331
337
  [sg.Text("Proceed with current detection ?", font= 'bold 10')],
@@ -263,4 +263,13 @@ def launch_colocalisation(result_tables, result_dataframe, colocalisation_distan
263
263
  'fraction_spots1_coloc_cluster2' : [fraction_spots1_coloc_cluster2],
264
264
  })
265
265
 
266
+ coloc_df['fraction_spots1_coloc_free2'] = coloc_df['fraction_spots1_coloc_spots2'] - coloc_df['fraction_spots1_coloc_cluster2']
267
+ coloc_df['fraction_spots2_coloc_free1'] = coloc_df['fraction_spots2_coloc_spots1'] - coloc_df['fraction_spots2_coloc_cluster1']
268
+
269
+ #Add names
270
+ coloc_df_col = list(coloc_df.columns)
271
+ coloc_df['name1'] = acquisition1.at['name']
272
+ coloc_df['name2'] = acquisition2.at['name']
273
+ coloc_df = coloc_df.loc[:,['name1','name2'] + coloc_df_col]
274
+
266
275
  return coloc_df
@@ -0,0 +1,198 @@
1
+ """
2
+ Contains Napari wrappers to visualise and correct spots/clusters.
3
+ """
4
+
5
+ import napari.layers
6
+ import napari.types
7
+ import numpy as np
8
+ import napari
9
+
10
+ from bigfish.stack import check_parameter
11
+ from ..utils import compute_anisotropy_coef
12
+ from ._colocalisation import spots_multicolocalisation
13
+
14
+ def _update_clusters(new_clusters: np.ndarray, spots: np.ndarray, voxel_size, cluster_size, min_spot_number, shape) :
15
+ if len(new_clusters) == 0 : return new_clusters
16
+ if len(spots) == 0 : return np.empty(shape=(0,2+len(voxel_size)))
17
+
18
+ if len(new_clusters[0]) in [2,3] :
19
+ new_clusters = np.concatenate([
20
+ new_clusters,
21
+ np.zeros(shape=(len(new_clusters),1), dtype=int),
22
+ np.arange(len(new_clusters), dtype=int).reshape(len(new_clusters),1)
23
+ ],axis=1, dtype=int)
24
+
25
+ assert len(new_clusters[0]) == 4 or len(new_clusters[0]) == 5, "Wrong number of coordinates for clusters should not happen."
26
+
27
+ # Update spots clusters
28
+ new_clusters[:,-2] = spots_multicolocalisation(new_clusters[:,:-2], spots, radius_nm= cluster_size, voxel_size=voxel_size, image_shape=shape)
29
+
30
+ # delete too small clusters
31
+ new_clusters = np.delete(new_clusters, new_clusters[:,-2] < min_spot_number, 0)
32
+
33
+ return new_clusters
34
+
35
+ def correct_spots(image, spots, voxel_size= (1,1,1), clusters= None, cluster_size=None, min_spot_number=0, cell_label= None, nucleus_label= None, other_images =[]):
36
+ """
37
+ Open Napari viewer for user to visualize and corrects spots, clusters.
38
+
39
+ Returns
40
+ -------
41
+ new_spots,new_clusters
42
+ """
43
+ check_parameter(image= np.ndarray, voxel_size= (tuple,list))
44
+ dim = len(voxel_size)
45
+
46
+ if dim == 3 and type(cell_label) != type(None) :
47
+ cell_label = np.repeat(
48
+ cell_label[np.newaxis],
49
+ repeats= len(image),
50
+ axis=0
51
+ )
52
+ if dim == 3 and type(nucleus_label) != type(None) :
53
+ nucleus_label = np.repeat(
54
+ nucleus_label[np.newaxis],
55
+ repeats= len(image),
56
+ axis=0
57
+ )
58
+
59
+ scale = compute_anisotropy_coef(voxel_size)
60
+ Viewer = napari.Viewer(ndisplay=2, title= 'Spot correction', axis_labels=['z','y','x'], show= False)
61
+ Viewer.add_image(image, scale=scale, name= "rna signal", blending= 'additive', colormap='red', contrast_limits=[image.min(), image.max()])
62
+ other_colors = ['green', 'blue', 'gray', 'cyan', 'bop orange', 'bop purple'] * ((len(other_images)-1 // 7) + 1)
63
+ for im, color in zip(other_images, other_colors) :
64
+ Viewer.add_image(im, scale=scale, blending='additive', visible=False, colormap=color, contrast_limits=[im.min(), im.max()])
65
+ layer_offset = len(other_images)
66
+
67
+ Viewer.add_points( # single molecule spots; this layer can be update by user.
68
+ spots,
69
+ size = 5,
70
+ scale=scale,
71
+ face_color= 'transparent',
72
+ opacity= 1,
73
+ symbol= 'disc',
74
+ name= 'single spots'
75
+ )
76
+
77
+ if type(clusters) != type(None) : Viewer.add_points( # cluster; this layer can be update by user.
78
+ clusters[:,:dim],
79
+ size = 10,
80
+ scale=scale,
81
+ face_color= 'blue',
82
+ opacity= 0.7,
83
+ symbol= 'diamond',
84
+ name= 'foci',
85
+ features= {"spot_number" : clusters[:,dim], "id" : clusters[:,dim+1]},
86
+ feature_defaults= {"spot_number" : 0, "id" : -1}
87
+ )
88
+
89
+ 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')
90
+ if type(nucleus_label) != type(None) : Viewer.add_labels(nucleus_label, scale=scale, opacity= 0.2, blending= 'additive')
91
+
92
+ Viewer.show(block=False)
93
+ napari.run()
94
+
95
+ new_spots = np.array(Viewer.layers['single spots'].data, dtype= int)
96
+
97
+ if type(clusters) != type(None) :
98
+ new_clusters = np.array(Viewer.layers['foci'].data, dtype= int)
99
+ new_clusters = _update_clusters(new_clusters, new_spots, voxel_size=voxel_size, cluster_size=cluster_size, min_spot_number=min_spot_number, shape=image.shape)
100
+ else : new_clusters = None
101
+
102
+ return new_spots, new_clusters
103
+
104
+ def show_segmentation(
105
+ nuc_image : np.ndarray,
106
+ nuc_label : np.ndarray,
107
+ cyto_image : np.ndarray = None,
108
+ cyto_label : np.ndarray = None,
109
+ ) :
110
+ dim = nuc_image.ndim
111
+
112
+ if type(cyto_image) != type(None) :
113
+ if cyto_image.ndim != nuc_image.ndim : raise ValueError("Cyto and Nuc dimensions missmatch.")
114
+ if type(cyto_label) == type(None) : raise ValueError("If cyto image is passed cyto label must be passed too.")
115
+
116
+ if dim == 3 and nuc_label.ndim == 2 :
117
+ nuc_label = np.repeat(
118
+ nuc_label[np.newaxis],
119
+ repeats= len(nuc_image),
120
+ axis=0
121
+ )
122
+ if type(cyto_label) != type(None) :
123
+
124
+ if type(cyto_image) == type(None) : raise ValueError("If cyto label is passed cyto image must be passed too.")
125
+
126
+ if dim == 3 and cyto_label.ndim == 2 :
127
+ cyto_label = np.repeat(
128
+ cyto_label[np.newaxis],
129
+ repeats= len(nuc_image),
130
+ axis=0
131
+ )
132
+
133
+ #Init Napari viewer
134
+ Viewer = napari.Viewer(ndisplay=2, title= 'Show segmentation', axis_labels=['z','y','x'] if dim == 3 else ['y', 'x'], show= False)
135
+
136
+ # Adding channels
137
+ nuc_signal_layer = Viewer.add_image(nuc_image, name= "nucleus signal", blending= 'additive', colormap='blue', contrast_limits=[nuc_image.min(), nuc_image.max()])
138
+ nuc_label_layer = Viewer.add_labels(nuc_label, opacity= 0.5, blending= 'additive', name= 'nucleus_label',)
139
+ nuc_label_layer.preserve_labels = True
140
+
141
+ #Adding labels
142
+ if type(cyto_image) != type(None) : Viewer.add_image(cyto_image, name= "cytoplasm signal", blending= 'additive', colormap='red', contrast_limits=[cyto_image.min(), cyto_image.max()])
143
+ if (type(cyto_label) != type(None) and not np.array_equal(cyto_label, nuc_label) ) or (type(cyto_label) != type(None) and cyto_label.max() == 0):
144
+ cyto_label_layer = Viewer.add_labels(cyto_label, opacity= 0.4, blending= 'additive', name= 'cytoplasm_label')
145
+ cyto_label_layer.preserve_labels = True
146
+
147
+ #Launch Napari
148
+ Viewer.show(block=False)
149
+ napari.run()
150
+
151
+ new_nuc_label = Viewer.layers['nucleus_label'].data
152
+ if 'cytoplasm_label' in Viewer.layers :
153
+ new_cyto_label = Viewer.layers['cytoplasm_label'].data
154
+ else : new_cyto_label = new_nuc_label
155
+
156
+ return new_nuc_label, new_cyto_label
157
+
158
+ def threshold_selection(
159
+ image : np.ndarray,
160
+ filtered_image : np.ndarray,
161
+ threshold_slider,
162
+ voxel_size : tuple,
163
+ ) :
164
+
165
+ """
166
+ 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.
167
+ """
168
+
169
+ Viewer = napari.Viewer(title= "Small fish - Threshold selector", ndisplay=2, show=True)
170
+ scale = compute_anisotropy_coef(voxel_size)
171
+ Viewer.add_image(
172
+ data= image,
173
+ contrast_limits= [image.min(), image.max()],
174
+ name= "raw signal",
175
+ colormap= 'green',
176
+ scale= scale,
177
+ blending= 'additive'
178
+ )
179
+ Viewer.add_image(
180
+ data= filtered_image,
181
+ contrast_limits= [filtered_image.min(), filtered_image.max()],
182
+ colormap= 'gray',
183
+ scale=scale,
184
+ blending='additive'
185
+ )
186
+
187
+ Viewer.window.add_dock_widget(threshold_slider, name='threshold_selector')
188
+ threshold_slider() #First occurence with auto or entered threshold.
189
+
190
+ napari.run()
191
+
192
+ spots = Viewer.layers['single spots'].data.astype(int)
193
+ if len(spots) == 0 :
194
+ pass
195
+ else :
196
+ threshold = Viewer.layers['single spots'].properties.get('threshold')[0]
197
+
198
+ return spots, threshold
@@ -69,7 +69,7 @@ def map_channels(user_parameters) :
69
69
  try :
70
70
  map = _auto_map_channels(is_3D_stack, is_time_stack, multichannel, image=image)
71
71
  except MappingError as e :
72
- sg.popup("Automatic dimension mapping went wrong. Please indicate manually dimensions positions in the array.")
72
+ sg.popup("Automatic dimension mapping went wrong. Please indicate dimensions positions in the array.")
73
73
  map = _ask_channel_map(image.shape, is_3D_stack, is_time_stack, multichannel, preset_map= e.get_map())
74
74
 
75
75
  else :
@@ -125,14 +125,16 @@ def _auto_map_channels(is_3D_stack, is_time_stack, multichannel, image: np.ndarr
125
125
  def _ask_channel_map(shape, is_3D_stack, is_time_stack, multichannel, preset_map: dict= {}) :
126
126
  while True :
127
127
  relaunch = False
128
+ save_preset = preset_map.copy()
128
129
  x = preset_map.setdefault('x',0)
129
130
  y = preset_map.setdefault('y',0)
130
131
  z = preset_map.setdefault('z',0)
131
132
  c = preset_map.setdefault('c',0)
132
133
  t = preset_map.setdefault('t',0)
133
134
 
135
+
134
136
  layout = [
135
- add_header("Dimensions mapping", [sg.Text("Image shape : {0}".format(shape))])
137
+ add_header("Dimensions mapping") + [sg.Text("Image shape : {0}".format(shape))]
136
138
  ]
137
139
  layout += [parameters_layout(['x','y'], default_values=[x,y])]
138
140
  if is_3D_stack : layout += [parameters_layout(['z'], default_values=[z])]
@@ -140,7 +142,7 @@ def _ask_channel_map(shape, is_3D_stack, is_time_stack, multichannel, preset_map
140
142
  if is_time_stack : layout += [parameters_layout(['t'], default_values=[t])]
141
143
 
142
144
  event, preset_map = prompt_with_help(layout,help= 'mapping', add_scrollbar=False)
143
- if event == 'Cancel' : quit()
145
+ if event == 'Cancel' : return save_preset
144
146
 
145
147
  #Check integrity
146
148
  channels_values = np.array(list(preset_map.values()), dtype= int)
@@ -157,24 +159,26 @@ def _ask_channel_map(shape, is_3D_stack, is_time_stack, multichannel, preset_map
157
159
  return preset_map
158
160
 
159
161
  def _show_mapping(shape, map, is_3D_stack, is_time_stack, multichannel) :
160
- layout = [
161
- [sg.Text("Image shape : {0}".format(shape))],
162
- [sg.Text('Dimensions mapping was set to :')],
163
- [sg.Text('x : {0} \ny : {1} \nz : {2} \nc : {3} \nt : {4}'.format(
164
- map['x'], map['y'], map.get('z'), map.get("c"), map.get('t')
165
- ))],
166
- [sg.Button('Change mapping')]
167
- ]
168
-
169
- event, values = prompt_with_help(layout, help='mapping', add_scrollbar=False)
170
-
171
- if event == 'Ok' :
172
- return map
173
- elif event == 'Change mapping' or event == 'Cancel':
174
- map = _ask_channel_map(shape, is_3D_stack, is_time_stack, multichannel, preset_map=map)
175
- else : raise AssertionError('Unforseen event')
162
+ while True :
163
+ layout = [
164
+ [sg.Text("Image shape : {0}".format(shape))],
165
+ [sg.Text('Dimensions mapping was set to :')],
166
+ [sg.Text('x : {0} \ny : {1} \nz : {2} \nc : {3} \nt : {4}'.format(
167
+ map['x'], map['y'], map.get('z'), map.get("c"), map.get('t')
168
+ ))],
169
+ [sg.Button('Change mapping')]
170
+ ]
171
+
172
+ event, values = prompt_with_help(layout, help='mapping', add_scrollbar=False)
173
+
174
+ if event == 'Ok' :
175
+ return map
176
+ elif event == 'Change mapping':
177
+ map = _ask_channel_map(shape, is_3D_stack, is_time_stack, multichannel, preset_map=map)
178
+ elif event == 'Cancel' :
179
+ return None
180
+ else : raise AssertionError('Unforseen event')
176
181
 
177
- return map
178
182
 
179
183
  def convert_parameters_types(values:dict) :
180
184
  """