small-fish-gui 1.5.0__tar.gz → 1.7.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 (54) hide show
  1. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/PKG-INFO +1 -1
  2. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/pyproject.toml +1 -1
  3. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/__init__.py +1 -1
  4. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/__main__.py +3 -3
  5. small_fish_gui-1.7.0/src/small_fish_gui/gui/_napari_widgets.py +93 -0
  6. small_fish_gui-1.5.0/src/small_fish_gui/pipeline/_napari_wrapper.py → small_fish_gui-1.7.0/src/small_fish_gui/gui/napari.py +43 -21
  7. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/gui/prompts.py +12 -8
  8. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/interface/output.py +18 -14
  9. small_fish_gui-1.7.0/src/small_fish_gui/pipeline/_colocalisation.py +429 -0
  10. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/_segmentation.py +2 -2
  11. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/actions.py +77 -14
  12. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/detection.py +15 -4
  13. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/main.py +41 -14
  14. small_fish_gui-1.5.0/src/small_fish_gui/pipeline/_colocalisation.py +0 -266
  15. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/LICENSE +0 -0
  16. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/README.md +0 -0
  17. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/.github/workflows/python-publish.yml +0 -0
  18. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/.readthedocs.yaml +0 -0
  19. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/LICENSE +0 -0
  20. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/README.md +0 -0
  21. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/Segmentation example.jpg +0 -0
  22. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/__init__.py +0 -0
  23. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/input.py +0 -0
  24. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/integrity.py +0 -0
  25. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/output.py +0 -0
  26. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/pipeline.py +0 -0
  27. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/prompt.py +0 -0
  28. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/test.py +0 -0
  29. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/update.py +0 -0
  30. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/utils.py +0 -0
  31. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/values.py +0 -0
  32. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/batch/values.txt +0 -0
  33. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/docs/conf.py +0 -0
  34. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/gui/__init__.py +0 -0
  35. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/gui/animation.py +0 -0
  36. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/gui/general_help_screenshot.png +0 -0
  37. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/gui/help_module.py +0 -0
  38. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/gui/layout.py +0 -0
  39. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/gui/mapping_help_screenshot.png +0 -0
  40. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/gui/segmentation_help_screenshot.png +0 -0
  41. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/interface/__init__.py +0 -0
  42. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/interface/image.py +0 -0
  43. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/interface/parameters.py +0 -0
  44. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/interface/testing.py +0 -0
  45. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/napari_detection_example.png +0 -0
  46. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/__init__.py +0 -0
  47. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/_custom_errors.py +0 -0
  48. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/_preprocess.py +0 -0
  49. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/_signaltonoise.py +0 -0
  50. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/spots.py +0 -0
  51. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/test.py +0 -0
  52. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/pipeline/utils.py +0 -0
  53. {small_fish_gui-1.5.0 → small_fish_gui-1.7.0}/src/small_fish_gui/requirements.txt +0 -0
  54. {small_fish_gui-1.5.0 → small_fish_gui-1.7.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.5.0
3
+ Version: 1.7.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.5.0"
7
+ version = "1.7.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.5.0"
41
+ __version__ = "1.7.0"
@@ -1,6 +1,8 @@
1
1
  import sys, subprocess
2
2
  import PySimpleGUI as sg
3
3
 
4
+ from small_fish_gui import __version__
5
+
4
6
  def main():
5
7
  import small_fish_gui.pipeline.main
6
8
 
@@ -10,9 +12,7 @@ def is_last_version() :
10
12
  latest_version = latest_version[:latest_version.find(')')]
11
13
  latest_version = latest_version.replace(' ','').split(',')[-1]
12
14
 
13
- current_version = str(subprocess.run([sys.executable, '-m', 'pip', 'show', '{}'.format('small_fish_gui')], capture_output=True, text=True))
14
- current_version = current_version[current_version.find('Version:')+8:]
15
- current_version = current_version[:current_version.find('\\n')].replace(' ','')
15
+ current_version = __version__
16
16
 
17
17
  return current_version == latest_version
18
18
 
@@ -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,13 +7,21 @@ 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
- if len(spots) == 0 : return new_clusters
24
+ if len(spots) == 0 : return np.empty(shape=(0,2+len(voxel_size)))
17
25
 
18
26
  if len(new_clusters[0]) in [2,3] :
19
27
  new_clusters = np.concatenate([
@@ -25,13 +33,10 @@ def _update_clusters(new_clusters: np.ndarray, spots: np.ndarray, voxel_size, cl
25
33
  assert len(new_clusters[0]) == 4 or len(new_clusters[0]) == 5, "Wrong number of coordinates for clusters should not happen."
26
34
 
27
35
  # Update spots clusters
28
- if len(voxel_size) == 3 :
29
- new_clusters[:,-2] = spots_multicolocalisation(new_clusters[:,:3], spots, radius_nm= cluster_size, voxel_size=voxel_size, image_shape=shape)
30
- elif len(voxel_size) == 2 :
31
- new_clusters[:,-2] = spots_multicolocalisation(new_clusters[:,:2], spots, radius_nm= cluster_size, voxel_size=voxel_size, image_shape=shape)
36
+ new_clusters[:,-2] = spots_multicolocalisation(new_clusters[:,:-2], spots, radius_nm= cluster_size, voxel_size=voxel_size, image_shape=shape)
32
37
 
33
38
  # delete too small clusters
34
- 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)
35
40
 
36
41
  return new_clusters
37
42
 
@@ -98,13 +103,16 @@ def correct_spots(image, spots, voxel_size= (1,1,1), clusters= None, cluster_siz
98
103
  new_spots = np.array(Viewer.layers['single spots'].data, dtype= int)
99
104
 
100
105
  if type(clusters) != type(None) :
101
- if len(clusters) > 0 :
102
- new_clusters = np.array(Viewer.layers['foci'].data, dtype= int)
103
- 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)
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)
104
108
  else : new_clusters = None
105
109
 
106
110
  return new_spots, new_clusters
107
111
 
112
+ # Segmentation
113
+
114
+
115
+
108
116
  def show_segmentation(
109
117
  nuc_image : np.ndarray,
110
118
  nuc_label : np.ndarray,
@@ -135,26 +143,40 @@ def show_segmentation(
135
143
  )
136
144
 
137
145
  #Init Napari viewer
138
- 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'])
139
147
 
140
- # Adding channels
148
+ # Adding nuclei
141
149
  nuc_signal_layer = Viewer.add_image(nuc_image, name= "nucleus signal", blending= 'additive', colormap='blue', contrast_limits=[nuc_image.min(), nuc_image.max()])
142
- 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',)
143
151
  nuc_label_layer.preserve_labels = True
152
+ labels_layer_list = [nuc_label_layer]
144
153
 
145
- #Adding labels
146
- 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
147
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):
148
- 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')
149
158
  cyto_label_layer.preserve_labels = True
150
-
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
+
151
174
  #Launch Napari
152
- Viewer.show(block=False)
153
175
  napari.run()
154
176
 
155
-
156
177
  new_nuc_label = Viewer.layers['nucleus_label'].data
157
- if type(cyto_label) != type(None) and not np.array_equal(cyto_label, nuc_label) : new_cyto_label = Viewer.layers['cytoplasm_label'].data
178
+ if 'cytoplasm_label' in Viewer.layers :
179
+ new_cyto_label = Viewer.layers['cytoplasm_label'].data
158
180
  else : new_cyto_label = new_nuc_label
159
181
 
160
182
  return new_nuc_label, new_cyto_label
@@ -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")
@@ -280,7 +280,7 @@ def _warning_popup(warning:str) :
280
280
 
281
281
  def _sumup_df(results: pd.DataFrame) :
282
282
 
283
- COLUMNS = ['acquisition_id','threshold', 'spot_number', 'cell_number', 'filename', 'channel to compute']
283
+ COLUMNS = ['acquisition_id','name','threshold', 'spot_number', 'cell_number', 'filename', 'channel to compute']
284
284
 
285
285
  if len(results) > 0 :
286
286
  if 'channel to compute' not in results : results['channel to compute'] = np.NaN
@@ -303,7 +303,7 @@ def hub_prompt(fov_results, do_segmentation=False) :
303
303
  [sg.Text('RESULTS', font= 'bold 13')],
304
304
  [sg.Table(values= list(sumup_df.values), headings= list(sumup_df.columns), row_height=20, num_rows= 5, vertical_scroll_only=False, key= "result_table"), segmentation_object],
305
305
  [sg.Button('Add detection'), sg.Button('Compute colocalisation'), sg.Button('Batch detection')],
306
- [sg.Button('Save results', button_color= 'green'), sg.Button('Delete acquisitions',button_color= 'gray'), sg.Button('Reset segmentation',button_color= 'gray'), sg.Button('Reset results',button_color= 'gray')]
306
+ [sg.Button('Rename acquisition', button_color= 'green'), sg.Button('Save results', button_color= 'green'), sg.Button('Delete acquisitions',button_color= 'gray'), sg.Button('Reset segmentation',button_color= 'gray'), sg.Button('Reset results',button_color= 'gray')]
307
307
  # [sg.Button('Save results', button_color= 'green'), sg.Button('Reset results',button_color= 'gray')]
308
308
  ]
309
309
 
@@ -318,16 +318,20 @@ def hub_prompt(fov_results, do_segmentation=False) :
318
318
  return event, values
319
319
 
320
320
  def coloc_prompt() :
321
- layout = [
322
- [parameters_layout(['colocalisation distance'], header= 'Colocalisation', default_values= 0)]
323
- ]
324
-
321
+ layout = parameters_layout(['colocalisation distance'], unit= 'nm', header= 'Colocalisation', default_values= 0)
325
322
  event, values = prompt_with_help(layout)
326
323
 
327
324
  if event == 'Ok' :
328
325
  return values['colocalisation distance']
329
326
  else : return False
330
327
 
328
+ def rename_prompt() :
329
+ layout = parameters_layout(['name'], header= "Rename acquisitions", size=12)
330
+ event, values = prompt_with_help(layout)
331
+ if event == 'Ok' :
332
+ return values['name']
333
+ else : return False
334
+
331
335
  def ask_detection_confirmation(used_threshold) :
332
336
  layout = [
333
337
  [sg.Text("Proceed with current detection ?", font= 'bold 10')],
@@ -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,6 +20,14 @@ 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
@@ -31,23 +37,21 @@ def write_results(dataframe: pd.DataFrame, path:str, filename:str, do_excel= Tru
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
+ 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