fishfeats 1.1.19.post1.dev0__py3-none-any.whl
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.
- fish_feats/Analysis.py +239 -0
- fish_feats/Association.py +230 -0
- fish_feats/CellObjects.py +1624 -0
- fish_feats/ClassifyCells.py +702 -0
- fish_feats/Configuration.py +100 -0
- fish_feats/DaskedEpyseg.py +222 -0
- fish_feats/FishGrid.py +71 -0
- fish_feats/FishWidgets.py +238 -0
- fish_feats/MainImage.py +1014 -0
- fish_feats/NapaCells.py +311 -0
- fish_feats/NapaMix.py +149 -0
- fish_feats/NapaNuclei.py +456 -0
- fish_feats/NapaRNA.py +1222 -0
- fish_feats/Naparing.py +1713 -0
- fish_feats/ProcessFolder.py +77 -0
- fish_feats/RNASpots.py +385 -0
- fish_feats/SegmentObj.py +481 -0
- fish_feats/Separe.py +232 -0
- fish_feats/Utils.py +868 -0
- fish_feats/__init__.py +18 -0
- fish_feats/_version.py +1 -0
- fish_feats/cellpose_dask.py +303 -0
- fish_feats/napari.yaml +52 -0
- fishfeats-1.1.19.post1.dev0.dist-info/METADATA +71 -0
- fishfeats-1.1.19.post1.dev0.dist-info/RECORD +29 -0
- fishfeats-1.1.19.post1.dev0.dist-info/WHEEL +5 -0
- fishfeats-1.1.19.post1.dev0.dist-info/entry_points.txt +2 -0
- fishfeats-1.1.19.post1.dev0.dist-info/licenses/LICENSE +29 -0
- fishfeats-1.1.19.post1.dev0.dist-info/top_level.txt +1 -0
fish_feats/Analysis.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
To handle post pipeline analysis
|
|
3
|
+
- Hierarchical clustering: from csv results file and segmented cells, perform and display clustering.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pathlib, os, csv
|
|
9
|
+
|
|
10
|
+
import matplotlib as mpl
|
|
11
|
+
from matplotlib.backends.backend_qt5agg import FigureCanvas
|
|
12
|
+
from matplotlib.figure import Figure
|
|
13
|
+
import matplotlib.pyplot as plt
|
|
14
|
+
|
|
15
|
+
from scipy.cluster.hierarchy import dendrogram
|
|
16
|
+
from scipy.cluster.hierarchy import fcluster
|
|
17
|
+
from scipy.spatial.distance import pdist, squareform
|
|
18
|
+
from scipy.cluster.hierarchy import ward
|
|
19
|
+
from skimage.io import imsave
|
|
20
|
+
from sklearn.preprocessing import scale
|
|
21
|
+
|
|
22
|
+
import napari
|
|
23
|
+
from magicgui import magicgui
|
|
24
|
+
from napari.utils.notifications import show_info
|
|
25
|
+
|
|
26
|
+
import fish_feats.Utils as ut
|
|
27
|
+
import fish_feats.MainImage as mi
|
|
28
|
+
|
|
29
|
+
## disable scipy cluster warning
|
|
30
|
+
from scipy.cluster.hierarchy import ClusterWarning
|
|
31
|
+
from warnings import simplefilter
|
|
32
|
+
simplefilter("ignore", ClusterWarning)
|
|
33
|
+
|
|
34
|
+
def do_hierarchy():
|
|
35
|
+
mig = mi.MainImage( talkative=True )
|
|
36
|
+
viewer = napari.current_viewer()
|
|
37
|
+
viewer.title = "ZENnapari"
|
|
38
|
+
|
|
39
|
+
filename = ut.dialog_filename()
|
|
40
|
+
if filename is None:
|
|
41
|
+
print("No file selected")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
mig.open_image( filename=filename )
|
|
45
|
+
ut.update_history(mig.imagedir)
|
|
46
|
+
|
|
47
|
+
for chanel in range(mig.nbchannels):
|
|
48
|
+
cmap = ut.colormapname(chanel)
|
|
49
|
+
img = mig.get_channel(chanel)
|
|
50
|
+
cview = viewer.add_image( img, name="originalChannel"+str(chanel), blending="additive", scale=(mig.scaleZ, mig.scaleXY, mig.scaleXY), colormap=cmap )
|
|
51
|
+
dint = np.max(img)-np.min(img)
|
|
52
|
+
cview.contrast_limits=(np.min(img), np.max(img)-0.75*dint)
|
|
53
|
+
viewer.axes.visible = True
|
|
54
|
+
|
|
55
|
+
return getScales(mig, viewer)
|
|
56
|
+
|
|
57
|
+
def getScales(mig, viewer):
|
|
58
|
+
@magicgui(call_button="Update",
|
|
59
|
+
scaleXY={"widget_type": "LiteralEvalLineEdit"},
|
|
60
|
+
scaleZ={"widget_type": "LiteralEvalLineEdit"},
|
|
61
|
+
)
|
|
62
|
+
def get_scale( scaleXY= mig.scaleXY, scaleZ= mig.scaleZ,
|
|
63
|
+
segmented_cells=pathlib.Path(mig.junction_filename(dim=2,ifexist=True)),
|
|
64
|
+
):
|
|
65
|
+
mig.scaleXY = scaleXY
|
|
66
|
+
mig.scaleZ = scaleZ
|
|
67
|
+
for chan in range(mig.nbchannels):
|
|
68
|
+
viewer.layers['originalChannel'+str(chan)].scale = [mig.scaleZ, mig.scaleXY, mig.scaleXY]
|
|
69
|
+
viewer.window.remove_dock_widget("all")
|
|
70
|
+
mig.load_segmentation( segmented_cells )
|
|
71
|
+
mig.popFromJunctions()
|
|
72
|
+
|
|
73
|
+
hiera = HierAnalysis()
|
|
74
|
+
hiera.set(mig, viewer)
|
|
75
|
+
hiera.get_data()
|
|
76
|
+
|
|
77
|
+
wid = viewer.window.add_dock_widget(get_scale, name="Scale")
|
|
78
|
+
return wid
|
|
79
|
+
|
|
80
|
+
############ hierarchical analysis
|
|
81
|
+
class HierAnalysis:
|
|
82
|
+
""" Perform and display hierarchical analysis based on selected features """
|
|
83
|
+
|
|
84
|
+
def __init__(self):
|
|
85
|
+
self.nclusters = 4
|
|
86
|
+
self.wid = None
|
|
87
|
+
self.clustward = None
|
|
88
|
+
|
|
89
|
+
def set(self, mig, viewer):
|
|
90
|
+
self.mig = mig
|
|
91
|
+
self.viewer = viewer
|
|
92
|
+
self.cluster_img = np.zeros(mig.get_image_shape(in2d=True), np.uint8)
|
|
93
|
+
self.featlayer = self.viewer.add_labels(self.cluster_img, name="ClusteredCells", scale=(mig.scaleXY, mig.scaleXY), opacity=1)
|
|
94
|
+
|
|
95
|
+
def get_data(self):
|
|
96
|
+
""" Interface to select the file and the parameters """
|
|
97
|
+
def load_file():
|
|
98
|
+
""" Load the excel/csv file """
|
|
99
|
+
with open(get_columns.load_file.value, 'r') as infile:
|
|
100
|
+
csvreader = csv.DictReader(infile)
|
|
101
|
+
print(csvreader.fieldnames)
|
|
102
|
+
get_columns.use_column.choices = csvreader.fieldnames
|
|
103
|
+
|
|
104
|
+
def load_table():
|
|
105
|
+
""" Load the specific columns """
|
|
106
|
+
keep = get_columns.use_column.value
|
|
107
|
+
self.prepare_data( get_columns.load_file.value, keep )
|
|
108
|
+
self.show_clusters()
|
|
109
|
+
|
|
110
|
+
def update_clusters():
|
|
111
|
+
""" Update all with the new number of clusters chosen """
|
|
112
|
+
self.nclusters = int(get_columns.nb_clusters.value)
|
|
113
|
+
self.show_clusters()
|
|
114
|
+
|
|
115
|
+
def save_cluscells():
|
|
116
|
+
""" Save image of cells colored by cluster """
|
|
117
|
+
ccells = self.featlayer.data
|
|
118
|
+
outname = self.mig.build_filename( endname="_ClusteredCells_nclus_"+str(self.nclusters)+".png" )
|
|
119
|
+
vis = []
|
|
120
|
+
for lay in self.viewer.layers:
|
|
121
|
+
vis.append(lay.visible)
|
|
122
|
+
lay.visible = False
|
|
123
|
+
self.featlayer.visible = True
|
|
124
|
+
screenshot = self.viewer.screenshot()
|
|
125
|
+
for visib, lay in zip(vis, self.viewer.layers):
|
|
126
|
+
lay.visible = visib
|
|
127
|
+
|
|
128
|
+
imsave(outname, screenshot)
|
|
129
|
+
show_info("Saved in "+outname)
|
|
130
|
+
|
|
131
|
+
def save_dendrogram_img():
|
|
132
|
+
""" Save image of dendrogram to file """
|
|
133
|
+
outname = self.mig.build_filename(endname="_ClusterDendrogram_nclus_"+str(self.nclusters)+".png")
|
|
134
|
+
self.fig.savefig(outname)
|
|
135
|
+
show_info("Saved in "+outname)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@magicgui(call_button="Cluster from selected columns",
|
|
139
|
+
use_column = dict(widget_type="Select", choices=[]),
|
|
140
|
+
nb_clusters={"widget_type": "Slider", "min":1, "max": 50},
|
|
141
|
+
save_clustered_cells={"widget_type":"PushButton", "value": False},
|
|
142
|
+
save_dendrogram={"widget_type":"PushButton", "value": False},
|
|
143
|
+
)
|
|
144
|
+
def get_columns(
|
|
145
|
+
#load_file=pathlib.Path(self.mig.rnacount_filename(ifexist=True)),
|
|
146
|
+
load_file=pathlib.Path(self.mig.resdir),
|
|
147
|
+
use_column = [],
|
|
148
|
+
nb_clusters = 4,
|
|
149
|
+
save_clustered_cells=False, save_dendrogram=False,
|
|
150
|
+
):
|
|
151
|
+
load_table()
|
|
152
|
+
|
|
153
|
+
get_columns.load_file.changed.connect(load_file)
|
|
154
|
+
get_columns.nb_clusters.changed.connect(update_clusters)
|
|
155
|
+
get_columns.save_clustered_cells.clicked.connect(save_cluscells)
|
|
156
|
+
get_columns.save_dendrogram.clicked.connect(save_dendrogram_img)
|
|
157
|
+
self.viewer.window.add_dock_widget( get_columns, name="Load data" )
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_cluster_colors(self):
|
|
161
|
+
""" To have same color between the label layer and the matplotlib plot """
|
|
162
|
+
return [0] + [mpl.colors.rgb2hex(self.featlayer.get_color(i+1)) for i in range(self.nclusters)]
|
|
163
|
+
|
|
164
|
+
def check_label(self, columns, lab):
|
|
165
|
+
""" Check if a label is in the list that should not """
|
|
166
|
+
if lab in columns:
|
|
167
|
+
ut.show_warning("Warning, "+lab+" is in the selected features list, that's weird")
|
|
168
|
+
|
|
169
|
+
def prepare_data(self, filename, columns):
|
|
170
|
+
""" normalisation of the data """
|
|
171
|
+
res = []
|
|
172
|
+
self.labels = []
|
|
173
|
+
self.check_label(columns, "CellLabel")
|
|
174
|
+
self.check_label(columns, "CellID")
|
|
175
|
+
self.check_label(columns, "NucleusID")
|
|
176
|
+
self.check_label(columns, "NucleusLabel")
|
|
177
|
+
with open(filename, 'r') as infile:
|
|
178
|
+
csvreader = csv.DictReader(infile)
|
|
179
|
+
for row in csvreader:
|
|
180
|
+
cres = []
|
|
181
|
+
clab = int(row["CellLabel"])
|
|
182
|
+
for col in columns:
|
|
183
|
+
cres.append(float(row[col])+1)
|
|
184
|
+
res.append(cres)
|
|
185
|
+
self.labels.append(clab)
|
|
186
|
+
tab = np.array(res)
|
|
187
|
+
nans = np.isnan( res ).any( axis=1 )
|
|
188
|
+
kinds = [ind for ind in range(tab.shape[0]) if not nans[ind] ]
|
|
189
|
+
tab = tab[ kinds ]
|
|
190
|
+
self.labels = [ self.labels[ind] for ind in kinds]
|
|
191
|
+
negs = ( tab<0 ).any( axis=1 )
|
|
192
|
+
kinds = [ind for ind in range(tab.shape[0]) if not negs[ind] ]
|
|
193
|
+
tab = tab[ kinds ]
|
|
194
|
+
self.labels = [ self.labels[ind] for ind in kinds]
|
|
195
|
+
tab = np.log(tab)
|
|
196
|
+
tab = scale(tab)
|
|
197
|
+
print(tab.shape)
|
|
198
|
+
|
|
199
|
+
dist_tab = pdist(tab, metric='euclidean')
|
|
200
|
+
dist_tab = squareform(dist_tab)
|
|
201
|
+
self.clustward = ward(dist_tab)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def show_clusters(self):
|
|
205
|
+
""" Show dendogram and classified cells """
|
|
206
|
+
if self.clustward is None:
|
|
207
|
+
return
|
|
208
|
+
clustered = fcluster(self.clustward, t=self.nclusters, criterion="maxclust")
|
|
209
|
+
|
|
210
|
+
self.mig.set_cells(self.cluster_img, clustered, self.labels)
|
|
211
|
+
self.featlayer.refresh()
|
|
212
|
+
#self.cmap = self.featlayer.colormap.colors
|
|
213
|
+
if self.wid is None:
|
|
214
|
+
self.wid = self.create_plotwidget()
|
|
215
|
+
self.update_plotwidget(clustered)
|
|
216
|
+
if "Dendrogram" not in self.viewer.window._dock_widgets:
|
|
217
|
+
self.viewer.window.add_dock_widget( self.wid, name="Dendrogram" )
|
|
218
|
+
|
|
219
|
+
def create_plotwidget(self):
|
|
220
|
+
mpl_widget = FigureCanvas( Figure(figsize=(6,6) ) )
|
|
221
|
+
self.fig = mpl_widget.figure
|
|
222
|
+
self.ax = mpl_widget.figure.subplots()
|
|
223
|
+
return mpl_widget
|
|
224
|
+
|
|
225
|
+
def update_plotwidget(self, clustered):
|
|
226
|
+
clus_str = [ f"cluster #{l}: n={c}\n" for (l,c) in zip(*np.unique(clustered, return_counts=True)) ]
|
|
227
|
+
|
|
228
|
+
cluster_colors = self.get_cluster_colors()
|
|
229
|
+
cluster_colors_array = [cluster_colors[cl] for cl in clustered]
|
|
230
|
+
link_cols = {}
|
|
231
|
+
for i, i12 in enumerate(self.clustward[:,:2].astype(int)):
|
|
232
|
+
c1, c2 = (link_cols[x] if x > len(self.clustward) else cluster_colors_array[x] for x in i12)
|
|
233
|
+
link_cols[i+1+len(self.clustward)] = c1 if c1 == c2 else 'k'
|
|
234
|
+
|
|
235
|
+
self.ax.cla()
|
|
236
|
+
dend = dendrogram(self.clustward, p=8, truncate_mode='level', no_labels=True, ax=self.ax, link_color_func=lambda x: link_cols[x] )
|
|
237
|
+
self.fig.canvas.draw_idle()
|
|
238
|
+
#plt.show()
|
|
239
|
+
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
## Associate contours and nuclei by distance
|
|
2
|
+
## algorithm hongrois: Kuhn-Munkres
|
|
3
|
+
import numpy as np
|
|
4
|
+
from math import sqrt, floor
|
|
5
|
+
from scipy.ndimage.morphology import distance_transform_edt
|
|
6
|
+
from munkres import Munkres
|
|
7
|
+
from skimage.measure import label, regionprops
|
|
8
|
+
|
|
9
|
+
def distance2DCenters( cent0, cent1, scaleXY ):
|
|
10
|
+
if cent0 is None:
|
|
11
|
+
return 0
|
|
12
|
+
return sqrt( (cent0[0]-cent1[0])*(cent0[0]-cent1[0]) + (cent0[1]-cent1[1])*(cent0[1]-cent1[1]) )*scaleXY
|
|
13
|
+
|
|
14
|
+
def associateWindows(wlab, wbal, dlim, scaleXY):
|
|
15
|
+
labels = np.unique(wlab[wlab>0])
|
|
16
|
+
balels = np.unique(wbal[wbal>0])
|
|
17
|
+
nbal = len(balels)
|
|
18
|
+
nlab = len(labels)
|
|
19
|
+
resbal = np.zeros_like(wbal)
|
|
20
|
+
n = max(nbal, nlab)
|
|
21
|
+
matrix = np.zeros((n, n)) ## should be square.
|
|
22
|
+
if n == 0:
|
|
23
|
+
return resbal
|
|
24
|
+
|
|
25
|
+
## distance_transform is distance to closest background, so inverse
|
|
26
|
+
dist2lab, nearest_coord = distance_transform_edt(wlab==0, return_indices=True)
|
|
27
|
+
dillabels = np.zeros_like(wlab)
|
|
28
|
+
## check if within reasonnable distance
|
|
29
|
+
dilate_mask = ((dist2lab*scaleXY*scaleXY) <= dlim)
|
|
30
|
+
masked_nearest_label_coords = [ dind[dilate_mask] for dind in nearest_coord]
|
|
31
|
+
nearest_labels = wlab[tuple(masked_nearest_label_coords)]
|
|
32
|
+
dillabels[dilate_mask] = nearest_labels
|
|
33
|
+
|
|
34
|
+
## look for best fit
|
|
35
|
+
for j, jlab in enumerate(balels):
|
|
36
|
+
nei = dillabels[wbal==jlab]
|
|
37
|
+
for i, ilab in enumerate(labels):
|
|
38
|
+
count = np.sum(nei==ilab)
|
|
39
|
+
if count > 0:
|
|
40
|
+
matrix[i][j] = 1.0/(count+1)
|
|
41
|
+
else:
|
|
42
|
+
matrix[i][j] = dlim+1
|
|
43
|
+
|
|
44
|
+
## algorithm hongrois: Kuhn-Munkres
|
|
45
|
+
dmat = np.copy(matrix)
|
|
46
|
+
munk = Munkres()
|
|
47
|
+
assoc = munk.compute(matrix)
|
|
48
|
+
matrix = None
|
|
49
|
+
munk = None
|
|
50
|
+
for asso in assoc:
|
|
51
|
+
if asso[0]<nlab and asso[1]<nbal:
|
|
52
|
+
if (dmat[asso[0]][asso[1]] > 0) and (dmat[asso[0]][asso[1]]<dlim):
|
|
53
|
+
## associate
|
|
54
|
+
resbal[wbal==balels[asso[1]]] = labels[asso[0]]
|
|
55
|
+
|
|
56
|
+
return resbal
|
|
57
|
+
|
|
58
|
+
def associateLabWithLab(lab, bal, dlim, scaleXY):
|
|
59
|
+
""" associate labels of img 2 to labels of img 1 """
|
|
60
|
+
##### do overlapping windows otherwise calcul distances too slow (to test)
|
|
61
|
+
sizex = 1000
|
|
62
|
+
sizey = 1000
|
|
63
|
+
over = 50
|
|
64
|
+
sizes = lab.shape
|
|
65
|
+
|
|
66
|
+
posy = 0
|
|
67
|
+
resbal = np.zeros(bal.shape, dtype="uint16")
|
|
68
|
+
while posy < sizes[0]:
|
|
69
|
+
posx = 0
|
|
70
|
+
while posx < sizes[1]:
|
|
71
|
+
windowbal = np.copy(bal[posy:(posy+sizey),posx:(posx+sizex)])
|
|
72
|
+
windowlab = lab[posy:(posy+sizey),posx:(posx+sizex)]
|
|
73
|
+
assobal = associateWindows( windowlab, windowbal, dlim, scaleXY )
|
|
74
|
+
bal[posy:(posy+sizey),posx:(posx+sizex)][assobal>0] = 0
|
|
75
|
+
resbal[posy:(posy+sizey),posx:(posx+sizex)][assobal>0] = assobal[assobal>0]
|
|
76
|
+
|
|
77
|
+
posx += (sizex-over)
|
|
78
|
+
posy += (sizey-over)
|
|
79
|
+
|
|
80
|
+
## what is left in bal image has not been associated, add it to the result image as new label
|
|
81
|
+
maxlab = np.max(lab)
|
|
82
|
+
for l in np.unique(bal):
|
|
83
|
+
if l > 0:
|
|
84
|
+
resbal[bal==l] = maxlab+1
|
|
85
|
+
maxlab = np.max(resbal)
|
|
86
|
+
|
|
87
|
+
return resbal
|
|
88
|
+
|
|
89
|
+
def associateNucleus(labs, dlimit=3, scaleXY=1):
|
|
90
|
+
""" Associate each slice with previous slice """
|
|
91
|
+
for i in range(len(labs)):
|
|
92
|
+
if i > 1:
|
|
93
|
+
rlab = associateLabWithLab( labs[i-1,], labs[i,], dlimit, scaleXY )
|
|
94
|
+
labs[i,] = rlab
|
|
95
|
+
return labs
|
|
96
|
+
|
|
97
|
+
def associateOverlap( labimg, labimgprev, threshold_overlap=0.25):
|
|
98
|
+
nuclei_prop = regionprops( labimg, intensity_image=labimgprev )
|
|
99
|
+
new_label = np.max(labimgprev) + 1
|
|
100
|
+
#taken = []
|
|
101
|
+
for nucprop in nuclei_prop:
|
|
102
|
+
overlap = nucprop.image_intensity
|
|
103
|
+
overlabs, counts = np.unique(overlap, return_counts=True)
|
|
104
|
+
if 0 in overlabs:
|
|
105
|
+
zero = np.where(overlabs==0)
|
|
106
|
+
zero = zero[0]
|
|
107
|
+
overlabs = [over for i, over in enumerate(overlabs) if i != zero]
|
|
108
|
+
counts = [over for i, over in enumerate(counts) if i != zero]
|
|
109
|
+
|
|
110
|
+
done = False
|
|
111
|
+
if len(counts)>0:
|
|
112
|
+
maxlabind = np.argmax( counts )
|
|
113
|
+
#maxlabind = maxlabind[0]
|
|
114
|
+
if counts[maxlabind]/nucprop.area > threshold_overlap:
|
|
115
|
+
## overlap between the two labels, associate
|
|
116
|
+
labimg[nucprop.bbox[0]:nucprop.bbox[2], nucprop.bbox[1]:nucprop.bbox[3]][nucprop.image] = overlabs[maxlabind]
|
|
117
|
+
done = True
|
|
118
|
+
|
|
119
|
+
if not done:
|
|
120
|
+
# no match found, new label
|
|
121
|
+
labimg[nucprop.bbox[0]:nucprop.bbox[2], nucprop.bbox[1]:nucprop.bbox[3]][nucprop.image] = new_label
|
|
122
|
+
new_label = new_label + 1
|
|
123
|
+
#taken.append(maxlabind)
|
|
124
|
+
return labimg
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def associateNucleusOverlap(labs, threshold_overlap):
|
|
128
|
+
""" Associate each slice with previous slice based on IOU """
|
|
129
|
+
for i, lab in enumerate(labs):
|
|
130
|
+
if i >= 1:
|
|
131
|
+
plab = associateOverlap( lab, plab, threshold_overlap )
|
|
132
|
+
else:
|
|
133
|
+
# first slice
|
|
134
|
+
plab = lab
|
|
135
|
+
labs[i,] = plab
|
|
136
|
+
return labs
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def associate_objects(pop, wnuc, wcells, dlim, scaleXY, scaleZ):
|
|
140
|
+
## algorithm hongrois: Kuhn-Munkres
|
|
141
|
+
ncells = len(wcells)
|
|
142
|
+
nnuclei = len(wnuc)
|
|
143
|
+
n = max(ncells, nnuclei)
|
|
144
|
+
#print(str(ncells)+" "+str(nnuclei)+" "+str(n))
|
|
145
|
+
|
|
146
|
+
matrix = np.zeros((n,n)) ## should be square.
|
|
147
|
+
row = 0
|
|
148
|
+
for i, cell in enumerate(wcells):
|
|
149
|
+
for j, nuc in enumerate(wnuc):
|
|
150
|
+
matrix[i][j] = pop.distanceNucleusToCell( nuc, cell, scaleXY, scaleZ ) ## put more weights to XY distance than z
|
|
151
|
+
|
|
152
|
+
munk = Munkres()
|
|
153
|
+
dmat = np.copy(matrix)
|
|
154
|
+
assoc = munk.compute(matrix)
|
|
155
|
+
munk = None
|
|
156
|
+
associated = []
|
|
157
|
+
associatedNuc = []
|
|
158
|
+
#acells = []
|
|
159
|
+
## assoc contient indices. If indices > nnuclei or ncells, mean non associated
|
|
160
|
+
for asso in assoc:
|
|
161
|
+
if asso[0] < ncells and asso[1] < nnuclei:
|
|
162
|
+
if dmat[asso[0]][asso[1]] < dlim:
|
|
163
|
+
## associate them
|
|
164
|
+
pop.associateNucleusAndRelabel( nucleus=wnuc[asso[1]], cell=wcells[asso[0]] )
|
|
165
|
+
associated.append(asso[0])
|
|
166
|
+
associatedNuc.append(asso[1])
|
|
167
|
+
|
|
168
|
+
dmat = None
|
|
169
|
+
return associated, associatedNuc
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def associate_nucleiToCell(pop, imgsizes, dlim=20, scaleXY=1, scaleZ=1, pbar=None):
|
|
173
|
+
""" Need scaling for non isotropic distances """
|
|
174
|
+
##### do overlapping windows otherwise association too slow (too big matrix)
|
|
175
|
+
sizex = floor(180/scaleXY)
|
|
176
|
+
sizey = floor(180/scaleXY)
|
|
177
|
+
over = floor(40/scaleXY)
|
|
178
|
+
margin = floor(5/scaleXY)
|
|
179
|
+
|
|
180
|
+
posy = 0
|
|
181
|
+
#fullcells = [] ## cells with associated nuclei
|
|
182
|
+
nuclei = pop.nuclei.values()
|
|
183
|
+
cells = pop.cells.values()
|
|
184
|
+
if pbar is not None:
|
|
185
|
+
pbar.total = imgsizes[0]
|
|
186
|
+
while posy < imgsizes[0]:
|
|
187
|
+
if pbar is not None:
|
|
188
|
+
pbar.update(posy)
|
|
189
|
+
posx = 0
|
|
190
|
+
while posx < imgsizes[1]:
|
|
191
|
+
border = (posy, posx, posy+sizey, posx+sizex)
|
|
192
|
+
|
|
193
|
+
winuclei = []
|
|
194
|
+
leftnuclei = []
|
|
195
|
+
#print("newcnts "+str(len(newcnts)))
|
|
196
|
+
for nuc in nuclei:
|
|
197
|
+
if nuc.insideBorderCenter(border):
|
|
198
|
+
winuclei.append(nuc)
|
|
199
|
+
else:
|
|
200
|
+
leftnuclei.append(nuc)
|
|
201
|
+
nuclei = leftnuclei
|
|
202
|
+
|
|
203
|
+
wincells = []
|
|
204
|
+
border = (posy-margin, posx-margin, posy+sizey+margin, posx+sizex+margin)
|
|
205
|
+
leftcells = []
|
|
206
|
+
for cell in cells:
|
|
207
|
+
if cell.insideBorderCenter(border):
|
|
208
|
+
wincells.append(cell)
|
|
209
|
+
else:
|
|
210
|
+
leftcells.append(cell)
|
|
211
|
+
|
|
212
|
+
if len(winuclei)>0 and len(wincells)>0:
|
|
213
|
+
associated, associatedNuc = associate_objects(pop, winuclei, wincells, dlim, scaleXY, scaleZ)
|
|
214
|
+
#fullcells = fullcells + acells
|
|
215
|
+
|
|
216
|
+
for i in range(len(wincells)):
|
|
217
|
+
if i not in associated:
|
|
218
|
+
leftcells.append(wincells[i])
|
|
219
|
+
|
|
220
|
+
for i in range(len(winuclei)):
|
|
221
|
+
if i not in associatedNuc:
|
|
222
|
+
nuclei.append(winuclei[i])
|
|
223
|
+
|
|
224
|
+
cells = leftcells
|
|
225
|
+
|
|
226
|
+
posx += (sizex-over)
|
|
227
|
+
posy += (sizey-over)
|
|
228
|
+
|
|
229
|
+
print("Unassociated nuclei left "+str(len(nuclei)))
|
|
230
|
+
pop.relabelUnassociatedNuclei(nuclei)
|