small-fish-gui 1.6.0__tar.gz → 1.7.1__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 (54) hide show
  1. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/PKG-INFO +1 -1
  2. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/pyproject.toml +1 -1
  3. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/__init__.py +1 -1
  4. small_fish_gui-1.7.1/src/small_fish_gui/gui/_napari_widgets.py +93 -0
  5. small_fish_gui-1.6.0/src/small_fish_gui/pipeline/_napari_wrapper.py → small_fish_gui-1.7.1/src/small_fish_gui/gui/napari.py +38 -12
  6. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/prompts.py +2 -2
  7. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/output.py +19 -15
  8. small_fish_gui-1.7.1/src/small_fish_gui/pipeline/_colocalisation.py +429 -0
  9. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/_segmentation.py +2 -2
  10. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/actions.py +55 -21
  11. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/detection.py +5 -2
  12. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/main.py +37 -14
  13. small_fish_gui-1.6.0/src/small_fish_gui/pipeline/_colocalisation.py +0 -275
  14. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/LICENSE +0 -0
  15. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/README.md +0 -0
  16. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/.github/workflows/python-publish.yml +0 -0
  17. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/.readthedocs.yaml +0 -0
  18. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/LICENSE +0 -0
  19. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/README.md +0 -0
  20. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/Segmentation example.jpg +0 -0
  21. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/__main__.py +0 -0
  22. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/__init__.py +0 -0
  23. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/input.py +0 -0
  24. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/integrity.py +0 -0
  25. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/output.py +0 -0
  26. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/pipeline.py +0 -0
  27. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/prompt.py +0 -0
  28. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/test.py +0 -0
  29. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/update.py +0 -0
  30. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/utils.py +0 -0
  31. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/values.py +0 -0
  32. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/values.txt +0 -0
  33. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/docs/conf.py +0 -0
  34. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/__init__.py +0 -0
  35. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/animation.py +0 -0
  36. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/general_help_screenshot.png +0 -0
  37. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/help_module.py +0 -0
  38. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/layout.py +0 -0
  39. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/mapping_help_screenshot.png +0 -0
  40. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/segmentation_help_screenshot.png +0 -0
  41. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/__init__.py +0 -0
  42. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/image.py +0 -0
  43. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/parameters.py +0 -0
  44. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/testing.py +0 -0
  45. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/napari_detection_example.png +0 -0
  46. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/__init__.py +0 -0
  47. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/_custom_errors.py +0 -0
  48. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/_preprocess.py +0 -0
  49. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/_signaltonoise.py +0 -0
  50. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/spots.py +0 -0
  51. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/test.py +0 -0
  52. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/utils.py +0 -0
  53. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/requirements.txt +0 -0
  54. {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/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.6.0
3
+ Version: 1.7.1
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.6.0"
7
+ version = "1.7.1"
8
8
  authors = [
9
9
  { name="Slimani Floric", email="floric.slimani@live.com" },
10
10
  ]
@@ -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.6.0"
41
+ __version__ = "1.7.1"
@@ -0,0 +1,93 @@
1
+ """
2
+ Submodule containing custom class for napari widgets
3
+ """
4
+ import numpy as np
5
+ from napari.layers import Labels
6
+ from magicgui import magicgui
7
+
8
+ class cell_label_eraser :
9
+ """
10
+ Must be instanced within Napari Viewer definition range for update connection to work, cell deletion works fine anyway.
11
+ """
12
+ def __init__(self, label_list: 'list[Labels]'):
13
+ self.widget = self._create_eraser(label_list)
14
+ for label_layer in label_list :
15
+ label_layer.events.selected_label.connect((self, 'update'))
16
+
17
+ def update(self, event) :
18
+ layer : Labels = event.source
19
+ new_label = layer.selected_label
20
+ self.widget.label_number.value = new_label
21
+ self.widget.update()
22
+
23
+ def _create_eraser(self, label_list: 'list[Labels]') :
24
+ @magicgui(
25
+ call_button="Delete cell",
26
+ auto_call=False
27
+ )
28
+ def label_eraser(label_number: int) -> None :
29
+
30
+ for i, label in enumerate(label_list) :
31
+ label_list[i].data[label.data == label_number] = 0
32
+ label.refresh()
33
+
34
+ return label_eraser
35
+
36
+
37
+
38
+ class free_label_picker :
39
+ def __init__(self, label_list):
40
+ self.widget = self._create_free_label_picker(label_list)
41
+
42
+ def _create_free_label_picker(self, label_list : 'list[Labels]') :
43
+ @magicgui(
44
+ call_button="Pick free label",
45
+ auto_call=False
46
+ )
47
+ def label_pick()->None :
48
+ max_list = [label_layer.data.max() for label_layer in label_list]
49
+ new_label = max(max_list) + 1
50
+ for label_layer in label_list :
51
+ label_layer.selected_label = new_label
52
+ label_layer.refresh()
53
+
54
+ return label_pick
55
+
56
+
57
+ class segmentation_reseter :
58
+ def __init__(self, label_list):
59
+ self.save = self._get_save(label_list)
60
+ self.widget = self._create_widget(label_list)
61
+
62
+
63
+ def _get_save(self, label_list : 'list[Labels]') :
64
+ return [label.data.copy() for label in label_list]
65
+
66
+ def _create_widget(self, label_list: 'list[Labels]') :
67
+ @magicgui(
68
+ call_button= 'Reset segmentation',
69
+ auto_call=False,
70
+ )
71
+ def reset_segmentation() -> None:
72
+ for save_data, layer in zip(self.save, label_list) :
73
+ layer.data = save_data.copy()
74
+ layer.refresh()
75
+
76
+ return reset_segmentation
77
+
78
+ class changes_propagater :
79
+ def __init__(self, label_list):
80
+ self.widget = self._create_widget(label_list)
81
+
82
+ def _create_widget(self, label_list: 'list[Labels]') :
83
+ @magicgui(
84
+ call_button='Apply changes',
85
+ auto_call=False,
86
+ )
87
+ def apply_changes() -> None:
88
+ for layer in label_list :
89
+ slices = layer.data.shape[0]
90
+ layer_2D = np.max(layer.data, axis=0)
91
+ layer.data = np.repeat(layer_2D[np.newaxis], slices, axis=0)
92
+ layer.refresh()
93
+ return apply_changes
@@ -7,11 +7,19 @@ import napari.types
7
7
  import numpy as np
8
8
  import napari
9
9
 
10
+ from napari.layers import Labels
11
+
12
+ from magicgui import widgets
13
+ from magicgui import magicgui
14
+
10
15
  from bigfish.stack import check_parameter
16
+ from ._napari_widgets import cell_label_eraser, segmentation_reseter, changes_propagater, free_label_picker
11
17
  from ..utils import compute_anisotropy_coef
12
- from ._colocalisation import spots_multicolocalisation
18
+ from ..pipeline._colocalisation import spots_multicolocalisation
19
+
20
+ #Post detection
13
21
 
14
- def _update_clusters(new_clusters: np.ndarray, spots: np.ndarray, voxel_size, cluster_size, min_spot_number, shape) :
22
+ def _update_clusters(new_clusters: np.ndarray, spots: np.ndarray, voxel_size, cluster_size, shape) :
15
23
  if len(new_clusters) == 0 : return new_clusters
16
24
  if len(spots) == 0 : return np.empty(shape=(0,2+len(voxel_size)))
17
25
 
@@ -28,7 +36,7 @@ def _update_clusters(new_clusters: np.ndarray, spots: np.ndarray, voxel_size, cl
28
36
  new_clusters[:,-2] = spots_multicolocalisation(new_clusters[:,:-2], spots, radius_nm= cluster_size, voxel_size=voxel_size, image_shape=shape)
29
37
 
30
38
  # delete too small clusters
31
- new_clusters = np.delete(new_clusters, new_clusters[:,-2] < min_spot_number, 0)
39
+ new_clusters = np.delete(new_clusters, new_clusters[:,-2] == 0, 0)
32
40
 
33
41
  return new_clusters
34
42
 
@@ -96,11 +104,15 @@ def correct_spots(image, spots, voxel_size= (1,1,1), clusters= None, cluster_siz
96
104
 
97
105
  if type(clusters) != type(None) :
98
106
  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)
107
+ new_clusters = _update_clusters(new_clusters, new_spots, voxel_size=voxel_size, cluster_size=cluster_size, shape=image.shape)
100
108
  else : new_clusters = None
101
109
 
102
110
  return new_spots, new_clusters
103
111
 
112
+ # Segmentation
113
+
114
+
115
+
104
116
  def show_segmentation(
105
117
  nuc_image : np.ndarray,
106
118
  nuc_label : np.ndarray,
@@ -131,21 +143,35 @@ def show_segmentation(
131
143
  )
132
144
 
133
145
  #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)
146
+ Viewer = napari.Viewer(ndisplay=2, title= 'Show segmentation', axis_labels=['z','y','x'] if dim == 3 else ['y', 'x'])
135
147
 
136
- # Adding channels
148
+ # Adding nuclei
137
149
  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',)
150
+ nuc_label_layer = Viewer.add_labels(nuc_label, opacity= 0.6, name= 'nucleus_label',)
139
151
  nuc_label_layer.preserve_labels = True
152
+ labels_layer_list = [nuc_label_layer]
140
153
 
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()])
154
+ #Adding cytoplasm
143
155
  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')
156
+ Viewer.add_image(cyto_image, name= "cytoplasm signal", blending= 'additive', colormap='red', contrast_limits=[cyto_image.min(), cyto_image.max()])
157
+ cyto_label_layer = Viewer.add_labels(cyto_label, opacity= 0.6, name= 'cytoplasm_label')
145
158
  cyto_label_layer.preserve_labels = True
146
-
159
+ labels_layer_list += [cyto_label_layer]
160
+
161
+ #Adding widget
162
+ label_eraser = cell_label_eraser(labels_layer_list)
163
+ label_picker = free_label_picker(labels_layer_list)
164
+ label_reseter = segmentation_reseter(labels_layer_list)
165
+ changes_applier = changes_propagater(labels_layer_list)
166
+
167
+ buttons_container = widgets.Container(widgets=[label_picker.widget, changes_applier.widget, label_reseter.widget], labels=False, layout='horizontal')
168
+ tools_container = widgets.Container(
169
+ widgets = [buttons_container, label_eraser.widget],
170
+ labels=False,
171
+ )
172
+ Viewer.window.add_dock_widget(tools_container, name='SmallFish', area='left')
173
+
147
174
  #Launch Napari
148
- Viewer.show(block=False)
149
175
  napari.run()
150
176
 
151
177
  new_nuc_label = Viewer.layers['nucleus_label'].data
@@ -136,8 +136,8 @@ def output_image_prompt(filename) :
136
136
  excel_filename = values['filename'] + ".xlsx"
137
137
  feather_filename = values['filename'] + ".feather"
138
138
 
139
- if not values['Excel'] and not values['Feather'] :
140
- sg.popup("Please check at least one box : Excel/Feather")
139
+ if not values['Excel'] and not values['Feather'] and not values['csv'] :
140
+ sg.popup("Please check at least one box : Excel/Feather/csv")
141
141
  relaunch = True
142
142
  elif not os.path.isdir(values['folder']) :
143
143
  sg.popup("Incorrect folder")
@@ -10,11 +10,9 @@ 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, do_csv=False, overwrite=False) :
13
+ def write_results(dataframe: pd.DataFrame, path:str, filename:str, do_excel= True, do_feather= False, do_csv=False, overwrite=False, reset_index=True) :
14
14
  check_parameter(dataframe= pd.DataFrame, path= str, filename = str, do_excel = bool, do_feather = bool)
