small-fish-gui 1.8.0__tar.gz → 1.9.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 (52) hide show
  1. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/PKG-INFO +3 -2
  2. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/pyproject.toml +1 -1
  3. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/__init__.py +1 -1
  4. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/pipeline.py +4 -1
  5. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/prompt.py +10 -10
  6. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/layout.py +7 -11
  7. small_fish_gui-1.8.0/src/small_fish_gui/gui/napari.py → small_fish_gui-1.9.0/src/small_fish_gui/gui/napari_visualiser.py +159 -21
  8. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/prompts.py +1 -1
  9. small_fish_gui-1.9.0/src/small_fish_gui/gui/testing.ipynb +422 -0
  10. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/hints.py +1 -1
  11. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/interface/inoutput.py +59 -1
  12. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/_colocalisation.py +66 -41
  13. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/actions.py +34 -24
  14. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/detection.py +66 -15
  15. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/main.py +7 -7
  16. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/segmentation.py +1 -1
  17. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/spots.py +7 -0
  18. small_fish_gui-1.9.0/src/small_fish_gui/pipeline/testing.ipynb +2067 -0
  19. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/LICENSE +0 -0
  20. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/README.md +0 -0
  21. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/.github/workflows/python-publish.yml +0 -0
  22. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/.readthedocs.yaml +0 -0
  23. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/LICENSE +0 -0
  24. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/README.md +0 -0
  25. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/Segmentation example.jpg +0 -0
  26. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/__main__.py +0 -0
  27. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/__init__.py +0 -0
  28. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/input.py +0 -0
  29. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/integrity.py +0 -0
  30. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/test.py +0 -0
  31. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/update.py +0 -0
  32. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/utils.py +0 -0
  33. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/batch/values.txt +0 -0
  34. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/__init__.py +0 -0
  35. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/_napari_widgets.py +0 -0
  36. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/animation.py +0 -0
  37. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/help_module.py +0 -0
  38. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/screenshot/general_help_screenshot.png +0 -0
  39. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/screenshot/mapping_help_screenshot.png +0 -0
  40. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/gui/screenshot/segmentation_help_screenshot.png +0 -0
  41. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/interface/__init__.py +0 -0
  42. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/interface/image.py +0 -0
  43. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/interface/testing.py +0 -0
  44. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/napari_detection_example.png +0 -0
  45. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/__init__.py +0 -0
  46. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/_custom_errors.py +0 -0
  47. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/_preprocess.py +0 -0
  48. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/_signaltonoise.py +0 -0
  49. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/test.py +0 -0
  50. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/pipeline/utils.py +0 -0
  51. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/requirements.txt +0 -0
  52. {small_fish_gui-1.8.0 → small_fish_gui-1.9.0}/src/small_fish_gui/utils.py +0 -0
@@ -1,10 +1,11 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: small_fish_gui
3
- Version: 1.8.0
3
+ Version: 1.9.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
7
7
  Author-email: Slimani Floric <floric.slimani@live.com>
8
+ License-File: LICENSE
8
9
  Classifier: License :: OSI Approved :: BSD License
9
10
  Classifier: Operating System :: OS Independent
