small-fish-gui 1.1.0__tar.gz → 1.3.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 (41) hide show
  1. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/PKG-INFO +1 -1
  2. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/pyproject.toml +1 -1
  3. small_fish_gui-1.3.0/src/small_fish_gui/README.md +98 -0
  4. small_fish_gui-1.3.0/src/small_fish_gui/Segmentation example.jpg +0 -0
  5. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/__init__.py +1 -1
  6. small_fish_gui-1.3.0/src/small_fish_gui/__main__.py +29 -0
  7. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/layout.py +3 -3
  8. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/prompts.py +24 -5
  9. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/interface/output.py +13 -6
  10. small_fish_gui-1.3.0/src/small_fish_gui/napari_detection_example.png +0 -0
  11. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/_colocalisation.py +7 -7
  12. small_fish_gui-1.1.0/src/small_fish_gui/pipeline/_detection_visualisation.py → small_fish_gui-1.3.0/src/small_fish_gui/pipeline/_napari_wrapper.py +93 -4
  13. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/_preprocess.py +19 -2
  14. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/_segmentation.py +33 -14
  15. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/actions.py +54 -7
  16. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/detection.py +163 -28
  17. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/main.py +9 -3
  18. small_fish_gui-1.3.0/src/small_fish_gui/pipeline/spots.py +71 -0
  19. small_fish_gui-1.1.0/src/small_fish_gui/README.md +0 -51
  20. small_fish_gui-1.1.0/src/small_fish_gui/__main__.py +0 -15
  21. small_fish_gui-1.1.0/src/small_fish_gui/start.py +0 -7
  22. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/LICENSE +0 -0
  23. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/README.md +0 -0
  24. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/.github/workflows/python-publish.yml +0 -0
  25. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/LICENSE +0 -0
  26. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/__init__.py +0 -0
  27. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/animation.py +0 -0
  28. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/general_help_screenshot.png +0 -0
  29. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/help_module.py +0 -0
  30. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/mapping_help_screenshot.png +0 -0
  31. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/segmentation_help_screenshot.png +0 -0
  32. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/gui/test.py +0 -0
  33. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/interface/__init__.py +0 -0
  34. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/interface/image.py +0 -0
  35. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/interface/parameters.py +0 -0
  36. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/interface/testing.py +0 -0
  37. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/_custom_errors.py +0 -0
  38. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/_signaltonoise.py +0 -0
  39. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/pipeline/test.py +0 -0
  40. {small_fish_gui-1.1.0 → small_fish_gui-1.3.0}/src/small_fish_gui/requirements.txt +0 -0
  41. {small_fish_gui-1.1.0 → small_fish_gui-1.3.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.1.0
3
+ Version: 1.3.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.1.0"
7
+ version = "1.3.0"
8
8
  authors = [
9
9
  { name="Slimani Floric", email="floric.slimani@live.com" },
10
10
  ]
@@ -0,0 +1,98 @@
1
+ # Small Fish
2
+ **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.
3
+
4
+ Cell segmentation (**2D**) is peformed using *cellpose* (published work) : https://github.com/MouseLand/cellpose; compatible with your own cellpose models.
5
+
6
+ Spot detection is performed via *big-fish* (published work) : https://github.com/fish-quant/big-fish
7
+
8
+ Time stacks are not yet supported.
9
+
10
+ ## What can you do with small fish ?
11
+
12
+ - Single molecule quantification (including a lot of spatial features)
13
+ - Foci/Transcription site quantification
14
+ - Nuclear signal quantification
15
+ - Signal to noise analysis
16
+ - multichannel colocalisation
17
+
18
+ <img src="https://github.com/2Echoes/small_fish_gui/blob/main/Segmentation%20example.jpg" width="500" title="Cell segmentation with Cellpose" alt="Cell segmentation - cellpose">| <img src="https://github.com/2Echoes/small_fish_gui/blob/main/napari_detection_example.png" width="500" title="Spot detection; clustering visualisation on Napari" alt="detection; Napari example">
19
+
20
+ ## Installation
21
+ If you don't have a python installation yet I would recommend the [miniconda distribution](https://docs.anaconda.com/free/miniconda/miniconda-other-installer-links/); but any distribution should work.
22
+
23
+ It is higly recommanded to create a specific [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) or [virtual](https://docs.python.org/3.6/library/venv.html) environnement to install small fish.
24
+
25
+ ```bash
26
+ conda create -n small_fish python=3.8
27
+ conda activate small_fish
28
+ ```
29
+ Then download the small_fish package :
30
+ ```bash
31
+ pip install small_fish_gui
32
+ ```
33
+ <b> (Recommended) </b> Results visualisation is achieved through *Napari* which you can install with :
34
+
35
+ ```bash
36
+ pip install napari[all]
37
+ ```
38
+
39
+ ## Run Small fish
40
+
41
+ First activate your python environnement :
42
+ ```bash
43
+ activate small_fish
44
+ ```
45
+ Then launch Small fish :
46
+ ```bash
47
+ python -m small_fish_gui
48
+ ```
49
+
50
+ ## Cellpose configuration
51
+
52
+ For the following steps first activate your small fish environnement :
53
+
54
+ ```bash
55
+ conda activate small_fish
56
+ ```
57
+ ### Setting up your GPU for cellpose (Windows / Linux)
58
+ This instructions describe how I installed CUDA and GPU cellpose on the machines I tested, unfortunatly, drivers installations don't always run smoothly, if you run into any difficulties please have a look at the *GPU version (CUDA) on Windows or Linux* section of the [cellpose documentation](https://github.com/MouseLand/cellpose) for assistance.
59
+
60
+ First step is to check that your GPU is CUDA compatible which it should be if from the brand NVIIDA.
61
+ Then you need to install CUDA from the [NVIDIA archives](https://developer.nvidia.com/cuda-toolkit-archive), any 11.x version should work but I recommend the 11.8 version.
62
+
63
+ Finally we need to make some modifcation to your small fish environnement :
64
+
65
+ Remove the CPU version of torch
66
+
67
+ ```bash
68
+ pip uninstall torch
69
+ ```
70
+ Then install pytorch and cudatoolkit :
71
+
72
+ ```bash
73
+ conda install pytorch==1.12.0 cudatoolkit=11.3 -c pytorch
74
+ ```
75
+ If the installation succeeded next time your run segmentation with small fish you should see the "GPU is ON" notice upon entering the segmentation parameters.
76
+ If you run into any problems I would recommend following the official cellpose instructions as mentionned above.
77
+
78
+
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
81
+
82
+ To install the GUI run :
83
+
84
+ ```bash
85
+ pip install cellpose[gui]
86
+ ```
87
+ Then to run cellpose
88
+ ```bash
89
+ cellpose
90
+ ```
91
+ Note that for training it is recommended to first set up your GPU as training computation can be quite long otherwise. To get started with how to train your models you can watch the [video](https://www.youtube.com/watch?v=5qANHWoubZU) from cellpose authors.
92
+
93
+ ## Developpement
94
+
95
+ Optional features to include in future versions :
96
+ - batch processing
97
+ - time stack (which would include cell tracking)
98
+ - 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__ = "0.5.0"
41
+ __version__ = "1.3.0"
@@ -0,0 +1,29 @@
1
+ import sys, subprocess
2
+ import PySimpleGUI as sg
3
+
4
+ def main():
5
+ import small_fish_gui.pipeline.main
6
+
7
+ def is_last_version() :
8
+ latest_version = str(subprocess.run([sys.executable, '-m', 'pip', 'install', '{}==random'.format('small_fish_gui')], capture_output=True, text=True))
9
+ latest_version = latest_version[latest_version.find('(from versions:')+15:]
10
+ latest_version = latest_version[:latest_version.find(')')]
11
+ latest_version = latest_version.replace(' ','').split(',')[-1]
12
+
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(' ','')
16
+
17
+ return current_version == latest_version
18
+
19
+ if __name__ == "__main__":
20
+
21
+ if not is_last_version() :
22
+ print("A new version of Small Fish is available. To update close small fish and type :\npip install --upgrade small_fish_gui")
23
+
24
+ try :
25
+ sys.exit(main())
26
+ except Exception as error :
27
+ sg.popup("Sorry. Something went wrong...\nIf you have some time to spare could you please communicate the error you encountered (next window) on :\nhttps://github.com/2Echoes/small_fish/issues.")
28
+ sg.popup_error_with_traceback(error)
29
+ raise error
@@ -151,7 +151,7 @@ def radio_layout(values, header=None) :
151
151
  layout = add_header(header, layout=layout)
152
152
  return layout
153
153
 
154
- def _segmentation_layout(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') :
154
+ 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
155
 
156
156
  USE_GPU = use_gpu()
157
157
 
@@ -165,14 +165,14 @@ def _segmentation_layout(cytoplasm_model_preset= 'cyto2', nucleus_model_preset=
165
165
  layout += [add_header("Cell Segmentation", [sg.Text("Choose cellpose model for cytoplasm: \n")]),
166
166
  [combo_layout(models_list, key='cyto_model_name', default_value= cytoplasm_model_preset)]
167
167
  ]
168
- layout += [parameters_layout(['cytoplasm channel'],default_values= [cytoplasm_channel_preset])]
168
+ if multichannel : layout += [parameters_layout(['cytoplasm channel'],default_values= [cytoplasm_channel_preset])]
169
169
  layout += [parameters_layout(['cytoplasm diameter'], unit= "px", default_values= [cyto_diameter_preset])]
170
170
  #Nucleus parameters
171
171
  layout += [
172
172
  add_header("Nucleus segmentation",[sg.Text("Choose cellpose model for nucleus: \n")]),
173
173
  combo_layout(models_list, key='nucleus_model_name', default_value= nucleus_model_preset)
174
174
  ]
175
- layout += [parameters_layout(['nucleus channel'], default_values= [nucleus_channel_preset])]
175
+ if multichannel : layout += [parameters_layout(['nucleus channel'], default_values= [nucleus_channel_preset])]
176
176
  layout += [parameters_layout([ 'nucleus diameter'],unit= "px", default_values= [nucleus_diameter_preset])]
177
177
  layout += [bool_layout(["Segment only nuclei"], preset=segment_only_nuclei_preset)]
178
178
 
@@ -1,7 +1,8 @@
1
1
  import PySimpleGUI as sg
2
2
  import pandas as pd
3
3
  import os
4
- from .layout import path_layout, parameters_layout, bool_layout, tuple_layout, combo_layout, add_header
4
+ import numpy as np
5
+ from .layout import path_layout, parameters_layout, bool_layout, tuple_layout, combo_layout, add_header, path_layout
5
6
  from ..interface import open_image, check_format, FormatError
6
7
  from .help_module import ask_help
7
8
 
@@ -112,7 +113,7 @@ def output_image_prompt(filename) :
112
113
  relaunch = False
113
114
  layout = path_layout(['folder'], look_for_dir= True, header= "Output parameters :")
114
115
  layout += parameters_layout(["filename"], default_values= [filename + "_quantification"], size=25)
115
- layout += bool_layout(['Excel', 'Feather'])
116
+ layout += bool_layout(['csv','Excel', 'Feather'])
116
117
  layout.append([sg.Button('Cancel')])
117
118
 
118
119
  event,values= prompt(layout)
@@ -203,6 +204,23 @@ def detection_parameters_promt(is_3D_stack, is_multichannel, do_dense_region_dec
203
204
  default_segmentation = [default_dict.setdefault('nucleus channel signal', default_dict.setdefault('nucleus channel',0))]
204
205
  layout += parameters_layout(['nucleus channel signal'], default_values=default_segmentation) + [[sg.Text(" channel from which signal will be measured for nucleus features.")]]
205
206
 
207
+ layout += bool_layout(['Interactive threshold selector'], preset=[False])
208
+ layout += path_layout(
209
+ keys=['spots_extraction_folder'],
210
+ look_for_dir=True,
211
+ header= "Individual spot extraction",
212
+ preset= default_dict.setdefault('spots_extraction_folder', '')
213
+ )
214
+ layout += parameters_layout(
215
+ parameters=['spots_filename'],
216
+ default_values=[default_dict.setdefault('spots_filename','spots_extraction')],
217
+ size= 13
218
+ )
219
+ layout += bool_layout(
220
+ parameters= ['do_spots_csv', 'do_spots_excel', 'do_spots_feather'],
221
+ preset= [default_dict.setdefault('do_spots_csv',False), default_dict.setdefault('do_spots_excel',False),default_dict.setdefault('do_spots_feather',False)]
222
+ )
223
+
206
224
  event, values = prompt_with_help(layout, help='detection')
207
225
  if event == 'Cancel' : return None
208
226
  if is_3D_stack : values['dim'] = 3
@@ -266,6 +284,7 @@ def _warning_popup(warning:str) :
266
284
  def _sumup_df(results: pd.DataFrame) :
267
285
 
268
286
  if len(results) > 0 :
287
+ if 'channel to compute' not in results : results['channel to compute'] = np.NaN
269
288
  res = results.loc[:,['acquisition_id', 'spot_number', 'cell_number', 'filename', 'channel to compute']]
270
289
  else :
271
290
  res = pd.DataFrame(columns= ['acquisition_id', 'spot_number', 'cell_number', 'filename', 'channel to compute'])
@@ -284,9 +303,9 @@ def hub_prompt(fov_results, do_segmentation=False) :
284
303
  layout = [
285
304
  [sg.Text('RESULTS', font= 'bold 13')],
286
305
  [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')]
306
+ [sg.Button('Add detection'), sg.Button('Compute colocalisation')],#, sg.Button('Batch detection')],
307
+ [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')]
308
+ # [sg.Button('Save results', button_color= 'green'), sg.Button('Reset results',button_color= 'gray')]
290
309
  ]
291
310
 
292
311
  window = sg.Window('small fish', layout= layout, margins= (10,10))
@@ -2,7 +2,7 @@ import os
2
2
  import pandas as pd
3
3
  from bigfish.stack import check_parameter
4
4
 
5
-
5
+ MAX_LEN_EXCEL = 1048576
6
6
 
7
7
  def _cast_spot_to_tuple(spot) :
8
8
  return tuple([coord for coord in spot])
@@ -10,11 +10,11 @@ def _cast_spot_to_tuple(spot) :
10
10
  def _cast_spots_to_tuple(spots) :
11
11
  return tuple(list(map(_cast_spot_to_tuple, spots)))
12
12
 
13
- def write_results(dataframe: pd.DataFrame, path:str, filename:str, do_excel= True, do_feather= False) :
13
+ def write_results(dataframe: pd.DataFrame, path:str, filename:str, do_excel= True, do_feather= False, do_csv=False) :
14
14
  check_parameter(dataframe= pd.DataFrame, path= str, filename = str, do_excel = bool, do_feather = bool)
15
15
 
16
16
  if len(dataframe) == 0 : return True
17
- if not do_excel and not do_feather :
17
+ if not do_excel and not do_feather and not do_csv :
18
18
  return False
19
19
 
20
20
  if not path.endswith('/') : path +='/'
@@ -23,7 +23,7 @@ def write_results(dataframe: pd.DataFrame, path:str, filename:str, do_excel= Tru
23
23
 
24
24
  new_filename = filename
25
25
  i= 1
26
- while new_filename + '.xlsx' in os.listdir(path) or new_filename + '.feather' in os.listdir(path) :
26
+ while new_filename + '.xlsx' in os.listdir(path) or new_filename + '.feather' in os.listdir(path) or new_filename + '.csv' in os.listdir(path) :
27
27
  new_filename = filename + '_{0}'.format(i)
28
28
  i+=1
29
29
 
@@ -36,7 +36,14 @@ def write_results(dataframe: pd.DataFrame, path:str, filename:str, do_excel= Tru
36
36
  if 'clusters' in dataframe.columns :
37
37
  dataframe = dataframe.drop(['clusters'], axis= 1)
38
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')
39
+ if do_feather : dataframe.reset_index(drop=True).to_feather(path + new_filename + '.feather')
40
+ if do_csv : dataframe.reset_index(drop=True).to_csv(path + new_filename + '.csv', sep=";")
41
+ if do_excel :
42
+ if len(dataframe) < MAX_LEN_EXCEL :
43
+ dataframe.reset_index(drop=True).to_excel(path + new_filename + '.xlsx')
44
+ else :
45
+ print("Error : Table too big to be saved in excel format.")
46
+ return False
47
+
41
48
 
42
49
  return True
@@ -204,13 +204,9 @@ def launch_colocalisation(result_tables, result_dataframe, colocalisation_distan
204
204
  voxel_size = voxel_size1
205
205
 
206
206
  if shape1 != shape2 :
207
- print(shape1)
208
- print(shape2)
209
207
  raise MissMatchError("shape 1 different than shape 2")
210
208
  else :
211
209
  shape = shape1
212
- print(shape1)
213
- print(shape2)
214
210
 
215
211
  acquisition_couple = (acquisition1.at['acquisition_id'], acquisition2.at['acquisition_id'])
216
212
 
@@ -235,6 +231,9 @@ def launch_colocalisation(result_tables, result_dataframe, colocalisation_distan
235
231
  except MissMatchError as e :
236
232
  sg.popup(str(e))
237
233
  fraction_spots2_coloc_cluster1 = np.NaN
234
+ except TypeError : # Clusters not computed
235
+ fraction_spots2_coloc_cluster1 = np.NaN
236
+
238
237
 
239
238
  else : fraction_spots2_coloc_cluster1 = np.NaN
240
239
 
@@ -242,9 +241,12 @@ def launch_colocalisation(result_tables, result_dataframe, colocalisation_distan
242
241
  try :
243
242
  clusters2 = acquisition2['clusters'][:,:len(voxel_size)]
244
243
  fraction_spots1_coloc_cluster2 = spots_colocalisation(image_shape=shape, spot_list1=spots1, spot_list2=clusters2, distance= colocalisation_distance, voxel_size=voxel_size) / spot1_total
245
- except MissMatchError as e :
244
+ except MissMatchError as e :# Clusters not computed
246
245
  sg.popup(str(e))
247
246
  fraction_spots1_coloc_cluster2 = np.NaN
247
+ except TypeError :
248
+ fraction_spots1_coloc_cluster2 = np.NaN
249
+
248
250
 
249
251
  else : fraction_spots1_coloc_cluster2 = np.NaN
250
252
 
@@ -261,6 +263,4 @@ def launch_colocalisation(result_tables, result_dataframe, colocalisation_distan
261
263
  'fraction_spots1_coloc_cluster2' : [fraction_spots1_coloc_cluster2],
262
264
  })
263
265
 
264
- print(coloc_df.loc[:,['fraction_spots1_coloc_spots2','fraction_spots2_coloc_spots1', 'fraction_spots2_coloc_cluster1', 'fraction_spots1_coloc_cluster2']])
265
-
266
266
  return coloc_df
@@ -2,7 +2,6 @@
2
2
  Contains Napari wrappers to visualise and correct spots/clusters.
3
3
  """
4
4
 
5
-
6
5
  import numpy as np
7
6
  import scipy.ndimage as ndi
8
7
  import napari
@@ -96,15 +95,15 @@ def correct_spots(image, spots, voxel_size= (1,1,1), clusters= None, cluster_siz
96
95
  scale = compute_anisotropy_coef(voxel_size)
97
96
  try :
98
97
  Viewer = napari.Viewer(ndisplay=2, title= 'Spot correction', axis_labels=['z','y','x'], show= False)
99
- Viewer.add_image(image, scale=scale, name= "rna signal", blending= 'additive', colormap='red')
98
+ Viewer.add_image(image, scale=scale, name= "rna signal", blending= 'additive', colormap='red', contrast_limits=[image.min(), image.max()])
100
99
  other_colors = ['green', 'blue', 'gray', 'cyan', 'bop orange', 'bop purple'] * ((len(other_images)-1 // 7) + 1)
101
100
  for im, color in zip(other_images, other_colors) :
102
- Viewer.add_image(im, scale=scale, blending='additive', visible=False, colormap=color)
101
+ Viewer.add_image(im, scale=scale, blending='additive', visible=False, colormap=color, contrast_limits=[im.min(), im.max()])
103
102
  layer_offset = len(other_images)
104
103
 
105
104
  Viewer.add_points(spots, size = 5, scale=scale, face_color= 'green', opacity= 1, symbol= 'ring', name= 'single spots') # spots
106
105
  if type(clusters) != type(None) : Viewer.add_points(clusters[:,:dim], size = 10, scale=scale, face_color= 'blue', opacity= 0.7, symbol= 'diamond', name= 'foci', features= {"spot_number" : clusters[:,dim], "id" : clusters[:,dim+1]}, feature_defaults= {"spot_number" : 0, "id" : -1}) # cluster
107
- if type(cell_label) != type(None) and np.array_equal(nucleus_label, cell_label) : Viewer.add_labels(cell_label, scale=scale, opacity= 0.2, blending= 'additive')
106
+ 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')
108
107
  if type(nucleus_label) != type(None) : Viewer.add_labels(nucleus_label, scale=scale, opacity= 0.2, blending= 'additive')
109
108
 
110
109
  #prepare cluster update
@@ -136,4 +135,94 @@ def correct_spots(image, spots, voxel_size= (1,1,1), clusters= None, cluster_siz
136
135
 
137
136
  return new_spots, new_clusters
138
137
 
138
+ def show_segmentation(
139
+ nuc_image : np.ndarray,
140
+ nuc_label : np.ndarray,
141
+ cyto_image : np.ndarray = None,
142
+ cyto_label : np.ndarray = None,
143
+ ) :
144
+ dim = nuc_image.ndim
145
+
146
+ if type(cyto_image) != type(None) :
147
+ if cyto_image.ndim != nuc_image.ndim : raise ValueError("Cyto and Nuc dimensions missmatch.")
148
+ if type(cyto_label) == type(None) : raise ValueError("If cyto image is passed cyto label must be passed too.")
149
+
150
+ if dim == 3 and nuc_label.ndim == 2 :
151
+ nuc_label = np.repeat(
152
+ nuc_label[np.newaxis],
153
+ repeats= len(nuc_image),
154
+ axis=0
155
+ )
156
+ if type(cyto_label) != type(None) :
157
+
158
+ if type(cyto_image) == type(None) : raise ValueError("If cyto label is passed cyto image must be passed too.")
159
+
160
+ if dim == 3 and cyto_label.ndim == 2 :
161
+ cyto_label = np.repeat(
162
+ cyto_label[np.newaxis],
163
+ repeats= len(nuc_image),
164
+ axis=0
165
+ )
166
+
167
+ #Init Napari viewer
168
+ Viewer = napari.Viewer(ndisplay=2, title= 'Show segmentation', axis_labels=['z','y','x'] if dim == 3 else ['y', 'x'], show= False)
169
+
170
+ # Adding channels
171
+ Viewer.add_image(nuc_image, name= "nucleus signal", blending= 'additive', colormap='blue', contrast_limits=[nuc_image.min(), nuc_image.max()])
172
+ Viewer.add_labels(nuc_label, opacity= 0.5, blending= 'additive')
173
+
174
+ #Adding labels
175
+ 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()])
176
+ if type(cyto_label) != type(None) : Viewer.add_labels(cyto_label, opacity= 0.4, blending= 'additive')
177
+
178
+ #Launch Napari
179
+ Viewer.show(block=False)
180
+ napari.run()
181
+
182
+ new_nuc_label = Viewer.layers[1].data
183
+ if type(cyto_label) != type(None) : new_cyto_label = Viewer.layers[3].data
184
+
185
+ return new_nuc_label, new_cyto_label
186
+
139
187
 
188
+
189
+ def threshold_selection(
190
+ image : np.ndarray,
191
+ filtered_image : np.ndarray,
192
+ threshold_slider,
193
+ voxel_size : tuple,
194
+ ) :
195
+
196
+ """
197
+ To view code for spot selection please have a look at magicgui instance created with `detection._create_threshold_slider` which is then passed to this napari wrapper as 'threshold_slider' argument.
198
+ """
199
+
200
+
201
+ Viewer = napari.Viewer(title= "Small fish - Threshold selector", ndisplay=2, show=True)
202
+ Viewer.add_image(
203
+ data= image,
204
+ contrast_limits= [image.min(), image.max()],
205
+ name= "raw signal",
206
+ colormap= 'green',
207
+ scale= voxel_size,
208
+ blending= 'additive'
209
+ )
210
+ Viewer.add_image(
211
+ data= filtered_image,
212
+ contrast_limits= [filtered_image.min(), filtered_image.max()],
213
+ colormap= 'gray',
214
+ scale=voxel_size,
215
+ blending='additive'
216
+ )
217
+
218
+ Viewer.window.add_dock_widget(threshold_slider, name='threshold_selector')
219
+ threshold_slider() #First occurence with auto or entered threshold.
220
+ napari.run()
221
+
222
+ spots = Viewer.layers[-1].data.astype(int)
223
+ if len(spots) == 0 :
224
+ threshold = filtered_image.max()
225
+ else :
226
+ threshold = Viewer.layers[-1].properties.get('threshold')[0]
227
+
228
+ return spots, threshold
@@ -1,5 +1,5 @@
1
1
  import numpy as np
2
- import pandas as pd
2
+ import os
3
3
  import PySimpleGUI as sg
4
4
  from ..gui import _error_popup, _warning_popup, parameters_layout, add_header, prompt, prompt_with_help
5
5
 
@@ -253,6 +253,11 @@ def check_integrity(values: dict, do_dense_region_deconvolution, multichannel,se
253
253
  raise ParameterInputError("Channel to compute is out of range for image.\nPlease select from {0}".format(list(range(ch_len))))
254
254
  values['channel to compute'] = ch
255
255
 
256
+ #Spot extraction
257
+ if not os.path.isdir(values['spots_extraction_folder']) and values['spots_extraction_folder'] != '':
258
+ raise ParameterInputError("Incorrect spot extraction folder.")
259
+
260
+
256
261
  return values
257
262
 
258
263
 
@@ -269,4 +274,16 @@ def reorder_shape(shape, map) :
269
274
  np.array(shape)[source]
270
275
  )
271
276
 
272
- return new_shape
277
+ return new_shape
278
+
279
+ def clean_unused_parameters_cache(user_parameters: dict) :
280
+ """
281
+ Clean unused parameters that were set to None in previous run.
282
+ """
283
+ parameters = ['alpha', 'beta', 'gamma', 'cluster size', 'min number of spots']
284
+ for parameter in parameters :
285
+ if parameter in user_parameters.keys() :
286
+ if type(user_parameters[parameter]) == type(None) :
287
+ del user_parameters[parameter]
288
+
289
+ return user_parameters
@@ -6,6 +6,7 @@ from cellpose.core import use_gpu
6
6
  from skimage.measure import label
7
7
  from ..gui.layout import _segmentation_layout
8
8
  from ..gui import prompt, prompt_with_help, ask_cancel_segmentation
9
+ from ._napari_wrapper import show_segmentation as napari_show_segmentation
9
10
 
10
11
  import cellpose.models as models
11
12
  import numpy as np
@@ -31,7 +32,7 @@ def launch_segmentation(image: np.ndarray, user_parameters: dict) :
31
32
 
32
33
  while True : # Loop if show_segmentation
33
34
  #Default parameters
34
- cyto_model_name = user_parameters.setdefault('cyto_model_name', 'cyto2')
35
+ cyto_model_name = user_parameters.setdefault('cyto_model_name', 'cyto3')
35
36
  cyto_size = user_parameters.setdefault('cytoplasm diameter', 180)
36
37
  cytoplasm_channel = user_parameters.setdefault('cytoplasm channel', 0)
37
38
  nucleus_model_name = user_parameters.setdefault('nucleus_model_name', 'nuclei')
@@ -42,6 +43,7 @@ def launch_segmentation(image: np.ndarray, user_parameters: dict) :
42
43
  segment_only_nuclei = user_parameters.setdefault('Segment only nuclei', False)
43
44
  filename = user_parameters['filename']
44
45
  available_channels = list(range(image.shape[0]))
46
+ multichannel = user_parameters.get('multichannel')
45
47
 
46
48
 
47
49
  #Ask user for parameters
@@ -58,6 +60,7 @@ def launch_segmentation(image: np.ndarray, user_parameters: dict) :
58
60
  show_segmentation_preset=show_segmentation,
59
61
  segment_only_nuclei_preset=segment_only_nuclei,
60
62
  filename_preset=filename,
63
+ multichannel=multichannel,
61
64
  )
62
65
 
63
66
  event, values = prompt_with_help(layout, help='segmentation')
@@ -150,19 +153,18 @@ def launch_segmentation(image: np.ndarray, user_parameters: dict) :
150
153
  )
151
154
 
152
155
  finally : window.close()
153
- if show_segmentation or type(output_path) != type(None) :
154
- nuc_proj = image[nucleus_channel]
155
- im_proj = image[cytoplasm_channel]
156
- if im_proj.ndim == 3 :
157
- im_proj = stack.maximum_projection(im_proj)
158
- if nuc_proj.ndim == 3 :
159
- nuc_proj = stack.maximum_projection(nuc_proj)
160
- plot.plot_segmentation_boundary(nuc_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=show_segmentation, path_output=None, title= "Nucleus segmentation (blue)", remove_frame=False,)
161
- if type(nuc_path) != type(None) : plot.plot_segmentation_boundary(nuc_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=False, path_output=nuc_path, title= "Nucleus segmentation (blue)", remove_frame=True,)
162
- if not do_only_nuc :
163
- plot.plot_segmentation_boundary(im_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=show_segmentation, path_output=cyto_path, title="Cytoplasm Segmentation (red)", remove_frame=False)
164
- if type(cyto_path) != type(None) : plot.plot_segmentation_boundary(im_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=False, path_output=cyto_path, title="Cytoplasm Segmentation (red)", remove_frame=True)
156
+
165
157
  if show_segmentation :
158
+ nucleus_label, cytoplasm_label = napari_show_segmentation(
159
+ nuc_image=image[nucleus_channel],
160
+ nuc_label= nucleus_label,
161
+ cyto_image=image[cytoplasm_channel],
162
+ cyto_label=cytoplasm_label,
163
+ )
164
+
165
+ if nucleus_label.ndim == 3 : nucleus_label = np.max(nucleus_label, axis=0)
166
+ if cytoplasm_label.ndim == 3 : cytoplasm_label = np.max(cytoplasm_label, axis=0)
167
+
166
168
  layout = [
167
169
  [sg.Text("Proceed with current segmentation ?")],
168
170
  [sg.Button("Yes"), sg.Button("No")]
@@ -172,6 +174,20 @@ def launch_segmentation(image: np.ndarray, user_parameters: dict) :
172
174
  if event == "No" :
173
175
  continue
174
176
 
177
+ if type(output_path) != type(None) :
178
+ nuc_proj = image[nucleus_channel]
179
+ im_proj = image[cytoplasm_channel]
180
+ if im_proj.ndim == 3 :
181
+ im_proj = stack.maximum_projection(im_proj)
182
+ if nuc_proj.ndim == 3 :
183
+ nuc_proj = stack.maximum_projection(nuc_proj)
184
+ plot.plot_segmentation_boundary(nuc_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=False, path_output=nuc_path, title= "Nucleus segmentation (blue)", remove_frame=True,)
185
+ if not do_only_nuc :
186
+ plot.plot_segmentation_boundary(im_proj, cytoplasm_label, nucleus_label, boundary_size=2, contrast=True, show=False, path_output=cyto_path, title="Cytoplasm Segmentation (red)", remove_frame=True)
187
+
188
+
189
+
190
+
175
191
  if cytoplasm_label.max() == 0 : #No cell segmented
176
192
  layout = [
177
193
  [sg.Text("No cell segmented. Proceed anyway ?")],
@@ -236,7 +252,10 @@ def _segmentate_object(im, model_name, object_size_px, channels = [0,0]) :
236
252
 
237
253
  return label
238
254
 
239
- def _cast_segmentation_parameters(values) :
255
+ def _cast_segmentation_parameters(values:dict) :
256
+
257
+ values.setdefault('cytoplasm channel',0)
258
+ values.setdefault('nucleus channel',0)
240
259
 
241
260
  if values['cyto_model_name'] == '' :
242
261
  values['cyto_model_name'] = None
@@ -4,13 +4,14 @@ from ._preprocess import map_channels, prepare_image_detection, reorder_shape, r
4
4
  from .detection import ask_input_parameters, initiate_detection, launch_detection, launch_features_computation, get_nucleus_signal
5
5
  from ._segmentation import launch_segmentation
6
6
  from ._colocalisation import initiate_colocalisation, launch_colocalisation
7
+ from .spots import launch_spots_extraction
7
8
 
8
9
  import pandas as pd
9
10
  import PySimpleGUI as sg
10
11
 
11
12
  def add_detection(user_parameters, segmentation_done, acquisition_id, cytoplasm_label, nucleus_label) :
12
13
  """
13
- #TODO : list all keys added to user_parameters when returned
14
+ #TODO : list all keys added to user_parameters when returned.
14
15
  """
15
16
 
16
17
  new_results_df = pd.DataFrame()
@@ -31,7 +32,8 @@ def add_detection(user_parameters, segmentation_done, acquisition_id, cytoplasm_
31
32
  if user_parameters['Segmentation'] and not segmentation_done:
32
33
  im_seg = reorder_image_stack(map, user_parameters)
33
34
  cytoplasm_label, nucleus_label, user_parameters = launch_segmentation(im_seg, user_parameters=user_parameters)
34
-
35
+ elif segmentation_done :
36
+ pass
35
37
  else :
36
38
  cytoplasm_label, nucleus_label = None,None
37
39
 
@@ -83,7 +85,20 @@ def add_detection(user_parameters, segmentation_done, acquisition_id, cytoplasm_
83
85
  if ask_detection_confirmation(user_parameters.get('threshold')) : break
84
86
  else :
85
87
  break
86
-
88
+
89
+ if user_parameters['spots_extraction_folder'] != '' and type(user_parameters['spots_extraction_folder']) != type(None) :
90
+ if user_parameters['spots_filename'] != '' and type(user_parameters['spots_filename']) != type(None) :
91
+ if any((user_parameters['do_spots_excel'], user_parameters['do_spots_csv'], user_parameters['do_spots_feather'])) :
92
+ print((user_parameters['do_spots_excel'], user_parameters['do_spots_csv'], user_parameters['do_spots_feather']))
93
+ launch_spots_extraction(
94
+ acquisition_id=acquisition_id,
95
+ user_parameters=user_parameters,
96
+ image=image,
97
+ spots=spots,
98
+ nucleus_label= nucleus_label,
99
+ cell_label= cytoplasm_label,
100
+ )
101
+
87
102
  #Features computation
88
103
  new_results_df, new_cell_results_df = launch_features_computation(
89
104
  acquisition_id=acquisition_id,
@@ -107,9 +122,10 @@ def save_results(result_df, cell_result_df, coloc_df) :
107
122
  filename = dic['filename']
108
123
  do_excel = dic['Excel']
109
124
  do_feather = dic['Feather']
110
- sucess1 = write_results(result_df, path= path, filename=filename, do_excel= do_excel, do_feather= do_feather)
111
- sucess2 = write_results(cell_result_df, path= path, filename=filename + '_cell_result', do_excel= do_excel, do_feather= do_feather)
112
- sucess3 = write_results(coloc_df, path= path, filename=filename + '_coloc_result', do_excel= do_excel, do_feather= do_feather)
125
+ do_csv = dic['csv']
126
+ sucess1 = write_results(result_df, path= path, filename=filename, do_excel= do_excel, do_feather= do_feather, do_csv=do_csv)
127
+ sucess2 = write_results(cell_result_df, path= path, filename=filename + '_cell_result', do_excel= do_excel, do_feather= do_feather, do_csv=do_csv)
128
+ sucess3 = write_results(coloc_df, path= path, filename=filename + '_coloc_result', do_excel= do_excel, do_feather= do_feather, do_csv=do_csv)
113
129
  if sucess1 and sucess2 and sucess3 : sg.popup("Sucessfully saved at {0}.".format(path))
114
130
 
115
131
  else :
@@ -124,4 +140,35 @@ def compute_colocalisation(result_tables, result_dataframe) :
124
140
  else :
125
141
  res_coloc = launch_colocalisation(result_tables, result_dataframe=result_dataframe, colocalisation_distance=colocalisation_distance)
126
142
 
127
- return res_coloc
143
+ return res_coloc
144
+
145
+ def delete_acquisitions(selected_acquisitions : pd.DataFrame,
146
+ result_df : pd.DataFrame,
147
+ cell_result_df : pd.DataFrame,
148
+ coloc_df : pd.DataFrame
149
+ ) :
150
+
151
+ if len(result_df) == 0 :
152
+ sg.popup("No acquisition to delete.")
153
+ return result_df, cell_result_df, coloc_df
154
+
155
+ if len(selected_acquisitions) == 0 :
156
+ sg.popup("Please select the acquisitions you would like to delete.")
157
+ else :
158
+ acquisition_ids = list(result_df.iloc[list(selected_acquisitions)]['acquisition_id'])
159
+ result_drop_idx = result_df[result_df['acquisition_id'].isin(acquisition_ids)].index
160
+ print("{0} acquisitions deleted.".format(len(result_drop_idx)))
161
+
162
+ if len(cell_result_df) > 0 :
163
+ cell_result_df_drop_idx = cell_result_df[cell_result_df['acquisition_id'].isin(acquisition_ids)].index
164
+ print("{0} cells deleted.".format(len(cell_result_df_drop_idx)))
165
+ cell_result_df = cell_result_df.drop(cell_result_df_drop_idx, axis=0)
166
+
167
+ if len(coloc_df) > 0 :
168
+ coloc_df_drop_idx = coloc_df[(coloc_df["acquisition_id_1"].isin(acquisition_ids)) | (coloc_df['acquisition_id_2'].isin(acquisition_ids))].index
169
+ print("{0} coloc measurement deleted.".format(len(coloc_df_drop_idx)))
170
+ coloc_df = coloc_df.drop(coloc_df_drop_idx, axis=0)
171
+
172
+ result_df = result_df.drop(result_drop_idx, axis=0)
173
+
174
+ return result_df, cell_result_df, coloc_df
@@ -5,9 +5,13 @@ Contains code to handle detection as well as bigfish wrappers related to spot de
5
5
  from ._preprocess import ParameterInputError
6
6
  from ._preprocess import check_integrity, convert_parameters_types
7
7
  from ._signaltonoise import compute_snr_spots
8
- from ._detection_visualisation import correct_spots, _update_clusters
8
+ from ._napari_wrapper import correct_spots, _update_clusters, threshold_selection
9
9
  from ..gui import add_default_loading
10
10
  from ..gui import detection_parameters_promt, input_image_prompt
11
+ from .spots import compute_Spots
12
+ from magicgui import magicgui
13
+ from napari.layers import Image, Points
14
+ from napari.types import LayerDataTuple
11
15
 
12
16
  import numpy as np
13
17
  import pandas as pd
@@ -20,6 +24,7 @@ import bigfish.multistack as multistack
20
24
  import bigfish.classification as classification
21
25
  from bigfish.detection.spot_detection import get_object_radius_pixel
22
26
  from types import GeneratorType
27
+ from skimage.measure import regionprops
23
28
 
24
29
 
25
30
  def ask_input_parameters(ask_for_segmentation=True) :
@@ -284,7 +289,7 @@ def initiate_detection(user_parameters, segmentation_done, map, shape) :
284
289
  return user_parameters
285
290
 
286
291
  @add_default_loading
287
- def _launch_detection(image, image_input_values: dict, time_stack_gen=None) :
292
+ def _launch_detection(image, image_input_values: dict) :
288
293
 
289
294
  """
290
295
  Performs spots detection
@@ -297,25 +302,48 @@ def _launch_detection(image, image_input_values: dict, time_stack_gen=None) :
297
302
  spot_size = image_input_values.get('spot_size')
298
303
  log_kernel_size = image_input_values.get('log_kernel_size')
299
304
  minimum_distance = image_input_values.get('minimum_distance')
305
+ threshold_user_selection = image_input_values.get('Interactive threshold selector')
300
306
 
301
- if type(threshold) == type(None) :
302
- #detection
303
- if type(time_stack_gen) != type(None) :
304
- image_sample = time_stack_gen()
305
- else :
306
- image_sample = image
307
-
308
- threshold = compute_auto_threshold(image_sample, voxel_size=voxel_size, spot_radius=spot_size) * threshold_penalty
307
+ if type(threshold) == type(None) :
308
+ threshold = compute_auto_threshold(image, voxel_size=voxel_size, spot_radius=spot_size, log_kernel_size=log_kernel_size, minimum_distance=minimum_distance) * threshold_penalty
309
309
 
310
- spots = detection.detect_spots(
311
- images= image,
312
- threshold=threshold,
313
- return_threshold= False,
310
+ filtered_image = _apply_log_filter(
311
+ image=image,
312
+ voxel_size=voxel_size,
313
+ spot_radius=spot_size,
314
+ log_kernel_size = log_kernel_size,
315
+ )
316
+
317
+ local_maxima = _local_maxima_mask(
318
+ image_filtered=filtered_image,
314
319
  voxel_size=voxel_size,
315
- spot_radius= spot_size,
316
- log_kernel_size=log_kernel_size,
320
+ spot_radius=spot_size,
317
321
  minimum_distance=minimum_distance
322
+ )
323
+
324
+ if threshold_user_selection :
325
+
326
+ threshold_slider = _create_threshold_slider(
327
+ logfiltered_image=filtered_image,
328
+ local_maxima=local_maxima,
329
+ default=threshold,
330
+ min=filtered_image[local_maxima].min(),
331
+ max=filtered_image[local_maxima].max(),
332
+ voxel_size=voxel_size
318
333
  )
334
+
335
+ spots, threshold = threshold_selection(
336
+ image=image,
337
+ filtered_image=filtered_image,
338
+ threshold_slider=threshold_slider,
339
+ voxel_size=voxel_size
340
+ )
341
+ else :
342
+ spots = detection.spots_thresholding(
343
+ image=filtered_image,
344
+ mask_local_max=local_maxima,
345
+ threshold=threshold
346
+ )[0]
319
347
 
320
348
  return spots, threshold
321
349
 
@@ -357,15 +385,17 @@ def launch_post_detection(image, spots, image_input_values: dict,) :
357
385
  #features
358
386
  fov_res['spot_number'] = len(spots)
359
387
  snr_res = compute_snr_spots(image, spots, voxel_size, spot_size)
360
-
361
- if dim == 3 :
362
- Z,Y,X = list(zip(*spots))
363
- spots_values = image[Z,Y,X]
388
+ if len(spots) == 0 :
389
+ fov_res['spotsSignal_median'], fov_res['spotsSignal_mean'], fov_res['spotsSignal_std'] = np.NaN, np.NaN, np.NaN
364
390
  else :
365
- Y,X = list(zip(*spots))
366
- spots_values = image[Y,X]
367
-
368
- fov_res['spotsSignal_median'], fov_res['spotsSignal_mean'], fov_res['spotsSignal_std'] = np.median(spots_values), np.mean(spots_values), np.std(spots_values)
391
+ if dim == 3 :
392
+ Z,Y,X = list(zip(*spots))
393
+ spots_values = image[Z,Y,X]
394
+ else :
395
+ Y,X = list(zip(*spots))
396
+ spots_values = image[Y,X]
397
+ fov_res['spotsSignal_median'], fov_res['spotsSignal_mean'], fov_res['spotsSignal_std'] = np.median(spots_values), np.mean(spots_values), np.std(spots_values)
398
+
369
399
  fov_res['median_pixel'] = np.median(image)
370
400
  fov_res['mean_pixel'] = np.mean(image)
371
401
 
@@ -447,6 +477,7 @@ def launch_cell_extraction(acquisition_id, spots, clusters, image, nucleus_signa
447
477
  #Nucleus features : area is computed in bigfish
448
478
  features_names += ['nucleus_mean_signal', 'nucleus_median_signal', 'nucleus_max_signal', 'nucleus_min_signal']
449
479
  features_names += ['snr_mean', 'snr_median', 'snr_std']
480
+ features_names += ['cell_center_coord','foci_number','foci_in_nuc_number']
450
481
 
451
482
  result_frame = pd.DataFrame()
452
483
 
@@ -483,6 +514,30 @@ def launch_cell_extraction(acquisition_id, spots, clusters, image, nucleus_signa
483
514
  compute_topography=True
484
515
  )
485
516
 
517
+ #center of cell coordinates
518
+ local_cell_center = regionprops(
519
+ label_image=cell_mask.astype(int)
520
+ )[0]['centroid']
521
+ cell_center = (local_cell_center[0] + min_y, local_cell_center[1] + min_x)
522
+
523
+ #foci in nucleus
524
+ if type(foci_coords) != type(None) :
525
+ if len(foci_coords) == 0 :
526
+ foci_number = 0
527
+ foci_in_nuc_number = 0
528
+ else :
529
+ foci_number = len(foci_coords)
530
+ foci_index = list(zip(*foci_coords))
531
+ if len(foci_index) == 5 :
532
+ foci_index = foci_index[1:3]
533
+ elif len(foci_index) == 4 :
534
+ foci_index = foci_index[:2]
535
+ else : raise AssertionError("Impossible number of dim for foci : ", len(foci_index))
536
+ foci_in_nuc_number = nuc_mask[tuple(foci_index)].astype(bool).sum()
537
+ else :
538
+ foci_number = np.NaN
539
+ foci_in_nuc_number = np.NaN
540
+
486
541
  #Signal to noise
487
542
  snr_dict = _compute_cell_snr(
488
543
  image,
@@ -499,6 +554,7 @@ def launch_cell_extraction(acquisition_id, spots, clusters, image, nucleus_signa
499
554
  features = list(features)
500
555
  features += [np.mean(nuc_signal), np.median(nuc_signal), np.max(nuc_signal), np.min(nuc_signal)]
501
556
  features += [snr_mean, snr_median, snr_std]
557
+ features += [cell_center, foci_number, foci_in_nuc_number]
502
558
 
503
559
  features = [acquisition_id, cell_id, cell_bbox] + features
504
560
 
@@ -601,9 +657,9 @@ def launch_features_computation(acquisition_id, image, nucleus_signal, spots, cl
601
657
  if user_parameters['Cluster computation'] :
602
658
  frame_results['cluster_number'] = len(clusters)
603
659
  if dim == 3 :
604
- frame_results['total_spots_in_clusters'] = clusters.sum(axis=0)[3]
660
+ frame_results['total_spots_in_clusters'] = clusters.sum(axis=0)[3] if len(clusters) >0 else 0
605
661
  else :
606
- frame_results['total_spots_in_clusters'] = clusters.sum(axis=0)[2]
662
+ frame_results['total_spots_in_clusters'] = clusters.sum(axis=0)[2] if len(clusters) >0 else 0
607
663
 
608
664
  if type(cell_label) != type(None) and type(nucleus_label) != type(None):
609
665
  cell_result_dframe = launch_cell_extraction(
@@ -673,7 +729,7 @@ def get_nucleus_signal(image, other_images, user_parameters) :
673
729
  return np.zeros(shape=image.shape)
674
730
 
675
731
  if rna_signal_channel == nucleus_signal_channel :
676
- nucleus_signal == image
732
+ nucleus_signal = image
677
733
 
678
734
  elif nucleus_signal_channel > rna_signal_channel :
679
735
  nucleus_signal_channel -=1
@@ -684,4 +740,83 @@ def get_nucleus_signal(image, other_images, user_parameters) :
684
740
 
685
741
  return nucleus_signal
686
742
  else :
687
- return image
743
+ return image
744
+
745
+ def _create_threshold_slider(
746
+ logfiltered_image : np.ndarray,
747
+ local_maxima : np.ndarray,
748
+ default : int,
749
+ min : int,
750
+ max : int,
751
+ voxel_size
752
+ ) :
753
+
754
+ if isinstance(default, float) : default = round(default)
755
+
756
+ @magicgui(
757
+ threshold={'widget_type' : 'Slider', 'value' : default, 'min' : min, 'max' : max},
758
+ auto_call=True
759
+ )
760
+ def threshold_slider(threshold: int) -> LayerDataTuple:
761
+ spots = detection.spots_thresholding(
762
+ image=logfiltered_image,
763
+ mask_local_max=local_maxima,
764
+ threshold=threshold
765
+ )[0]
766
+ layer_args = {
767
+ 'size': 7,
768
+ 'scale' : voxel_size,
769
+ 'face_color' : 'transparent',
770
+ 'edge_color' : 'blue',
771
+ 'symbol' : 'ring',
772
+ 'opacity' : 0.7,
773
+ 'blending' : 'additive',
774
+ 'name': 'single spots',
775
+ 'features' : {'threshold' : threshold}
776
+ }
777
+ return (spots, layer_args , 'points')
778
+ return threshold_slider
779
+
780
+ def _apply_log_filter(
781
+ image: np.ndarray,
782
+ voxel_size : tuple,
783
+ spot_radius : tuple,
784
+ log_kernel_size,
785
+
786
+ ) :
787
+ """
788
+ Apply spot detection steps until local maxima step (just before final threshold).
789
+ Return filtered image.
790
+ """
791
+
792
+ ndim = image.ndim
793
+
794
+ if type(log_kernel_size) == type(None) :
795
+ log_kernel_size = get_object_radius_pixel(
796
+ voxel_size_nm=voxel_size,
797
+ object_radius_nm=spot_radius,
798
+ ndim=ndim)
799
+
800
+
801
+ image_filtered = stack.log_filter(image, log_kernel_size)
802
+
803
+ return image_filtered
804
+
805
+ def _local_maxima_mask(
806
+ image_filtered: np.ndarray,
807
+ voxel_size : tuple,
808
+ spot_radius : tuple,
809
+ minimum_distance
810
+
811
+ ) :
812
+
813
+ ndim = image_filtered.ndim
814
+
815
+ if type(minimum_distance) == type(None) :
816
+ minimum_distance = get_object_radius_pixel(
817
+ voxel_size_nm=voxel_size,
818
+ object_radius_nm=spot_radius,
819
+ ndim=ndim)
820
+ mask_local_max = detection.local_maximum_detection(image_filtered, minimum_distance)
821
+
822
+ return mask_local_max.astype(bool)
@@ -2,7 +2,8 @@
2
2
  import pandas as pd
3
3
  import PySimpleGUI as sg
4
4
  from ..gui import hub_prompt
5
- from .actions import add_detection, save_results, compute_colocalisation
5
+ from .actions import add_detection, save_results, compute_colocalisation, delete_acquisitions
6
+ from ._preprocess import clean_unused_parameters_cache
6
7
 
7
8
  #'Global' parameters
8
9
  user_parameters = dict() # Very important object containg all choice from user that will influence the behavior of the main loop.
@@ -15,10 +16,15 @@ cytoplasm_label = None
15
16
  nucleus_label = None
16
17
 
17
18
  while True : #Break this loop to close small_fish
19
+ result_df = result_df.reset_index(drop=True)
20
+ cell_result_df = cell_result_df.reset_index(drop=True)
21
+ coloc_df = coloc_df.reset_index(drop=True)
18
22
  try :
19
23
  event, values = hub_prompt(result_df, segmentation_done)
20
24
 
21
25
  if event == 'Add detection' :
26
+ user_parameters = clean_unused_parameters_cache(user_parameters)
27
+
22
28
 
23
29
  new_result_df, new_cell_result_df, acquisition_id, user_parameters, segmentation_done, cytoplasm_label, nucleus_label = add_detection(
24
30
  user_parameters=user_parameters,
@@ -64,8 +70,8 @@ while True : #Break this loop to close small_fish
64
70
  nucleus_label = None
65
71
 
66
72
  elif event == "Delete acquisitions" :
67
- #TODO
68
- pass
73
+ selected_acquisitions = values.setdefault('result_table', []) #Contains the lines selected by the user on the sum-up array.
74
+ result_df, cell_result_df, coloc_df = delete_acquisitions(selected_acquisitions, result_df, cell_result_df, coloc_df)
69
75
 
70
76
  elif event == "Batch detection" :
71
77
  #TODO
@@ -0,0 +1,71 @@
1
+ """
2
+ Sub-module to handle individual spot extraction.
3
+
4
+ """
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ from ..interface.output import write_results
9
+
10
+ def launch_spots_extraction(
11
+ acquisition_id,
12
+ user_parameters,
13
+ image,
14
+ spots,
15
+ nucleus_label,
16
+ cell_label,
17
+ ) :
18
+ Spots = compute_Spots(
19
+ acquisition_id=acquisition_id,
20
+ image=image,
21
+ spots=spots,
22
+ nucleus_label=nucleus_label,
23
+ cell_label=cell_label,
24
+ )
25
+
26
+ did_output = write_results(
27
+ Spots,
28
+ path= user_parameters['spots_extraction_folder'],
29
+ filename= user_parameters['spots_filename'],
30
+ do_excel=user_parameters['do_spots_excel'],
31
+ do_csv=user_parameters['do_spots_csv'],
32
+ do_feather=user_parameters['do_spots_feather'],
33
+ )
34
+
35
+ if did_output : print("Individual spots extracted at {0}".format(user_parameters['spots_extraction_folder']))
36
+
37
+ def compute_Spots(
38
+ acquisition_id : int,
39
+ image : np.ndarray,
40
+ spots : np.ndarray,
41
+ nucleus_label = None,
42
+ cell_label = None,
43
+ ) :
44
+
45
+ index = list(zip(*spots))
46
+ index = tuple(index)
47
+ spot_intensities_list = list(image[index])
48
+ if type(nucleus_label) != type(None) :
49
+ in_nuc_list = list(nucleus_label.astype(bool)[index[-2:]]) #Only plane coordinates
50
+ else :
51
+ in_nuc_list = np.NaN
52
+ if type(cell_label) != type(None) :
53
+ cell_label_list = list(cell_label[index[-2:]]) #Only plane coordinates
54
+ else :
55
+ cell_label_list = np.NaN
56
+ id_list = np.arange(len(spots))
57
+
58
+ coord_list = list(zip(*index))
59
+
60
+ Spots = pd.DataFrame({
61
+ 'acquisition_id' : [acquisition_id] * len(spots),
62
+ 'spot_id' : id_list,
63
+ 'intensity' : spot_intensities_list,
64
+ 'cell_label' : cell_label_list,
65
+ 'in_nucleus' : in_nuc_list,
66
+ 'coordinates' : coord_list,
67
+ })
68
+
69
+ return Spots
70
+
71
+
@@ -1,51 +0,0 @@
1
- # Small Fish
2
- **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.
3
-
4
- Cell segmentation (**2D**) is peformed using *cellpose* (published work) : https://github.com/MouseLand/cellpose; compatible with your own cellpose models.
5
-
6
- Spot detection is performed via *big-fish* (published work) : https://github.com/fish-quant/big-fish
7
-
8
- Time stacks are not yet supported.
9
-
10
- ## What can you do with small fish ?
11
-
12
- - Single molecule quantification (including a lot of spatial features)
13
- - Foci/Transcription site quantification
14
- - Nuclear signal quantification
15
- - Signal to noise analysis
16
- - multichannel colocalisation
17
-
18
- ## Installation
19
-
20
- It is higly recommanded to create a specific [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) or [virtual](https://docs.python.org/3.6/library/venv.html) environnement to install small fish.
21
-
22
- ```bash
23
- conda create -n small_fish python=3.8
24
- activate small_fish
25
- ```
26
- Then download the small_fish package :
27
- ```bash
28
- pip install small_fish_gui
29
- ```
30
- <b> (Recommended) </b> Results visualisation is achieved through *Napari* which you can install with :
31
-
32
- ```bash
33
- pip install napari[all]
34
- ```
35
-
36
- ## Run Small fish
37
-
38
- First activate your python environnement :
39
- ```bash
40
- activate small_fish
41
- ```
42
- Then launch Small fish :
43
- ```bash
44
- python -m small_fish_gui
45
- ```
46
- ## Developpement
47
-
48
- Optional features to include in future versions :
49
- - batch processing
50
- - time stack (which would include cell tracking)
51
- - 3D segmentation
@@ -1,15 +0,0 @@
1
- import sys
2
- import os
3
- import PySimpleGUI as sg
4
-
5
- def main():
6
- import small_fish_gui.pipeline.main
7
-
8
-
9
- if __name__ == "__main__":
10
- try :
11
- sys.exit(main())
12
- except Exception as error :
13
- sg.popup("Sorry. Something went wrong...\nIf you have some time to spare could you please communicate the error you encountered (next window) on :\nhttps://github.com/2Echoes/small_fish/issues.")
14
- sg.popup_error_with_traceback(error)
15
- raise error
@@ -1,7 +0,0 @@
1
- import sys
2
-
3
- def main():
4
- import small_fish.pipeline.main
5
-
6
- if __name__ == "__main__":
7
- sys.exit(main())
File without changes
File without changes