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.
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/PKG-INFO +1 -1
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/pyproject.toml +1 -1
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/__init__.py +1 -1
- small_fish_gui-1.7.1/src/small_fish_gui/gui/_napari_widgets.py +93 -0
- 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
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/prompts.py +2 -2
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/output.py +19 -15
- small_fish_gui-1.7.1/src/small_fish_gui/pipeline/_colocalisation.py +429 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/_segmentation.py +2 -2
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/actions.py +55 -21
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/detection.py +5 -2
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/main.py +37 -14
- small_fish_gui-1.6.0/src/small_fish_gui/pipeline/_colocalisation.py +0 -275
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/LICENSE +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/README.md +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/.github/workflows/python-publish.yml +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/.readthedocs.yaml +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/LICENSE +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/README.md +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/Segmentation example.jpg +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/__main__.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/__init__.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/input.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/integrity.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/output.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/pipeline.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/prompt.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/test.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/update.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/utils.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/values.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/batch/values.txt +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/docs/conf.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/__init__.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/animation.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/general_help_screenshot.png +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/help_module.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/layout.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/mapping_help_screenshot.png +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/gui/segmentation_help_screenshot.png +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/__init__.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/image.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/parameters.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/interface/testing.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/napari_detection_example.png +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/__init__.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/_custom_errors.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/_preprocess.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/_signaltonoise.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/spots.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/test.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/pipeline/utils.py +0 -0
- {small_fish_gui-1.6.0 → small_fish_gui-1.7.1}/src/small_fish_gui/requirements.txt +0 -0
- {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.
|
|
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
|
|
@@ -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,
|
|
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]
|
|
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,
|
|
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']
|
|
146
|
+
Viewer = napari.Viewer(ndisplay=2, title= 'Show segmentation', axis_labels=['z','y','x'] if dim == 3 else ['y', 'x'])
|
|
135
147
|
|
|
136
|
-
# Adding
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 + '.
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|