10
11
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "small_fish_gui"
7
- version = "1.8.0"
7
+ version = "1.9.0"
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.8.0"
41
+ __version__ = "1.9.0"
@@ -137,7 +137,10 @@ def batch_pipeline(
137
137
  raise(error)
138
138
 
139
139
  if parameters['save detection'] :
140
- if parameters['do_cluster_computation'] : spots_list = [spots, clusters[:,:parameters['dim']]]
140
+ if parameters['do_cluster_computation'] :
141
+ if len(clusters) > 0 :
142
+ spots_list = [spots, clusters[:,:parameters['dim']]]
143
+ else : spots_list = [spots]
141
144
  else : spots_list = [spots]
142
145
  output_spot_tiffvisual(
143
146
  image,
@@ -45,18 +45,18 @@ def batch_promp(
45
45
  #Input tab
46
46
  input_layout = _input_parameters_layout(
47
47
  ask_for_segmentation=True,
48
- is_3D_stack_preset= preset.setdefault("3D stack" ,False),
48
+ is_3D_stack_preset= preset.setdefault("is_3D_stack" ,False),
49
49
  time_stack_preset=False,
50
- multichannel_preset=preset.setdefault("multichannel" ,False),
51
- do_dense_regions_deconvolution_preset=preset.setdefault("Dense regions deconvolution" ,False),
50
+ multichannel_preset=preset.setdefault("is_multichannel" ,False),
51
+ do_dense_regions_deconvolution_preset=preset.setdefault("do_dense_regions_deconvolution" ,False),
52
52
  do_clustering_preset= preset.setdefault("do_cluster_computation", False),
53
53
  do_Napari_correction=False,
54
- do_segmentation_preset= preset.setdefault("Segmentation", False),
54
+ do_segmentation_preset= preset.setdefault("segmentation_done", False),
55
55
  )
56
56
  input_layout += [[sg.Button('Ok')]]
57
57
  input_tab = sg.Tab("Input", input_layout)
58
58
 
59
- napari_correction_elmt = get_elmt_from_key(input_tab, key='Napari correction')
59
+ napari_correction_elmt = get_elmt_from_key(input_tab, key='show_napari_corrector')
60
60
 
61
61
  #Maptab
62
62
  map_layout = _ask_channel_map_layout(
@@ -86,7 +86,7 @@ def batch_promp(
86
86
 
87
87
  apply_segmentation_button = sg.Button('apply', key='apply-segmentation')
88
88
  segmentation_layout += [[apply_segmentation_button]]
89
- seg_keys_to_hide = ['show segmentation', 'saving path', 'filename']
89
+ seg_keys_to_hide = ['show segmentation', 'saving path', 'filename', 'other_nucleus_image']
90
90
  segmentation_tab = sg.Tab("Segmentation", segmentation_layout, visible=False)
91
91
 
92
92
  #Detection tab
@@ -228,10 +228,10 @@ def batch_promp(
228
228
  print("Welcome to small fish batch analysis. Please start by loading some files and setting parameters.")
229
229
 
230
230
  batch_folder = values.get('Batch_folder')
231
- is_multichanel = values.get('multichannel')
232
- is_3D = values.get('3D stack')
233
- do_segmentation = values.get('Segmentation')
234
- do_dense_regions_deconvolution = values.get('Dense regions deconvolution')
231
+ is_multichanel = values.get('is_multichannel')
232
+ is_3D = values.get('is_3D_stack')
233
+ do_segmentation = values.get('do_segmentation')
234
+ do_dense_regions_deconvolution = values.get('do_dense_regions_deconvolution')
235
235
  do_clustering = values.get('do_cluster_computation')
236
236
 
237
237
  if type(batch_folder) != type(None) and event == 'Load':
@@ -227,18 +227,14 @@ def _input_parameters_layout(
227
227
 
228
228
  ) :
229
229
  layout_image_path = path_layout(['image path'], header= "Image")
230
- layout_image_path += bool_layout(['is_3D_stack', 'is_multichannel'], preset= [is_3D_stack_preset, time_stack_preset, multichannel_preset])
230
+ layout_image_path += bool_layout(['3D stack', 'Multichannel stack'], keys=['is_3D_stack', 'is_multichannel'], preset= [is_3D_stack_preset, multichannel_preset])
231
231
 
232
- if ask_for_segmentation :
233
- layout_image_path += bool_layout(
234
- ['do_dense_regions_deconvolution', 'do_cluster_computation', 'Segmentation', 'show_napari_corrector'],
235
- preset= [do_dense_regions_deconvolution_preset, do_clustering_preset, do_segmentation_preset, do_Napari_correction],
236
- header= "Pipeline settings")
237
- else :
238
- layout_image_path += bool_layout(
239
- ['do_dense_regions_deconvolution', 'do_cluster_computation', 'show_napari_corrector'],
240
- preset= [do_dense_regions_deconvolution_preset, do_clustering_preset, do_Napari_correction],
241
- header= "Pipeline settings")
232
+ layout_image_path += bool_layout(
233
+ ['Dense regions deconvolution', 'Compute clusters', 'Cell segmentation', 'Open Napari corrector'],
234
+ keys= ['do_dense_regions_deconvolution', 'do_cluster_computation', 'do_segmentation', 'show_napari_corrector'],
235
+ preset= [do_dense_regions_deconvolution_preset, do_clustering_preset, do_segmentation_preset, do_Napari_correction],
236
+ header= "Pipeline settings")
237
+
242
238
 
243
239
  return layout_image_path
244
240
 
@@ -7,19 +7,121 @@ import napari.types
7
7
  import numpy as np
8
8
  import napari
9
9
 
10
- from napari.layers import Labels
10
+ from sklearn.cluster import DBSCAN
11
+ from sklearn.neighbors import NearestNeighbors
11
12
 
12
13
  from magicgui import widgets
13
- from magicgui import magicgui
14
14
 
15
15
  from bigfish.stack import check_parameter
16
+ from bigfish.detection.cluster_detection import _extract_information
16
17
  from ._napari_widgets import cell_label_eraser, segmentation_reseter, changes_propagater, free_label_picker
17
18
  from ..utils import compute_anisotropy_coef
18
19
  from ..pipeline._colocalisation import spots_multicolocalisation
19
20
 
20
21
  #Post detection
21
22
 
22
- def _update_clusters(new_clusters: np.ndarray, spots: np.ndarray, voxel_size, cluster_size, shape) :
23
+ def _update_clusters(
24
+ old_spots : np.ndarray,
25
+ spot_cluster_id : np.ndarray,
26
+ new_spots : np.ndarray,
27
+ old_clusters : np.ndarray,
28
+ new_clusters : np.ndarray,
29
+ cluster_size : int,
30
+ min_number_spot : int,
31
+ voxel_size : tuple,
32
+ null_value = -2,
33
+ talks = False,
34
+ ) :
35
+ """
36
+
37
+ new_spots get weight of 1.
38
+ spots already in cluster get weight 1
39
+ spots not in cluster before but now in cluster radius get weigth = min_number_spot/*number of spot in new cluster radius (>=1)*
40
+ spots in radius of deleted cluster get weight = 0 unless they are in radius of a new cluster.
41
+
42
+ Parameters
43
+ ----------
44
+ new_spots : array (spots_number, space_dim + 1,) containing coordinates of each spots after napari correction as well as the id of belonging cluster. -1 if free spot, np.NaN if unknown.
45
+ old_clusters : array (spots_number, space_dim + 2,) containing coordinates of each clusters centroid before napari correction, number of spots in cluster and the id of cluster.
46
+ new_clusters : array (spots_number, space_dim + 2,) containing coordinates of each clusters centroid after napari correction, number of spots in cluster and the id of cluster. number of spots is NaN if new cluster.
47
+ cluster_size : size of cluster in nanometer passed to DBSCAN.
48
+
49
+ Returns
50
+ -------
51
+ corrected_spots : array with updated cluster id.
52
+ corrected_clusters : array with updated number of spot.
53
+
54
+ """
55
+
56
+ spots_weights = np.ones(len(new_spots), dtype=float)
57
+
58
+ if talks :
59
+ print("\nTALKS IN napari_visualiser._update_clusters")
60
+ print('new_spots_shape : ', new_spots.shape)
61
+ print('old_clusters : ', old_clusters.shape)
62
+ print('new_clusters : ', new_clusters.shape)
63
+
64
+ #Finding new and deleted clusters
65
+ deleted_cluster = old_clusters[~(np.isin(old_clusters[:,-1], new_clusters[:,-1]))]
66
+ added_cluster = new_clusters[new_clusters[:,-1] == null_value]
67
+
68
+ if talks :
69
+ print('deleted_cluster : ', deleted_cluster.shape)
70
+ print('added_cluster : ', added_cluster.shape)
71
+
72
+
73
+
74
+ #Removing cluster_id from points clustered in deleted clusters
75
+ spots_0_weights = old_spots[np.isin(spot_cluster_id, deleted_cluster[:,-1])]
76
+ spots_weights[np.isin(new_spots, spots_0_weights).all(axis=1)] = 0 #Setting weigth to 0 for spots in deleted clusters.
77
+
78
+ if talks :
79
+ print("deleted cluster ids : ", deleted_cluster[:,-1])
80
+ print("spots in deleted cluster : \n", spots_0_weights)
81
+
82
+ #Finding spots in range of new clusters
83
+ if len(added_cluster) > 0 :
84
+ points_neighbors = NearestNeighbors(radius= cluster_size)
85
+ points_neighbors.fit(new_spots*voxel_size)
86
+ neighbor_query = points_neighbors.radius_neighbors(added_cluster[:,:-2]*voxel_size, return_distance=False)
87
+
88
+ for cluster_neighbor in neighbor_query :
89
+ neighboring_spot_number = len(cluster_neighbor)
90
+ if neighboring_spot_number == 0 : continue # will not add a cluster if there is not even one spot nearby.
91
+ weight = min_number_spot / neighboring_spot_number # >1
92
+ if weight <= 1 : print("napari._update_clusters warning : weight <= 1; this should not happen some clusters might be missed during post napari computation.")
93
+ if any(spots_weights[cluster_neighbor] > weight) : # Not replacing a weight for a smaller weigth to ensure all new clusters will be added.
94
+ mask = spots_weights[cluster_neighbor] > weight
95
+ cluster_neighbor = np.delete(cluster_neighbor, mask)
96
+ if len(cluster_neighbor) > 0 : spots_weights[cluster_neighbor] = weight
97
+
98
+ #Initiating new DBSCAN model
99
+ dbscan_model = DBSCAN(cluster_size, min_samples=min_number_spot)
100
+ dbscan_model.fit(new_spots*voxel_size, sample_weight=spots_weights)
101
+
102
+ #Constructing corrected_arrays
103
+ spots_labels = dbscan_model.labels_.reshape(len(new_spots), 1)
104
+ corrected_spots = np.concatenate([new_spots, spots_labels], axis=1).astype(int)
105
+ corrected_cluster = _extract_information(corrected_spots)
106
+
107
+ if talks :
108
+ print("spots with weigth 0 :", len(spots_weights[spots_weights == 0]))
109
+ print("spots with weigth > 1 :", len(spots_weights[spots_weights > 1]))
110
+ print("spots with weigth == 1 :", len(spots_weights[spots_weights == 1]))
111
+ print("spots with weigth < 1 :", len(spots_weights[np.logical_and(spots_weights < 1,spots_weights > 0)]))
112
+
113
+ print('corrected_spots : ', corrected_spots.shape)
114
+ print('corrected_cluster : ', corrected_cluster.shape)
115
+ print("END TALK\n")
116
+
117
+
118
+ return corrected_spots, corrected_cluster
119
+
120
+
121
+ def __update_clusters(new_clusters: np.ndarray, spots: np.ndarray, voxel_size, cluster_size, shape) :
122
+ """
123
+ Outdated. previous behaviour.
124
+ """
23
125
  if len(new_clusters) == 0 : return new_clusters
24
126
  if len(spots) == 0 : return np.empty(shape=(0,2+len(voxel_size)))
25
127
 
@@ -40,7 +142,18 @@ def _update_clusters(new_clusters: np.ndarray, spots: np.ndarray, voxel_size, cl
40
142
 
41
143
  return new_clusters
42
144
 
43
- 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 =[]):
145
+ def correct_spots(
146
+ image,
147
+ spots,
148
+ voxel_size= (1,1,1),
149
+ clusters= None,
150
+ spot_cluster_id= None,
151
+ cluster_size=None,
152
+ min_spot_number=0,
153
+ cell_label= None,
154
+ nucleus_label= None,
155
+ other_images =[]
156
+ ):
44
157
  """
45
158
  Open Napari viewer for user to visualize and corrects spots, clusters.
46
159
 
@@ -68,9 +181,8 @@ def correct_spots(image, spots, voxel_size= (1,1,1), clusters= None, cluster_siz
68
181
  Viewer = napari.Viewer(ndisplay=2, title= 'Spot correction', axis_labels=['z','y','x'], show= False)
69
182
  Viewer.add_image(image, scale=scale, name= "rna signal", blending= 'additive', colormap='red', contrast_limits=[image.min(), image.max()])
70
183
  other_colors = ['green', 'blue', 'gray', 'cyan', 'bop orange', 'bop purple'] * ((len(other_images)-1 // 7) + 1)
71
- for im, color in zip(other_images, other_colors) :
184
+ for im, color in zip(other_images, other_colors) :
72
185
  Viewer.add_image(im, scale=scale, blending='additive', visible=False, colormap=color, contrast_limits=[im.min(), im.max()])
73
- layer_offset = len(other_images)
74
186
 
75
187
  Viewer.add_points( # single molecule spots; this layer can be update by user.
76
188
  spots,
@@ -82,16 +194,21 @@ def correct_spots(image, spots, voxel_size= (1,1,1), clusters= None, cluster_siz
82
194
  name= 'single spots'
83
195
  )
84
196
 
85
- if type(clusters) != type(None) : Viewer.add_points( # cluster; this layer can be update by user.
86
- clusters[:,:dim],
87
- size = 10,
88
- scale=scale,
89
- face_color= 'blue',
90
- opacity= 0.7,
91
- symbol= 'diamond',
92
- name= 'foci',
93
- features= {"spot_number" : clusters[:,dim], "id" : clusters[:,dim+1]},
94
- feature_defaults= {"spot_number" : 0, "id" : -1}
197
+ if type(clusters) != type(None) :
198
+ if len(clusters) > 0 :
199
+ clusters_coordinates = clusters[:, :dim]
200
+ else :
201
+ clusters_coordinates = np.empty(shape=(0,3))
202
+ Viewer.add_points( # cluster; this layer can be update by user.
203
+ clusters_coordinates,
204
+ size = 10,
205
+ scale=scale,
206
+ face_color= 'blue',
207
+ opacity= 0.7,
208
+ symbol= 'diamond',
209
+ name= 'foci',
210
+ features= {"spot_number" : clusters[:,dim], "id" : clusters[:,dim+1]},
211
+ feature_defaults= {"spot_number" : 0, "id" : -2} # napari features default will not work with np.NaN passing -2 instead.
95
212
  )
96
213
 
97
214
  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')
@@ -103,16 +220,37 @@ def correct_spots(image, spots, voxel_size= (1,1,1), clusters= None, cluster_siz
103
220
  new_spots = np.array(Viewer.layers['single spots'].data, dtype= int)
104
221
 
105
222
  if type(clusters) != type(None) :
106
- new_clusters = np.array(Viewer.layers['foci'].data, dtype= int)
107
- new_clusters = _update_clusters(new_clusters, new_spots, voxel_size=voxel_size, cluster_size=cluster_size, shape=image.shape)
223
+ new_clusters = np.round(Viewer.layers['foci'].data).astype(int)
224
+ if len(new_clusters) == 0 :
225
+ new_clusters = np.empty(shape=(0,5))
226
+ new_cluster_id = -1 * np.ones(len(new_spots))
227
+ new_spots = np.concatenate([new_spots, new_cluster_id], axis=1)
228
+ else :
229
+ new_cluster_id = Viewer.layers['foci'].features.to_numpy()
230
+ new_clusters = np.concatenate([new_clusters, new_cluster_id], axis=1)
231
+
232
+ print("After concatenate new clusters shape = {0}".format(new_clusters.shape))
233
+
234
+ new_spots, new_clusters = _update_clusters(
235
+ old_spots =spots,
236
+ spot_cluster_id = spot_cluster_id,
237
+ new_spots=new_spots,
238
+ old_clusters=clusters,
239
+ new_clusters=new_clusters,
240
+ cluster_size=cluster_size,
241
+ min_number_spot=min_spot_number,
242
+ voxel_size=voxel_size,
243
+ null_value= -2
244
+ )
245
+
246
+ print("After _update_cluster\nnew_clusters shape = {0}\nnew_spots shape = {1}".format(new_clusters.shape, new_spots.shape))
247
+
108
248
  else : new_clusters = None
109
249
 
110
250
  return new_spots, new_clusters
111
251
 
112
- # Segmentation
113
-
114
-
115
252
 
253
+ # Segmentation
116
254
  def show_segmentation(
117
255
  nuc_image : np.ndarray,
118
256
  nuc_label : np.ndarray,
@@ -272,7 +272,7 @@ def _sumup_df(results: pd.DataFrame) :
272
272
 
273
273
  return res
274
274
 
275
- def hub_prompt(fov_results, do_segmentation=False) -> 'Union[Literal["Add detection", "Compute colocalisation", "Batch detection", "Rename acquisition", "Save results", "Delete acquisitions", "Reset segmentation", "Reset results", "Segment cells"], dict[Literal["result_table", ""]]]':
275
+ def hub_prompt(fov_results : pd.DataFrame, do_segmentation=False) -> 'Union[Literal["Add detection", "Compute colocalisation", "Batch detection", "Rename acquisition", "Save results", "Delete acquisitions", "Reset segmentation", "Reset results", "Segment cells"], dict[Literal["result_table", ""]]]':
276
276
 
277
277
  sumup_df = _sumup_df(fov_results)
278
278