15
15
 
16
- dataframe.columns = dataframe.columns.astype(str) # assert columns header are string for feather
17
-
18
16
  if len(dataframe) == 0 : return True
19
17
  if not do_excel and not do_feather and not do_csv :
20
18
  return False
@@ -22,32 +20,38 @@ def write_results(dataframe: pd.DataFrame, path:str, filename:str, do_excel= Tru
22
20
  if not path.endswith('/') : path +='/'
23
21
  assert os.path.isdir(path)
24
22
 
23
+ #Casting cols name to str for feather format
24
+ index_dim = dataframe.columns.nlevels
25
+ if index_dim == 1 :
26
+ dataframe.columns = dataframe.columns.astype(str)
27
+ else :
28
+ casted_cols = [dataframe.columns.get_level_values(level).astype(str) for level in range(index_dim)]
29
+ casted_cols = zip(*casted_cols)
30
+ dataframe.columns = pd.MultiIndex.from_tuples(casted_cols)
25
31
 
26
32
  new_filename = filename
27
33
  i= 1
28
34
 
29
35
  if not overwrite :
30
- while new_filename + '.xlsx' in os.listdir(path) or new_filename + '.feather' in os.listdir(path) or new_filename + '.csv' in os.listdir(path) :
36
+ while new_filename + '.xlsx' in os.listdir(path) or new_filename + '.parquet' in os.listdir(path) or new_filename + '.csv' in os.listdir(path) :
31
37
  new_filename = filename + '_{0}'.format(i)
32
38
  i+=1
33
39
 
34
- if 'image' in dataframe.columns :
35
- dataframe = dataframe.drop(['image'], axis=1)
40
+ COLUMNS_TO_DROP = ['image', 'spots', 'clusters', 'rna_coords', 'cluster_coords']
41
+ for col in COLUMNS_TO_DROP :
42
+ if col in dataframe.columns : dataframe = dataframe.drop(columns=col)
36
43
 
37
- if 'spots' in dataframe.columns :
38
- dataframe = dataframe.drop(['spots'], axis= 1)
39
-
40
- if 'clusters' in dataframe.columns :
41
- dataframe = dataframe.drop(['clusters'], axis= 1)
44
+ if reset_index : dataframe = dataframe.reset_index(drop=True)
42
45
 
43
- if do_feather : dataframe.reset_index(drop=True).to_feather(path + new_filename + '.feather')
44
- if do_csv : dataframe.reset_index(drop=True).to_csv(path + new_filename + '.csv', sep=";")
46
+ if do_csv : dataframe.to_csv(path + new_filename + '.csv', sep=";")
45
47
  if do_excel :
46
48
  if len(dataframe) < MAX_LEN_EXCEL :
47
- dataframe.reset_index(drop=True).to_excel(path + new_filename + '.xlsx')
49
+ dataframe.to_excel(path + new_filename + '.xlsx')
48
50
  else :
49
51
  print("Error : Table too big to be saved in excel format.")
50
52
  return False
51
53
 
54
+ if do_feather :
55
+ dataframe.to_parquet(path + new_filename + '.parquet')
52
56
 
53
- return True
57
+ return True