celldetective 1.1.1.post4__py3-none-any.whl → 1.2.0__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.
- celldetective/__init__.py +2 -1
- celldetective/extra_properties.py +62 -34
- celldetective/gui/__init__.py +1 -0
- celldetective/gui/analyze_block.py +2 -1
- celldetective/gui/classifier_widget.py +8 -7
- celldetective/gui/control_panel.py +50 -6
- celldetective/gui/layouts.py +5 -4
- celldetective/gui/neighborhood_options.py +10 -8
- celldetective/gui/plot_signals_ui.py +39 -11
- celldetective/gui/process_block.py +413 -95
- celldetective/gui/retrain_segmentation_model_options.py +17 -4
- celldetective/gui/retrain_signal_model_options.py +106 -6
- celldetective/gui/signal_annotator.py +25 -5
- celldetective/gui/signal_annotator2.py +2708 -0
- celldetective/gui/signal_annotator_options.py +3 -1
- celldetective/gui/survival_ui.py +15 -6
- celldetective/gui/tableUI.py +235 -39
- celldetective/io.py +537 -421
- celldetective/measure.py +919 -969
- celldetective/models/pair_signal_detection/blank +0 -0
- celldetective/neighborhood.py +426 -354
- celldetective/relative_measurements.py +648 -0
- celldetective/scripts/analyze_signals.py +1 -1
- celldetective/scripts/measure_cells.py +28 -8
- celldetective/scripts/measure_relative.py +103 -0
- celldetective/scripts/segment_cells.py +5 -5
- celldetective/scripts/track_cells.py +4 -1
- celldetective/scripts/train_segmentation_model.py +23 -18
- celldetective/scripts/train_signal_model.py +33 -0
- celldetective/signals.py +402 -8
- celldetective/tracking.py +8 -2
- celldetective/utils.py +93 -0
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.0.dist-info}/METADATA +8 -8
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.0.dist-info}/RECORD +38 -34
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.0.dist-info}/WHEEL +1 -1
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.0.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.0.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.0.dist-info}/top_level.txt +0 -0
celldetective/neighborhood.py
CHANGED
|
@@ -8,17 +8,15 @@ from mahotas.features import haralick
|
|
|
8
8
|
from scipy.ndimage import zoom
|
|
9
9
|
import os
|
|
10
10
|
import subprocess
|
|
11
|
-
from celldetective.utils import rename_intensity_column, create_patch_mask, remove_redundant_features
|
|
11
|
+
from celldetective.utils import contour_of_instance_segmentation, rename_intensity_column, create_patch_mask, remove_redundant_features, extract_identity_col
|
|
12
12
|
from scipy.spatial.distance import cdist
|
|
13
|
-
from celldetective.measure import contour_of_instance_segmentation
|
|
14
13
|
from celldetective.io import locate_labels, get_position_pickle, get_position_table
|
|
15
14
|
import re
|
|
16
15
|
|
|
17
16
|
abs_path = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], 'celldetective'])
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
def set_live_status(setA,setB,status, not_status_option):
|
|
21
|
-
|
|
19
|
+
def set_live_status(setA, setB, status, not_status_option):
|
|
22
20
|
"""
|
|
23
21
|
Updates the live status for cells in two datasets based on specified status columns and options.
|
|
24
22
|
|
|
@@ -58,35 +56,37 @@ def set_live_status(setA,setB,status, not_status_option):
|
|
|
58
56
|
if status[0] is None or status[0]=='live_status':
|
|
59
57
|
setA.loc[:,'live_status'] = 1
|
|
60
58
|
status[0] = 'live_status'
|
|
61
|
-
elif status[0] is not None and isinstance(not_status_option,list):
|
|
62
|
-
setA.loc[setA[status[0]]==2,status[0]] = 1
|
|
59
|
+
elif status[0] is not None and isinstance(not_status_option, list):
|
|
60
|
+
setA.loc[setA[status[0]] == 2, status[0]] = 1 # already happened events become event
|
|
63
61
|
if not_status_option[0]:
|
|
64
62
|
setA.loc[:,'not_'+status[0]] = [not a if a==0 or a==1 else np.nan for a in setA.loc[:,status[0]].values]
|
|
65
63
|
status[0] = 'not_'+status[0]
|
|
66
64
|
if status[1] is None or status[1]=='live_status':
|
|
67
65
|
setB.loc[:,'live_status'] = 1
|
|
68
66
|
status[1] = 'live_status'
|
|
69
|
-
elif status[1] is not None and isinstance(not_status_option,list):
|
|
70
|
-
setB.loc[setB[status[1]]==2,status[1]] = 1
|
|
67
|
+
elif status[1] is not None and isinstance(not_status_option, list):
|
|
68
|
+
setB.loc[setB[status[1]] == 2, status[1]] = 1 # already happened events become event
|
|
71
69
|
if not_status_option[1]:
|
|
72
|
-
setB.loc[:,'not_'+status[1]] = [not a if a==0 or a==1 else np.nan for a in
|
|
73
|
-
|
|
70
|
+
setB.loc[:, 'not_' + status[1]] = [not a if a == 0 or a == 1 else np.nan for a in
|
|
71
|
+
setB.loc[:, status[1]].values]
|
|
72
|
+
status[1] = 'not_' + status[1]
|
|
74
73
|
|
|
75
74
|
assert status[0] in list(setA.columns)
|
|
76
75
|
assert status[1] in list(setB.columns)
|
|
77
|
-
|
|
76
|
+
|
|
78
77
|
setA = setA.reset_index(drop=True)
|
|
79
|
-
setB = setB.reset_index(drop=True)
|
|
78
|
+
setB = setB.reset_index(drop=True)
|
|
80
79
|
|
|
81
80
|
return setA, setB, status
|
|
82
81
|
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
|
|
83
|
+
def compute_attention_weight(dist_matrix, cut_distance, opposite_cell_status, opposite_cell_ids, axis=1,
|
|
84
|
+
include_dead_weight=True):
|
|
85
85
|
"""
|
|
86
86
|
Computes the attention weight for each cell based on its proximity to cells of an opposite type within a specified distance.
|
|
87
87
|
|
|
88
88
|
This function calculates the attention weight for cells by considering the distance to the cells of an opposite type
|
|
89
|
-
within a given cutoff distance. It optionally considers only the 'live' opposite cells based on their status. The function
|
|
89
|
+
within a given cutoff distance. It optionally considers only the 'live' opposite cells based on their status. The function
|
|
90
90
|
returns two arrays: one containing the attention weights and another containing the IDs of the closest opposite cells.
|
|
91
91
|
|
|
92
92
|
Parameters
|
|
@@ -117,34 +117,36 @@ def compute_attention_weight(dist_matrix, cut_distance, opposite_cell_status, op
|
|
|
117
117
|
closest_opposite = np.empty(dist_matrix.shape[axis])
|
|
118
118
|
|
|
119
119
|
for i in range(dist_matrix.shape[axis]):
|
|
120
|
-
if axis==1:
|
|
121
|
-
row = dist_matrix[:,i]
|
|
122
|
-
elif axis==0:
|
|
123
|
-
row = dist_matrix[i
|
|
124
|
-
row[row==0.] = 1.0E06
|
|
125
|
-
nbr_opposite = len(row[row<=cut_distance])
|
|
126
|
-
|
|
120
|
+
if axis == 1:
|
|
121
|
+
row = dist_matrix[:, i]
|
|
122
|
+
elif axis == 0:
|
|
123
|
+
row = dist_matrix[i, :]
|
|
124
|
+
row[row == 0.] = 1.0E06
|
|
125
|
+
nbr_opposite = len(row[row <= cut_distance])
|
|
126
|
+
|
|
127
127
|
if not include_dead_weight:
|
|
128
|
-
stat = opposite_cell_status[np.where(row<=cut_distance)[0]]
|
|
129
|
-
nbr_opposite = len(stat[stat==1])
|
|
130
|
-
index_subpop = np.argmin(row[opposite_cell_status==1])
|
|
131
|
-
closest_opposite[i] = opposite_cell_ids[opposite_cell_status==1][index_subpop]
|
|
128
|
+
stat = opposite_cell_status[np.where(row <= cut_distance)[0]]
|
|
129
|
+
nbr_opposite = len(stat[stat == 1])
|
|
130
|
+
index_subpop = np.argmin(row[opposite_cell_status == 1])
|
|
131
|
+
closest_opposite[i] = opposite_cell_ids[opposite_cell_status == 1][index_subpop]
|
|
132
132
|
else:
|
|
133
133
|
closest_opposite[i] = opposite_cell_ids[np.argmin(row)]
|
|
134
|
-
|
|
135
|
-
if nbr_opposite>0:
|
|
136
|
-
weight = 1
|
|
134
|
+
|
|
135
|
+
if nbr_opposite > 0:
|
|
136
|
+
weight = 1. / float(nbr_opposite)
|
|
137
137
|
weights[i] = weight
|
|
138
138
|
|
|
139
139
|
return weights, closest_opposite
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
|
|
142
|
+
def distance_cut_neighborhood(setA, setB, distance, mode='two-pop', status=None, not_status_option=None,
|
|
143
|
+
compute_cum_sum=True,
|
|
142
144
|
attention_weight=True, symmetrize=True, include_dead_weight=True,
|
|
143
|
-
column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X',
|
|
144
|
-
|
|
145
|
+
column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X',
|
|
146
|
+
'y': 'POSITION_Y'}):
|
|
145
147
|
"""
|
|
146
148
|
|
|
147
|
-
Match neighbors in set A and B within a circle of radius d.
|
|
149
|
+
Match neighbors in set A and B within a circle of radius d.
|
|
148
150
|
|
|
149
151
|
Parameters
|
|
150
152
|
----------
|
|
@@ -154,7 +156,7 @@ def distance_cut_neighborhood(setA, setB, distance, mode='two-pop', status=None,
|
|
|
154
156
|
Cut-distance in pixels to match neighboring pairs.
|
|
155
157
|
mode: str
|
|
156
158
|
neighboring mode, between 'two-pop' (e.g. target-effector) and 'self' (target-target or effector-effector).
|
|
157
|
-
status: None or status
|
|
159
|
+
status: None or status
|
|
158
160
|
name to look for cells to ignore (because they are dead). By default all cells are kept.
|
|
159
161
|
compute_cum_sum: bool,
|
|
160
162
|
compute cumulated time of presence of neighbours (only if trajectories available for both sets)
|
|
@@ -170,112 +172,115 @@ def distance_cut_neighborhood(setA, setB, distance, mode='two-pop', status=None,
|
|
|
170
172
|
if setA is not None and setB is not None:
|
|
171
173
|
setA, setB, status = set_live_status(setA, setB, status, not_status_option)
|
|
172
174
|
else:
|
|
173
|
-
return None,None
|
|
175
|
+
return None, None
|
|
174
176
|
|
|
175
|
-
# Check distance option
|
|
177
|
+
# Check distance option
|
|
176
178
|
if not isinstance(distance, list):
|
|
177
179
|
distance = [distance]
|
|
178
|
-
|
|
180
|
+
|
|
179
181
|
for d in distance:
|
|
180
182
|
# loop over each provided distance
|
|
181
|
-
|
|
182
|
-
if mode=='two-pop':
|
|
183
|
+
|
|
184
|
+
if mode == 'two-pop':
|
|
183
185
|
neigh_col = f'neighborhood_2_circle_{d}_px'
|
|
184
|
-
elif mode=='self':
|
|
186
|
+
elif mode == 'self':
|
|
185
187
|
neigh_col = f'neighborhood_self_circle_{d}_px'
|
|
186
|
-
|
|
188
|
+
|
|
187
189
|
cl = []
|
|
188
|
-
for s in [setA,setB]:
|
|
190
|
+
for s in [setA, setB]:
|
|
189
191
|
|
|
190
192
|
# Check whether data can be tracked
|
|
191
193
|
temp_column_labels = column_labels.copy()
|
|
192
194
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
id_col = extract_identity_col(s)
|
|
196
|
+
temp_column_labels.update({'track': id_col})
|
|
197
|
+
if id_col=='ID':
|
|
198
|
+
compute_cum_sum = False # if no tracking data then cum_sum is not relevant
|
|
196
199
|
cl.append(temp_column_labels)
|
|
197
200
|
|
|
198
201
|
# Remove nan tracks (cells that do not belong to a track)
|
|
199
202
|
s[neigh_col] = np.nan
|
|
200
203
|
s[neigh_col] = s[neigh_col].astype(object)
|
|
201
|
-
s.dropna(subset=[cl[-1]['track']],inplace=True)
|
|
204
|
+
s.dropna(subset=[cl[-1]['track']], inplace=True)
|
|
202
205
|
|
|
203
206
|
# Loop over each available timestep
|
|
204
|
-
timeline = np.unique(np.concatenate([setA[cl[0]['time']].to_numpy(), setB[cl[1]['time']].to_numpy()])).astype(
|
|
207
|
+
timeline = np.unique(np.concatenate([setA[cl[0]['time']].to_numpy(), setB[cl[1]['time']].to_numpy()])).astype(
|
|
208
|
+
int)
|
|
205
209
|
for t in tqdm(timeline):
|
|
206
210
|
|
|
207
|
-
index_A = list(setA.loc[setA[cl[0]['time']]==t].index)
|
|
208
|
-
coordinates_A = setA.loc[setA[cl[0]['time']]==t,[cl[0]['x'], cl[0]['y']]].to_numpy()
|
|
209
|
-
ids_A = setA.loc[setA[cl[0]['time']]==t,cl[0]['track']].to_numpy()
|
|
210
|
-
status_A = setA.loc[setA[cl[0]['time']]==t,status[0]].to_numpy()
|
|
211
|
+
index_A = list(setA.loc[setA[cl[0]['time']] == t].index)
|
|
212
|
+
coordinates_A = setA.loc[setA[cl[0]['time']] == t, [cl[0]['x'], cl[0]['y']]].to_numpy()
|
|
213
|
+
ids_A = setA.loc[setA[cl[0]['time']] == t, cl[0]['track']].to_numpy()
|
|
214
|
+
status_A = setA.loc[setA[cl[0]['time']] == t, status[0]].to_numpy()
|
|
211
215
|
|
|
212
|
-
index_B = list(setB.loc[setB[cl[1]['time']]==t].index)
|
|
213
|
-
coordinates_B = setB.loc[setB[cl[1]['time']]==t,[cl[1]['x'], cl[1]['y']]].to_numpy()
|
|
214
|
-
ids_B = setB.loc[setB[cl[1]['time']]==t,cl[1]['track']].to_numpy()
|
|
215
|
-
status_B = setB.loc[setB[cl[1]['time']]==t,status[1]].to_numpy()
|
|
216
|
+
index_B = list(setB.loc[setB[cl[1]['time']] == t].index)
|
|
217
|
+
coordinates_B = setB.loc[setB[cl[1]['time']] == t, [cl[1]['x'], cl[1]['y']]].to_numpy()
|
|
218
|
+
ids_B = setB.loc[setB[cl[1]['time']] == t, cl[1]['track']].to_numpy()
|
|
219
|
+
status_B = setB.loc[setB[cl[1]['time']] == t, status[1]].to_numpy()
|
|
216
220
|
|
|
217
221
|
if len(ids_A) > 0 and len(ids_B) > 0:
|
|
218
|
-
|
|
222
|
+
|
|
219
223
|
# compute distance matrix
|
|
220
224
|
dist_map = cdist(coordinates_A, coordinates_B, metric="euclidean")
|
|
221
|
-
|
|
225
|
+
|
|
222
226
|
if attention_weight:
|
|
223
|
-
weights, closest_A = compute_attention_weight(dist_map, d, status_A, ids_A, axis=1,
|
|
224
|
-
|
|
227
|
+
weights, closest_A = compute_attention_weight(dist_map, d, status_A, ids_A, axis=1,
|
|
228
|
+
include_dead_weight=include_dead_weight)
|
|
229
|
+
|
|
225
230
|
# Target centric
|
|
226
231
|
for k in range(dist_map.shape[0]):
|
|
227
|
-
|
|
228
|
-
col = dist_map[k
|
|
229
|
-
col[col==0.] = 1.0E06
|
|
230
|
-
|
|
231
|
-
neighs_B = np.array([ids_B[i] for i in np.where((col<=d))[0]])
|
|
232
|
-
status_neigh_B = np.array([status_B[i] for i in np.where((col<=d))[0]])
|
|
233
|
-
dist_B = [round(col[i],2) for i in np.where((col<=d))[0]]
|
|
234
|
-
if len(dist_B)>0:
|
|
235
|
-
closest_B_cell = neighs_B[np.argmin(dist_B)]
|
|
236
|
-
|
|
232
|
+
|
|
233
|
+
col = dist_map[k, :]
|
|
234
|
+
col[col == 0.] = 1.0E06
|
|
235
|
+
|
|
236
|
+
neighs_B = np.array([ids_B[i] for i in np.where((col <= d))[0]])
|
|
237
|
+
status_neigh_B = np.array([status_B[i] for i in np.where((col <= d))[0]])
|
|
238
|
+
dist_B = [round(col[i], 2) for i in np.where((col <= d))[0]]
|
|
239
|
+
if len(dist_B) > 0:
|
|
240
|
+
closest_B_cell = neighs_B[np.argmin(dist_B)]
|
|
241
|
+
|
|
237
242
|
if symmetrize and attention_weight:
|
|
238
243
|
n_neighs = float(len(neighs_B))
|
|
239
244
|
if not include_dead_weight:
|
|
240
|
-
n_neighs_alive = len(np.where(status_neigh_B==1)[0])
|
|
245
|
+
n_neighs_alive = len(np.where(status_neigh_B == 1)[0])
|
|
241
246
|
neigh_count = n_neighs_alive
|
|
242
247
|
else:
|
|
243
248
|
neigh_count = n_neighs
|
|
244
|
-
if neigh_count>0:
|
|
245
|
-
weight_A = 1
|
|
249
|
+
if neigh_count > 0:
|
|
250
|
+
weight_A = 1. / neigh_count
|
|
246
251
|
else:
|
|
247
252
|
weight_A = np.nan
|
|
248
253
|
|
|
249
|
-
if not include_dead_weight and status_A[k]==0:
|
|
254
|
+
if not include_dead_weight and status_A[k] == 0:
|
|
250
255
|
weight_A = 0
|
|
251
|
-
|
|
256
|
+
|
|
252
257
|
neighs = []
|
|
253
258
|
setA.at[index_A[k], neigh_col] = []
|
|
254
259
|
for n in range(len(neighs_B)):
|
|
255
|
-
|
|
260
|
+
|
|
256
261
|
# index in setB
|
|
257
|
-
n_index = np.where(ids_B==neighs_B[n])[0][0]
|
|
262
|
+
n_index = np.where(ids_B == neighs_B[n])[0][0]
|
|
258
263
|
# Assess if neigh B is closest to A
|
|
259
264
|
if attention_weight:
|
|
260
|
-
if closest_A[n_index]==ids_A[k]:
|
|
265
|
+
if closest_A[n_index] == ids_A[k]:
|
|
261
266
|
closest = True
|
|
262
267
|
else:
|
|
263
268
|
closest = False
|
|
264
|
-
|
|
269
|
+
|
|
265
270
|
if symmetrize:
|
|
266
271
|
# Load neighborhood previous data
|
|
267
272
|
sym_neigh = setB.loc[index_B[n_index], neigh_col]
|
|
268
|
-
if neighs_B[n]==closest_B_cell:
|
|
269
|
-
closest_b=True
|
|
273
|
+
if neighs_B[n] == closest_B_cell:
|
|
274
|
+
closest_b = True
|
|
270
275
|
else:
|
|
271
|
-
closest_b=False
|
|
276
|
+
closest_b = False
|
|
272
277
|
if isinstance(sym_neigh, list):
|
|
273
278
|
sym_neigh.append({'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]})
|
|
274
279
|
else:
|
|
275
|
-
sym_neigh = [{'id': ids_A[k], 'distance': dist_B[n],'status': status_A[k]}]
|
|
280
|
+
sym_neigh = [{'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]}]
|
|
276
281
|
if attention_weight:
|
|
277
282
|
sym_neigh[-1].update({'weight': weight_A, 'closest': closest_b})
|
|
278
|
-
|
|
283
|
+
|
|
279
284
|
# Write the minimum info about neighborhing cell B
|
|
280
285
|
neigh_dico = {'id': neighs_B[n], 'distance': dist_B[n], 'status': status_neigh_B[n]}
|
|
281
286
|
if attention_weight:
|
|
@@ -283,33 +288,42 @@ def distance_cut_neighborhood(setA, setB, distance, mode='two-pop', status=None,
|
|
|
283
288
|
|
|
284
289
|
if compute_cum_sum:
|
|
285
290
|
# Compute the integrated presence of the neighboring cell B
|
|
286
|
-
assert cl[1][
|
|
287
|
-
|
|
291
|
+
assert cl[1][
|
|
292
|
+
'track'] == 'TRACK_ID', 'The set B does not seem to contain tracked data. The cumulative time will be meaningless.'
|
|
293
|
+
past_neighs = [[ll['id'] for ll in l] if len(l) > 0 else [None] for l in setA.loc[
|
|
294
|
+
(setA[cl[0]['track']] == ids_A[k]) & (setA[cl[0]['time']] <= t), neigh_col].to_numpy()]
|
|
288
295
|
past_neighs = [item for sublist in past_neighs for item in sublist]
|
|
289
|
-
|
|
296
|
+
|
|
290
297
|
if attention_weight:
|
|
291
|
-
past_weights = [[ll['weight'] for ll in l] if len(l)>0 else [None] for l in setA.loc[
|
|
298
|
+
past_weights = [[ll['weight'] for ll in l] if len(l) > 0 else [None] for l in setA.loc[
|
|
299
|
+
(setA[cl[0]['track']] == ids_A[k]) & (
|
|
300
|
+
setA[cl[0]['time']] <= t), neigh_col].to_numpy()]
|
|
292
301
|
past_weights = [item for sublist in past_weights for item in sublist]
|
|
293
302
|
|
|
294
|
-
cum_sum = len(np.where(past_neighs==neighs_B[n])[0])
|
|
295
|
-
neigh_dico.update({'cumulated_presence': cum_sum+1})
|
|
296
|
-
|
|
303
|
+
cum_sum = len(np.where(past_neighs == neighs_B[n])[0])
|
|
304
|
+
neigh_dico.update({'cumulated_presence': cum_sum + 1})
|
|
305
|
+
|
|
297
306
|
if attention_weight:
|
|
298
|
-
cum_sum_weighted = np.sum(
|
|
307
|
+
cum_sum_weighted = np.sum(
|
|
308
|
+
[w if l == neighs_B[n] else 0 for l, w in zip(past_neighs, past_weights)])
|
|
299
309
|
neigh_dico.update({'cumulated_presence_weighted': cum_sum_weighted + weights[n_index]})
|
|
300
310
|
|
|
301
311
|
if symmetrize:
|
|
302
312
|
setB.at[index_B[n_index], neigh_col] = sym_neigh
|
|
303
|
-
|
|
313
|
+
|
|
304
314
|
neighs.append(neigh_dico)
|
|
305
|
-
|
|
315
|
+
|
|
306
316
|
setA.at[index_A[k], neigh_col] = neighs
|
|
307
|
-
|
|
317
|
+
|
|
308
318
|
return setA, setB
|
|
309
319
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
320
|
+
|
|
321
|
+
def compute_neighborhood_at_position(pos, distance, population=['targets', 'effectors'], theta_dist=None,
|
|
322
|
+
img_shape=(2048, 2048), return_tables=False, clear_neigh=False,
|
|
323
|
+
event_time_col=None,
|
|
324
|
+
neighborhood_kwargs={'mode': 'two-pop', 'status': None, 'not_status_option': None,
|
|
325
|
+
'include_dead_weight': True, "compute_cum_sum": False,
|
|
326
|
+
"attention_weight": True, 'symmetrize': True}):
|
|
313
327
|
"""
|
|
314
328
|
Computes neighborhood metrics for specified cell populations within a given position, based on distance criteria and additional parameters.
|
|
315
329
|
|
|
@@ -347,12 +361,12 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
347
361
|
------
|
|
348
362
|
AssertionError
|
|
349
363
|
If the specified position path does not exist or if the number of distances and edge thresholds do not match.
|
|
350
|
-
|
|
364
|
+
|
|
351
365
|
"""
|
|
352
366
|
|
|
353
|
-
pos = pos.replace('\\','/')
|
|
367
|
+
pos = pos.replace('\\', '/')
|
|
354
368
|
pos = rf"{pos}"
|
|
355
|
-
assert os.path.exists(pos),f'Position {pos} is not a valid path.'
|
|
369
|
+
assert os.path.exists(pos), f'Position {pos} is not a valid path.'
|
|
356
370
|
|
|
357
371
|
if isinstance(population, str):
|
|
358
372
|
population = [population, population]
|
|
@@ -363,17 +377,27 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
363
377
|
theta_dist = [theta_dist]
|
|
364
378
|
|
|
365
379
|
if theta_dist is None:
|
|
366
|
-
theta_dist = [0.9*d for d in distance]
|
|
367
|
-
assert len(theta_dist)==len(distance),'Incompatible number of distances and number of edge thresholds.'
|
|
380
|
+
theta_dist = [0.9 * d for d in distance]
|
|
381
|
+
assert len(theta_dist) == len(distance), 'Incompatible number of distances and number of edge thresholds.'
|
|
368
382
|
|
|
369
|
-
if population[0]==population[1]:
|
|
383
|
+
if population[0] == population[1]:
|
|
370
384
|
neighborhood_kwargs.update({'mode': 'self'})
|
|
371
|
-
if population[1]!=population[0]:
|
|
385
|
+
if population[1] != population[0]:
|
|
372
386
|
neighborhood_kwargs.update({'mode': 'two-pop'})
|
|
373
387
|
|
|
374
388
|
df_A, path_A = get_position_table(pos, population=population[0], return_path=True)
|
|
375
389
|
df_B, path_B = get_position_table(pos, population=population[1], return_path=True)
|
|
376
390
|
|
|
391
|
+
if clear_neigh:
|
|
392
|
+
if os.path.exists(path_A.replace('.csv','.pkl')):
|
|
393
|
+
os.remove(path_A.replace('.csv','.pkl'))
|
|
394
|
+
if os.path.exists(path_B.replace('.csv','.pkl')):
|
|
395
|
+
os.remove(path_B.replace('.csv','.pkl'))
|
|
396
|
+
df_pair, pair_path = get_position_table(pos, population='pairs', return_path=True)
|
|
397
|
+
if df_pair is not None:
|
|
398
|
+
os.remove(pair_path)
|
|
399
|
+
|
|
400
|
+
|
|
377
401
|
df_A_pkl = get_position_pickle(pos, population=population[0], return_path=False)
|
|
378
402
|
df_B_pkl = get_position_pickle(pos, population=population[1], return_path=False)
|
|
379
403
|
|
|
@@ -382,12 +406,9 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
382
406
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
383
407
|
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
384
408
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
else:
|
|
389
|
-
cols.append('ID')
|
|
390
|
-
on_cols = ['ID','FRAME']
|
|
409
|
+
id_col = extract_identity_col(df_A_pkl)
|
|
410
|
+
cols.append(id_col)
|
|
411
|
+
on_cols = [id_col, 'FRAME']
|
|
391
412
|
|
|
392
413
|
print(f'Recover {cols} from the pickle file...')
|
|
393
414
|
try:
|
|
@@ -401,12 +422,9 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
401
422
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
402
423
|
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
403
424
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
else:
|
|
408
|
-
cols.append('ID')
|
|
409
|
-
on_cols = ['ID','FRAME']
|
|
425
|
+
id_col = extract_identity_col(df_B_pkl)
|
|
426
|
+
cols.append(id_col)
|
|
427
|
+
on_cols = [id_col, 'FRAME']
|
|
410
428
|
|
|
411
429
|
print(f'Recover {cols} from the pickle file...')
|
|
412
430
|
try:
|
|
@@ -418,17 +436,17 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
418
436
|
unwanted = df_A.columns[df_A.columns.str.contains('neighborhood')]
|
|
419
437
|
df_A = df_A.drop(columns=unwanted)
|
|
420
438
|
unwanted = df_B.columns[df_B.columns.str.contains('neighborhood')]
|
|
421
|
-
df_B = df_B.drop(columns=unwanted)
|
|
439
|
+
df_B = df_B.drop(columns=unwanted)
|
|
422
440
|
|
|
423
|
-
df_A, df_B = distance_cut_neighborhood(df_A,df_B, distance
|
|
424
|
-
if df_A is None or df_B is None:
|
|
441
|
+
df_A, df_B = distance_cut_neighborhood(df_A, df_B, distance, **neighborhood_kwargs)
|
|
442
|
+
if df_A is None or df_B is None or len(df_A)==0:
|
|
425
443
|
return None
|
|
426
444
|
|
|
427
|
-
for td,d in zip(theta_dist, distance):
|
|
445
|
+
for td, d in zip(theta_dist, distance):
|
|
428
446
|
|
|
429
|
-
if neighborhood_kwargs['mode']=='two-pop':
|
|
447
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
430
448
|
neigh_col = f'neighborhood_2_circle_{d}_px'
|
|
431
|
-
elif neighborhood_kwargs['mode']=='self':
|
|
449
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
432
450
|
neigh_col = f'neighborhood_self_circle_{d}_px'
|
|
433
451
|
|
|
434
452
|
# edge_filter_A = (df_A['POSITION_X'] > td)&(df_A['POSITION_Y'] > td)&(df_A['POSITION_Y'] < (img_shape[0] - td))&(df_A['POSITION_X'] < (img_shape[1] - td))
|
|
@@ -438,28 +456,37 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
438
456
|
|
|
439
457
|
print('Count neighborhood...')
|
|
440
458
|
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
441
|
-
if neighborhood_kwargs['symmetrize']:
|
|
442
|
-
|
|
459
|
+
# if neighborhood_kwargs['symmetrize']:
|
|
460
|
+
# df_B = compute_neighborhood_metrics(df_B, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
443
461
|
print('Done...')
|
|
444
|
-
|
|
445
|
-
if 'TRACK_ID' in list(df_A.columns):
|
|
446
|
-
print('Estimate average neighborhood before/after event...')
|
|
447
|
-
df_A = mean_neighborhood_before_event(df_A, neigh_col, event_time_col)
|
|
448
|
-
df_A = mean_neighborhood_after_event(df_A, neigh_col, event_time_col)
|
|
449
|
-
print('Done...')
|
|
450
462
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
463
|
+
if 'TRACK_ID' in list(df_A.columns):
|
|
464
|
+
if not np.all(df_A['TRACK_ID'].isnull()):
|
|
465
|
+
print('Estimate average neighborhood before/after event...')
|
|
466
|
+
df_A = mean_neighborhood_before_event(df_A, neigh_col, event_time_col)
|
|
467
|
+
if event_time_col is not None:
|
|
468
|
+
df_A = mean_neighborhood_after_event(df_A, neigh_col, event_time_col)
|
|
469
|
+
print('Done...')
|
|
470
|
+
|
|
471
|
+
df_A.to_pickle(path_A.replace('.csv', '.pkl'))
|
|
472
|
+
if not population[0] == population[1]:
|
|
473
|
+
# Remove neighborhood column
|
|
474
|
+
for td, d in zip(theta_dist, distance):
|
|
475
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
476
|
+
neigh_col = f'neighborhood_2_circle_{d}_px'
|
|
477
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
478
|
+
neigh_col = f'neighborhood_self_circle_{d}_px'
|
|
479
|
+
df_B = df_B.drop(columns=[neigh_col])
|
|
480
|
+
df_B.to_pickle(path_B.replace('.csv', '.pkl'))
|
|
454
481
|
|
|
455
482
|
unwanted = df_A.columns[df_A.columns.str.startswith('neighborhood_')]
|
|
456
483
|
df_A2 = df_A.drop(columns=unwanted)
|
|
457
484
|
df_A2.to_csv(path_A, index=False)
|
|
458
485
|
|
|
459
|
-
if not population[0]==population[1]:
|
|
486
|
+
if not population[0] == population[1]:
|
|
460
487
|
unwanted = df_B.columns[df_B.columns.str.startswith('neighborhood_')]
|
|
461
488
|
df_B_csv = df_B.drop(unwanted, axis=1, inplace=False)
|
|
462
|
-
df_B_csv.to_csv(path_B,index=False)
|
|
489
|
+
df_B_csv.to_csv(path_B, index=False)
|
|
463
490
|
|
|
464
491
|
if return_tables:
|
|
465
492
|
return df_A, df_B
|
|
@@ -508,7 +535,7 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
508
535
|
>>> neigh_col = 'neighborhood_info'
|
|
509
536
|
>>> updated_neigh_table = compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive'], decompose_by_status=True)
|
|
510
537
|
# Computes the inclusive count of neighbors for each cell, decomposed by cell status.
|
|
511
|
-
|
|
538
|
+
|
|
512
539
|
"""
|
|
513
540
|
|
|
514
541
|
neigh_table = neigh_table.reset_index(drop=True)
|
|
@@ -516,14 +543,13 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
516
543
|
groupbycols = ['position']
|
|
517
544
|
else:
|
|
518
545
|
groupbycols = []
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
groupbycols.append('ID')
|
|
546
|
+
|
|
547
|
+
id_col = extract_identity_col(neigh_table)
|
|
548
|
+
groupbycols.append(id_col)
|
|
523
549
|
|
|
524
550
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
525
551
|
|
|
526
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
552
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
527
553
|
group = group.dropna(subset=neigh_col)
|
|
528
554
|
indices = list(group.index)
|
|
529
555
|
neighbors = group[neigh_col].to_numpy()
|
|
@@ -569,49 +595,52 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
569
595
|
if 'intermediate' in metrics:
|
|
570
596
|
n_intermediate[t] = np.sum(weights_at_t)
|
|
571
597
|
if 'exclusive' in metrics:
|
|
572
|
-
n_exclusive[t] = sum([c==1.0 for c in closest_at_t])
|
|
598
|
+
n_exclusive[t] = sum([c == 1.0 for c in closest_at_t])
|
|
573
599
|
|
|
574
600
|
if decompose_by_status:
|
|
575
601
|
|
|
576
602
|
if 'inclusive' in metrics:
|
|
577
|
-
n_inclusive_status_0[t] = sum([s==0.0 for s in status_at_t])
|
|
578
|
-
n_inclusive_status_1[t] = sum([s==1.0 for s in status_at_t])
|
|
603
|
+
n_inclusive_status_0[t] = sum([s == 0.0 for s in status_at_t])
|
|
604
|
+
n_inclusive_status_1[t] = sum([s == 1.0 for s in status_at_t])
|
|
579
605
|
|
|
580
606
|
if 'intermediate' in metrics:
|
|
581
607
|
weights_at_t = np.array(weights_at_t)
|
|
582
|
-
|
|
608
|
+
|
|
583
609
|
# intermediate
|
|
584
|
-
weights_status_1 = weights_at_t[np.array([s==1.0 for s in status_at_t],dtype=bool)]
|
|
585
|
-
weights_status_0 = weights_at_t[np.array([s==0.0 for s in status_at_t],dtype=bool)]
|
|
610
|
+
weights_status_1 = weights_at_t[np.array([s == 1.0 for s in status_at_t], dtype=bool)]
|
|
611
|
+
weights_status_0 = weights_at_t[np.array([s == 0.0 for s in status_at_t], dtype=bool)]
|
|
586
612
|
n_intermediate_status_1[t] = np.sum(weights_status_1)
|
|
587
613
|
n_intermediate_status_0[t] = np.sum(weights_status_0)
|
|
588
614
|
|
|
589
615
|
if 'exclusive' in metrics:
|
|
590
|
-
n_exclusive_status_0[t] = sum(
|
|
591
|
-
|
|
616
|
+
n_exclusive_status_0[t] = sum(
|
|
617
|
+
[c == 1.0 if s == 0.0 else False for c, s in zip(closest_at_t, status_at_t)])
|
|
618
|
+
n_exclusive_status_1[t] = sum(
|
|
619
|
+
[c == 1.0 if s == 1.0 else False for c, s in zip(closest_at_t, status_at_t)])
|
|
592
620
|
|
|
593
621
|
if 'inclusive' in metrics:
|
|
594
|
-
neigh_table.loc[indices, 'inclusive_count_'+neigh_col] = n_inclusive
|
|
622
|
+
neigh_table.loc[indices, 'inclusive_count_' + neigh_col] = n_inclusive
|
|
595
623
|
if 'intermediate' in metrics:
|
|
596
|
-
neigh_table.loc[indices, 'intermediate_count_'+neigh_col] = n_intermediate
|
|
624
|
+
neigh_table.loc[indices, 'intermediate_count_' + neigh_col] = n_intermediate
|
|
597
625
|
if 'exclusive' in metrics:
|
|
598
|
-
neigh_table.loc[indices, 'exclusive_count_'+neigh_col] = n_exclusive
|
|
626
|
+
neigh_table.loc[indices, 'exclusive_count_' + neigh_col] = n_exclusive
|
|
599
627
|
|
|
600
628
|
if decompose_by_status:
|
|
601
629
|
if 'inclusive' in metrics:
|
|
602
|
-
neigh_table.loc[indices, 'inclusive_count_s0_'+neigh_col] = n_inclusive_status_0
|
|
603
|
-
neigh_table.loc[indices, 'inclusive_count_s1_'+neigh_col] = n_inclusive_status_1
|
|
630
|
+
neigh_table.loc[indices, 'inclusive_count_s0_' + neigh_col] = n_inclusive_status_0
|
|
631
|
+
neigh_table.loc[indices, 'inclusive_count_s1_' + neigh_col] = n_inclusive_status_1
|
|
604
632
|
if 'intermediate' in metrics:
|
|
605
|
-
neigh_table.loc[indices, 'intermediate_count_s0_'+neigh_col] = n_intermediate_status_0
|
|
606
|
-
neigh_table.loc[indices, 'intermediate_count_s1_'+neigh_col] = n_intermediate_status_1
|
|
607
|
-
if 'exclusive' in metrics:
|
|
608
|
-
neigh_table.loc[indices, 'exclusive_count_s0_'+neigh_col] = n_exclusive_status_0
|
|
609
|
-
neigh_table.loc[indices, 'exclusive_count_s1_'+neigh_col] = n_exclusive_status_1
|
|
633
|
+
neigh_table.loc[indices, 'intermediate_count_s0_' + neigh_col] = n_intermediate_status_0
|
|
634
|
+
neigh_table.loc[indices, 'intermediate_count_s1_' + neigh_col] = n_intermediate_status_1
|
|
635
|
+
if 'exclusive' in metrics:
|
|
636
|
+
neigh_table.loc[indices, 'exclusive_count_s0_' + neigh_col] = n_exclusive_status_0
|
|
637
|
+
neigh_table.loc[indices, 'exclusive_count_s1_' + neigh_col] = n_exclusive_status_1
|
|
610
638
|
|
|
611
639
|
return neigh_table
|
|
612
640
|
|
|
613
|
-
|
|
614
|
-
|
|
641
|
+
|
|
642
|
+
def mean_neighborhood_before_event(neigh_table, neigh_col, event_time_col,
|
|
643
|
+
metrics=['inclusive', 'exclusive', 'intermediate']):
|
|
615
644
|
"""
|
|
616
645
|
Computes the mean neighborhood metrics for each cell track before a specified event time.
|
|
617
646
|
|
|
@@ -645,53 +674,59 @@ def mean_neighborhood_before_event(neigh_table, neigh_col, event_time_col, metri
|
|
|
645
674
|
groupbycols = ['position']
|
|
646
675
|
else:
|
|
647
676
|
groupbycols = []
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
groupbycols.append('ID')
|
|
677
|
+
|
|
678
|
+
id_col = extract_identity_col(neigh_table)
|
|
679
|
+
groupbycols.append(id_col)
|
|
652
680
|
|
|
653
681
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
654
682
|
suffix = '_before_event'
|
|
655
|
-
|
|
683
|
+
|
|
656
684
|
if event_time_col is None:
|
|
657
685
|
print('No event time was provided... Estimating the mean neighborhood over the whole observation time...')
|
|
658
|
-
neigh_table.loc[:,'event_time_temp'] = neigh_table['FRAME'].max()
|
|
686
|
+
neigh_table.loc[:, 'event_time_temp'] = neigh_table['FRAME'].max()
|
|
659
687
|
event_time_col = 'event_time_temp'
|
|
660
688
|
suffix = ''
|
|
661
|
-
|
|
662
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
689
|
+
|
|
690
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
663
691
|
|
|
664
692
|
group = group.dropna(subset=neigh_col)
|
|
665
693
|
indices = list(group.index)
|
|
666
694
|
|
|
667
695
|
event_time_values = group[event_time_col].to_numpy()
|
|
668
|
-
if len(event_time_values)>0:
|
|
696
|
+
if len(event_time_values) > 0:
|
|
669
697
|
event_time = event_time_values[0]
|
|
670
698
|
else:
|
|
671
699
|
continue
|
|
672
700
|
|
|
673
|
-
if event_time<0.:
|
|
701
|
+
if event_time < 0.:
|
|
674
702
|
event_time = group['FRAME'].max()
|
|
675
703
|
|
|
676
704
|
if 'intermediate' in metrics:
|
|
677
|
-
valid_counts_intermediate = group.loc[
|
|
678
|
-
|
|
679
|
-
|
|
705
|
+
valid_counts_intermediate = group.loc[
|
|
706
|
+
group['FRAME'] <= event_time, 'intermediate_count_s1_' + neigh_col].to_numpy()
|
|
707
|
+
if len(valid_counts_intermediate[valid_counts_intermediate == valid_counts_intermediate]) > 0:
|
|
708
|
+
neigh_table.loc[indices, f'mean_count_intermediate_{neigh_col}{suffix}'] = np.nanmean(
|
|
709
|
+
valid_counts_intermediate)
|
|
680
710
|
if 'inclusive' in metrics:
|
|
681
|
-
valid_counts_inclusive = group.loc[
|
|
682
|
-
|
|
683
|
-
|
|
711
|
+
valid_counts_inclusive = group.loc[
|
|
712
|
+
group['FRAME'] <= event_time, 'inclusive_count_s1_' + neigh_col].to_numpy()
|
|
713
|
+
if len(valid_counts_inclusive[valid_counts_inclusive == valid_counts_inclusive]) > 0:
|
|
714
|
+
neigh_table.loc[indices, f'mean_count_inclusive_{neigh_col}{suffix}'] = np.nanmean(
|
|
715
|
+
valid_counts_inclusive)
|
|
684
716
|
if 'exclusive' in metrics:
|
|
685
|
-
valid_counts_exclusive = group.loc[
|
|
686
|
-
|
|
687
|
-
|
|
717
|
+
valid_counts_exclusive = group.loc[
|
|
718
|
+
group['FRAME'] <= event_time, 'exclusive_count_s1_' + neigh_col].to_numpy()
|
|
719
|
+
if len(valid_counts_exclusive[valid_counts_exclusive == valid_counts_exclusive]) > 0:
|
|
720
|
+
neigh_table.loc[indices, f'mean_count_exclusive_{neigh_col}{suffix}'] = np.nanmean(
|
|
721
|
+
valid_counts_exclusive)
|
|
688
722
|
|
|
689
|
-
if event_time_col=='event_time_temp':
|
|
723
|
+
if event_time_col == 'event_time_temp':
|
|
690
724
|
neigh_table = neigh_table.drop(columns='event_time_temp')
|
|
691
725
|
return neigh_table
|
|
692
726
|
|
|
693
|
-
|
|
694
|
-
|
|
727
|
+
|
|
728
|
+
def mean_neighborhood_after_event(neigh_table, neigh_col, event_time_col,
|
|
729
|
+
metrics=['inclusive', 'exclusive', 'intermediate']):
|
|
695
730
|
"""
|
|
696
731
|
Computes the mean neighborhood metrics for each cell track after a specified event time.
|
|
697
732
|
|
|
@@ -725,55 +760,62 @@ def mean_neighborhood_after_event(neigh_table, neigh_col, event_time_col, metric
|
|
|
725
760
|
groupbycols = ['position']
|
|
726
761
|
else:
|
|
727
762
|
groupbycols = []
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
groupbycols.append('ID')
|
|
763
|
+
|
|
764
|
+
id_col = extract_identity_col(neigh_table)
|
|
765
|
+
groupbycols.append(id_col)
|
|
732
766
|
|
|
733
767
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
734
768
|
suffix = '_after_event'
|
|
735
|
-
|
|
769
|
+
|
|
736
770
|
if event_time_col is None:
|
|
737
|
-
neigh_table.loc[:,'event_time_temp'] = None
|
|
771
|
+
neigh_table.loc[:, 'event_time_temp'] = None # neigh_table['FRAME'].max()
|
|
738
772
|
event_time_col = 'event_time_temp'
|
|
739
773
|
suffix = ''
|
|
740
|
-
|
|
741
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
774
|
+
|
|
775
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
742
776
|
|
|
743
777
|
group = group.dropna(subset=neigh_col)
|
|
744
778
|
indices = list(group.index)
|
|
745
779
|
|
|
746
780
|
event_time_values = group[event_time_col].to_numpy()
|
|
747
|
-
if len(event_time_values)>0:
|
|
781
|
+
if len(event_time_values) > 0:
|
|
748
782
|
event_time = event_time_values[0]
|
|
749
783
|
else:
|
|
750
784
|
continue
|
|
751
785
|
|
|
752
|
-
if event_time is not None and (event_time>=0.):
|
|
786
|
+
if event_time is not None and (event_time >= 0.):
|
|
753
787
|
|
|
754
788
|
if 'intermediate' in metrics:
|
|
755
|
-
valid_counts_intermediate = group.loc[
|
|
756
|
-
|
|
757
|
-
|
|
789
|
+
valid_counts_intermediate = group.loc[
|
|
790
|
+
group['FRAME'] > event_time, 'intermediate_count_s1_' + neigh_col].to_numpy()
|
|
791
|
+
if len(valid_counts_intermediate[valid_counts_intermediate == valid_counts_intermediate]) > 0:
|
|
792
|
+
neigh_table.loc[indices, f'mean_count_intermediate_{neigh_col}{suffix}'] = np.nanmean(
|
|
793
|
+
valid_counts_intermediate)
|
|
758
794
|
if 'inclusive' in metrics:
|
|
759
|
-
valid_counts_inclusive = group.loc[
|
|
760
|
-
|
|
761
|
-
|
|
795
|
+
valid_counts_inclusive = group.loc[
|
|
796
|
+
group['FRAME'] > event_time, 'inclusive_count_s1_' + neigh_col].to_numpy()
|
|
797
|
+
if len(valid_counts_inclusive[valid_counts_inclusive == valid_counts_inclusive]) > 0:
|
|
798
|
+
neigh_table.loc[indices, f'mean_count_inclusive_{neigh_col}{suffix}'] = np.nanmean(
|
|
799
|
+
valid_counts_inclusive)
|
|
762
800
|
if 'exclusive' in metrics:
|
|
763
|
-
valid_counts_exclusive = group.loc[
|
|
764
|
-
|
|
765
|
-
|
|
801
|
+
valid_counts_exclusive = group.loc[
|
|
802
|
+
group['FRAME'] > event_time, 'exclusive_count_s1_' + neigh_col].to_numpy()
|
|
803
|
+
if len(valid_counts_exclusive[valid_counts_exclusive == valid_counts_exclusive]) > 0:
|
|
804
|
+
neigh_table.loc[indices, f'mean_count_exclusive_{neigh_col}{suffix}'] = np.nanmean(
|
|
805
|
+
valid_counts_exclusive)
|
|
766
806
|
|
|
767
|
-
if event_time_col=='event_time_temp':
|
|
807
|
+
if event_time_col == 'event_time_temp':
|
|
768
808
|
neigh_table = neigh_table.drop(columns='event_time_temp')
|
|
769
809
|
|
|
770
810
|
return neigh_table
|
|
771
811
|
|
|
812
|
+
|
|
772
813
|
# New functions for direct cell-cell contact neighborhood
|
|
773
814
|
|
|
774
815
|
def sign(num):
|
|
775
816
|
return -1 if num < 0 else 1
|
|
776
817
|
|
|
818
|
+
|
|
777
819
|
def contact_neighborhood(labelsA, labelsB=None, border=3, connectivity=2):
|
|
778
820
|
|
|
779
821
|
labelsA = labelsA.astype(float)
|
|
@@ -781,50 +823,50 @@ def contact_neighborhood(labelsA, labelsB=None, border=3, connectivity=2):
|
|
|
781
823
|
labelsB = labelsB.astype(float)
|
|
782
824
|
|
|
783
825
|
print(f"Border = {border}")
|
|
784
|
-
|
|
826
|
+
|
|
785
827
|
if border > 0:
|
|
786
828
|
print(labelsA.shape, border * (-1))
|
|
787
829
|
labelsA_edge = contour_of_instance_segmentation(label=labelsA, distance=border * (-1)).astype(float)
|
|
788
|
-
labelsA[np.where(labelsA_edge>0)] = labelsA_edge[np.where(labelsA_edge>0)]
|
|
830
|
+
labelsA[np.where(labelsA_edge > 0)] = labelsA_edge[np.where(labelsA_edge > 0)]
|
|
789
831
|
if labelsB is not None:
|
|
790
832
|
labelsB_edge = contour_of_instance_segmentation(label=labelsB, distance=border * (-1)).astype(float)
|
|
791
|
-
labelsB[np.where(labelsB_edge>0)] = labelsB_edge[np.where(labelsB_edge>0)]
|
|
792
|
-
|
|
833
|
+
labelsB[np.where(labelsB_edge > 0)] = labelsB_edge[np.where(labelsB_edge > 0)]
|
|
834
|
+
|
|
793
835
|
if labelsB is not None:
|
|
794
|
-
labelsA[labelsA!=0] = -labelsA[labelsA!=0]
|
|
836
|
+
labelsA[labelsA != 0] = -labelsA[labelsA != 0]
|
|
795
837
|
labelsAB = merge_labels(labelsA, labelsB)
|
|
796
838
|
labelsBA = merge_labels(labelsB, labelsA)
|
|
797
839
|
label_cases = [labelsAB, labelsBA]
|
|
798
840
|
else:
|
|
799
841
|
label_cases = [labelsA]
|
|
800
|
-
|
|
842
|
+
|
|
801
843
|
coocurrences = []
|
|
802
844
|
for lbl in label_cases:
|
|
803
845
|
coocurrences.extend(find_contact_neighbors(lbl, connectivity=connectivity))
|
|
804
|
-
|
|
805
|
-
unique_pairs = np.unique(coocurrences,axis=0)
|
|
806
|
-
|
|
846
|
+
|
|
847
|
+
unique_pairs = np.unique(coocurrences, axis=0)
|
|
848
|
+
|
|
807
849
|
if labelsB is not None:
|
|
808
|
-
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0]!=p[1] and sign(p[0])!=sign(p[1])],
|
|
850
|
+
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0] != p[1] and sign(p[0]) != sign(p[1])],
|
|
851
|
+
axis=0)
|
|
809
852
|
else:
|
|
810
|
-
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0]!=p[1]],axis=0)
|
|
811
|
-
|
|
853
|
+
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0] != p[1]], axis=0)
|
|
854
|
+
|
|
812
855
|
return neighs
|
|
813
856
|
|
|
814
857
|
def merge_labels(labelsA, labelsB):
|
|
815
|
-
|
|
816
858
|
labelsA = labelsA.astype(float)
|
|
817
859
|
labelsB = labelsB.astype(float)
|
|
818
|
-
|
|
860
|
+
|
|
819
861
|
labelsAB = labelsA.copy()
|
|
820
|
-
labelsAB[np.where(labelsB!=0)] = labelsB[np.where(labelsB!=0)]
|
|
821
|
-
|
|
862
|
+
labelsAB[np.where(labelsB != 0)] = labelsB[np.where(labelsB != 0)]
|
|
863
|
+
|
|
822
864
|
return labelsAB
|
|
823
865
|
|
|
866
|
+
|
|
824
867
|
def find_contact_neighbors(labels, connectivity=2):
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
g, nodes = pixel_graph(labels, mask=labels.astype(bool),connectivity=connectivity)
|
|
868
|
+
assert labels.ndim == 2, "Wrong dimension for labels..."
|
|
869
|
+
g, nodes = pixel_graph(labels, mask=labels.astype(bool), connectivity=connectivity)
|
|
828
870
|
g.eliminate_zeros()
|
|
829
871
|
|
|
830
872
|
coo = g.tocoo()
|
|
@@ -833,18 +875,19 @@ def find_contact_neighbors(labels, connectivity=2):
|
|
|
833
875
|
|
|
834
876
|
center_values = labels.ravel()[center_coords]
|
|
835
877
|
neighbor_values = labels.ravel()[neighbor_coords]
|
|
836
|
-
touching_masks = np.column_stack((center_values, neighbor_values))
|
|
837
|
-
|
|
878
|
+
touching_masks = np.column_stack((center_values, neighbor_values))
|
|
879
|
+
|
|
838
880
|
return touching_masks
|
|
839
881
|
|
|
840
882
|
|
|
841
|
-
def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-pop', status=None,
|
|
883
|
+
def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-pop', status=None,
|
|
884
|
+
not_status_option=None, compute_cum_sum=True,
|
|
842
885
|
attention_weight=True, symmetrize=True, include_dead_weight=True,
|
|
843
|
-
column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y',
|
|
844
|
-
|
|
886
|
+
column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y',
|
|
887
|
+
'mask_id': 'class_id'}):
|
|
845
888
|
"""
|
|
846
889
|
|
|
847
|
-
Match neighbors in set A and B within a circle of radius d.
|
|
890
|
+
Match neighbors in set A and B within a circle of radius d.
|
|
848
891
|
|
|
849
892
|
Parameters
|
|
850
893
|
----------
|
|
@@ -854,7 +897,7 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
854
897
|
Cut-distance in pixels to match neighboring pairs.
|
|
855
898
|
mode: str
|
|
856
899
|
neighboring mode, between 'two-pop' (e.g. target-effector) and 'self' (target-target or effector-effector).
|
|
857
|
-
status: None or status
|
|
900
|
+
status: None or status
|
|
858
901
|
name to look for cells to ignore (because they are dead). By default all cells are kept.
|
|
859
902
|
compute_cum_sum: bool,
|
|
860
903
|
compute cumulated time of presence of neighbours (only if trajectories available for both sets)
|
|
@@ -870,57 +913,59 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
870
913
|
if setA is not None and setB is not None:
|
|
871
914
|
setA, setB, status = set_live_status(setA, setB, status, not_status_option)
|
|
872
915
|
else:
|
|
873
|
-
return None,None
|
|
916
|
+
return None, None
|
|
874
917
|
|
|
875
|
-
# Check distance option
|
|
918
|
+
# Check distance option
|
|
876
919
|
if not isinstance(distance, list):
|
|
877
920
|
distance = [distance]
|
|
878
|
-
|
|
921
|
+
|
|
879
922
|
for d in distance:
|
|
880
923
|
# loop over each provided distance
|
|
881
|
-
|
|
882
|
-
if mode=='two-pop':
|
|
924
|
+
|
|
925
|
+
if mode == 'two-pop':
|
|
883
926
|
neigh_col = f'neighborhood_2_contact_{d}_px'
|
|
884
|
-
elif mode=='self':
|
|
927
|
+
elif mode == 'self':
|
|
885
928
|
neigh_col = f'neighborhood_self_contact_{d}_px'
|
|
886
|
-
|
|
929
|
+
|
|
887
930
|
cl = []
|
|
888
|
-
for s in [setA,setB]:
|
|
931
|
+
for s in [setA, setB]:
|
|
889
932
|
|
|
890
933
|
# Check whether data can be tracked
|
|
891
934
|
temp_column_labels = column_labels.copy()
|
|
892
935
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
936
|
+
id_col = extract_identity_col(s)
|
|
937
|
+
temp_column_labels.update({'track': id_col})
|
|
938
|
+
if id_col=='ID':
|
|
939
|
+
compute_cum_sum = False # if no tracking data then cum_sum is not relevant
|
|
896
940
|
cl.append(temp_column_labels)
|
|
897
941
|
|
|
898
942
|
# Remove nan tracks (cells that do not belong to a track)
|
|
899
943
|
s[neigh_col] = np.nan
|
|
900
944
|
s[neigh_col] = s[neigh_col].astype(object)
|
|
901
|
-
s.dropna(subset=[cl[-1]['track']],inplace=True)
|
|
945
|
+
s.dropna(subset=[cl[-1]['track']], inplace=True)
|
|
902
946
|
|
|
903
947
|
# Loop over each available timestep
|
|
904
|
-
timeline = np.unique(np.concatenate([setA[cl[0]['time']].to_numpy(), setB[cl[1]['time']].to_numpy()])).astype(
|
|
948
|
+
timeline = np.unique(np.concatenate([setA[cl[0]['time']].to_numpy(), setB[cl[1]['time']].to_numpy()])).astype(
|
|
949
|
+
int)
|
|
905
950
|
for t in tqdm(timeline):
|
|
906
951
|
|
|
907
|
-
index_A = list(setA.loc[setA[cl[0]['time']]==t].index)
|
|
908
|
-
coordinates_A = setA.loc[setA[cl[0]['time']]==t,[cl[0]['x'], cl[0]['y']]].to_numpy()
|
|
909
|
-
ids_A = setA.loc[setA[cl[0]['time']]==t,cl[0]['track']].to_numpy()
|
|
910
|
-
mask_ids_A = setA.loc[setA[cl[0]['time']]==t,cl[0]['mask_id']].to_numpy()
|
|
911
|
-
status_A = setA.loc[setA[cl[0]['time']]==t,status[0]].to_numpy()
|
|
912
|
-
|
|
913
|
-
index_B = list(setB.loc[setB[cl[1]['time']]==t].index)
|
|
914
|
-
coordinates_B = setB.loc[setB[cl[1]['time']]==t,[cl[1]['x'], cl[1]['y']]].to_numpy()
|
|
915
|
-
ids_B = setB.loc[setB[cl[1]['time']]==t,cl[1]['track']].to_numpy()
|
|
916
|
-
mask_ids_B = setB.loc[setB[cl[1]['time']]==t,cl[1]['mask_id']].to_numpy()
|
|
917
|
-
status_B = setB.loc[setB[cl[1]['time']]==t,status[1]].to_numpy()
|
|
918
|
-
|
|
952
|
+
index_A = list(setA.loc[setA[cl[0]['time']] == t].index)
|
|
953
|
+
coordinates_A = setA.loc[setA[cl[0]['time']] == t, [cl[0]['x'], cl[0]['y']]].to_numpy()
|
|
954
|
+
ids_A = setA.loc[setA[cl[0]['time']] == t, cl[0]['track']].to_numpy()
|
|
955
|
+
mask_ids_A = setA.loc[setA[cl[0]['time']] == t, cl[0]['mask_id']].to_numpy()
|
|
956
|
+
status_A = setA.loc[setA[cl[0]['time']] == t, status[0]].to_numpy()
|
|
957
|
+
|
|
958
|
+
index_B = list(setB.loc[setB[cl[1]['time']] == t].index)
|
|
959
|
+
coordinates_B = setB.loc[setB[cl[1]['time']] == t, [cl[1]['x'], cl[1]['y']]].to_numpy()
|
|
960
|
+
ids_B = setB.loc[setB[cl[1]['time']] == t, cl[1]['track']].to_numpy()
|
|
961
|
+
mask_ids_B = setB.loc[setB[cl[1]['time']] == t, cl[1]['mask_id']].to_numpy()
|
|
962
|
+
status_B = setB.loc[setB[cl[1]['time']] == t, status[1]].to_numpy()
|
|
963
|
+
|
|
919
964
|
print(f"Frame {t}")
|
|
920
|
-
print(f"{mask_ids_A=}",f"{mask_ids_B}")
|
|
965
|
+
print(f"{mask_ids_A=}", f"{mask_ids_B}")
|
|
921
966
|
|
|
922
967
|
if len(ids_A) > 0 and len(ids_B) > 0:
|
|
923
|
-
|
|
968
|
+
|
|
924
969
|
# compute distance matrix
|
|
925
970
|
dist_map = cdist(coordinates_A, coordinates_B, metric="euclidean")
|
|
926
971
|
|
|
@@ -931,21 +976,21 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
931
976
|
lblB = labelsB
|
|
932
977
|
|
|
933
978
|
print(f"Distance {d} for contact as border")
|
|
934
|
-
contact_pairs = contact_neighborhood(labelsA[t], labelsB=lblB, border=d, connectivity=2)
|
|
979
|
+
contact_pairs = contact_neighborhood(labelsA[t], labelsB=lblB, border=d, connectivity=2)
|
|
935
980
|
|
|
936
981
|
print(t, f"{np.unique(labelsA[t])=}")
|
|
937
982
|
print(f"Frame {t}: found the following contact pairs: {contact_pairs}...")
|
|
938
983
|
# Put infinite distance to all non-contact pairs (something like this)
|
|
939
|
-
plot_map=False
|
|
984
|
+
plot_map = False
|
|
940
985
|
|
|
941
|
-
if len(contact_pairs)>0:
|
|
986
|
+
if len(contact_pairs) > 0:
|
|
942
987
|
mask = np.ones_like(dist_map).astype(bool)
|
|
943
|
-
|
|
988
|
+
|
|
944
989
|
indices_to_keep = []
|
|
945
990
|
for cp in contact_pairs:
|
|
946
|
-
|
|
947
|
-
if np.any(cp<0):
|
|
948
|
-
if cp[0]<0:
|
|
991
|
+
|
|
992
|
+
if np.any(cp < 0):
|
|
993
|
+
if cp[0] < 0:
|
|
949
994
|
mask_A = cp[1]
|
|
950
995
|
mask_B = np.abs(cp[0])
|
|
951
996
|
else:
|
|
@@ -957,8 +1002,8 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
957
1002
|
|
|
958
1003
|
try:
|
|
959
1004
|
|
|
960
|
-
idx_A = np.where(mask_ids_A==int(mask_A))[0][0]
|
|
961
|
-
idx_B = np.where(mask_ids_B==int(mask_B))[0][0]
|
|
1005
|
+
idx_A = np.where(mask_ids_A == int(mask_A))[0][0]
|
|
1006
|
+
idx_B = np.where(mask_ids_B == int(mask_B))[0][0]
|
|
962
1007
|
print(idx_A, idx_B)
|
|
963
1008
|
indices_to_keep.append([idx_A,idx_B])
|
|
964
1009
|
except Exception as e:
|
|
@@ -966,17 +1011,17 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
966
1011
|
pass
|
|
967
1012
|
|
|
968
1013
|
print(f'Indices to keep: {indices_to_keep}...')
|
|
969
|
-
if len(indices_to_keep)>0:
|
|
1014
|
+
if len(indices_to_keep) > 0:
|
|
970
1015
|
indices_to_keep = np.array(indices_to_keep)
|
|
971
|
-
mask[indices_to_keep[:,0],indices_to_keep[:,1]] = False
|
|
972
|
-
if mode=='self':
|
|
973
|
-
mask[indices_to_keep[:,1],indices_to_keep[:,0]] = False
|
|
1016
|
+
mask[indices_to_keep[:, 0], indices_to_keep[:, 1]] = False
|
|
1017
|
+
if mode == 'self':
|
|
1018
|
+
mask[indices_to_keep[:, 1], indices_to_keep[:, 0]] = False
|
|
974
1019
|
dist_map[mask] = 1.0E06
|
|
975
1020
|
plot_map=True
|
|
976
1021
|
else:
|
|
977
1022
|
dist_map[:,:] = 1.0E06
|
|
978
1023
|
else:
|
|
979
|
-
dist_map[
|
|
1024
|
+
dist_map[:, :] = 1.0E06
|
|
980
1025
|
|
|
981
1026
|
# PROCEED all the same?? --> I guess so
|
|
982
1027
|
# if plot_map:
|
|
@@ -988,62 +1033,63 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
988
1033
|
|
|
989
1034
|
d_filter = 1.0E05
|
|
990
1035
|
if attention_weight:
|
|
991
|
-
weights, closest_A = compute_attention_weight(dist_map, d_filter, status_A, ids_A, axis=1,
|
|
992
|
-
|
|
1036
|
+
weights, closest_A = compute_attention_weight(dist_map, d_filter, status_A, ids_A, axis=1,
|
|
1037
|
+
include_dead_weight=include_dead_weight)
|
|
1038
|
+
|
|
993
1039
|
# Target centric
|
|
994
1040
|
for k in range(dist_map.shape[0]):
|
|
995
|
-
|
|
996
|
-
col = dist_map[k
|
|
997
|
-
col[col==0.] = 1.0E06
|
|
998
|
-
|
|
999
|
-
neighs_B = np.array([ids_B[i] for i in np.where((col<=d_filter))[0]])
|
|
1000
|
-
status_neigh_B = np.array([status_B[i] for i in np.where((col<=d_filter))[0]])
|
|
1001
|
-
dist_B = [round(col[i],2) for i in np.where((col<=d_filter))[0]]
|
|
1002
|
-
if len(dist_B)>0:
|
|
1003
|
-
closest_B_cell = neighs_B[np.argmin(dist_B)]
|
|
1004
|
-
|
|
1041
|
+
|
|
1042
|
+
col = dist_map[k, :]
|
|
1043
|
+
col[col == 0.] = 1.0E06
|
|
1044
|
+
|
|
1045
|
+
neighs_B = np.array([ids_B[i] for i in np.where((col <= d_filter))[0]])
|
|
1046
|
+
status_neigh_B = np.array([status_B[i] for i in np.where((col <= d_filter))[0]])
|
|
1047
|
+
dist_B = [round(col[i], 2) for i in np.where((col <= d_filter))[0]]
|
|
1048
|
+
if len(dist_B) > 0:
|
|
1049
|
+
closest_B_cell = neighs_B[np.argmin(dist_B)]
|
|
1050
|
+
|
|
1005
1051
|
if symmetrize and attention_weight:
|
|
1006
1052
|
n_neighs = float(len(neighs_B))
|
|
1007
1053
|
if not include_dead_weight:
|
|
1008
|
-
n_neighs_alive = len(np.where(status_neigh_B==1)[0])
|
|
1054
|
+
n_neighs_alive = len(np.where(status_neigh_B == 1)[0])
|
|
1009
1055
|
neigh_count = n_neighs_alive
|
|
1010
1056
|
else:
|
|
1011
1057
|
neigh_count = n_neighs
|
|
1012
|
-
if neigh_count>0:
|
|
1013
|
-
weight_A = 1
|
|
1058
|
+
if neigh_count > 0:
|
|
1059
|
+
weight_A = 1. / neigh_count
|
|
1014
1060
|
else:
|
|
1015
1061
|
weight_A = np.nan
|
|
1016
1062
|
|
|
1017
|
-
if not include_dead_weight and status_A[k]==0:
|
|
1063
|
+
if not include_dead_weight and status_A[k] == 0:
|
|
1018
1064
|
weight_A = 0
|
|
1019
|
-
|
|
1065
|
+
|
|
1020
1066
|
neighs = []
|
|
1021
1067
|
setA.at[index_A[k], neigh_col] = []
|
|
1022
1068
|
for n in range(len(neighs_B)):
|
|
1023
|
-
|
|
1069
|
+
|
|
1024
1070
|
# index in setB
|
|
1025
|
-
n_index = np.where(ids_B==neighs_B[n])[0][0]
|
|
1071
|
+
n_index = np.where(ids_B == neighs_B[n])[0][0]
|
|
1026
1072
|
# Assess if neigh B is closest to A
|
|
1027
1073
|
if attention_weight:
|
|
1028
|
-
if closest_A[n_index]==ids_A[k]:
|
|
1074
|
+
if closest_A[n_index] == ids_A[k]:
|
|
1029
1075
|
closest = True
|
|
1030
1076
|
else:
|
|
1031
1077
|
closest = False
|
|
1032
|
-
|
|
1078
|
+
|
|
1033
1079
|
if symmetrize:
|
|
1034
1080
|
# Load neighborhood previous data
|
|
1035
1081
|
sym_neigh = setB.loc[index_B[n_index], neigh_col]
|
|
1036
|
-
if neighs_B[n]==closest_B_cell:
|
|
1037
|
-
closest_b=True
|
|
1082
|
+
if neighs_B[n] == closest_B_cell:
|
|
1083
|
+
closest_b = True
|
|
1038
1084
|
else:
|
|
1039
|
-
closest_b=False
|
|
1085
|
+
closest_b = False
|
|
1040
1086
|
if isinstance(sym_neigh, list):
|
|
1041
1087
|
sym_neigh.append({'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]})
|
|
1042
1088
|
else:
|
|
1043
|
-
sym_neigh = [{'id': ids_A[k], 'distance': dist_B[n],'status': status_A[k]}]
|
|
1089
|
+
sym_neigh = [{'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]}]
|
|
1044
1090
|
if attention_weight:
|
|
1045
1091
|
sym_neigh[-1].update({'weight': weight_A, 'closest': closest_b})
|
|
1046
|
-
|
|
1092
|
+
|
|
1047
1093
|
# Write the minimum info about neighborhing cell B
|
|
1048
1094
|
neigh_dico = {'id': neighs_B[n], 'distance': dist_B[n], 'status': status_neigh_B[n]}
|
|
1049
1095
|
if attention_weight:
|
|
@@ -1051,33 +1097,43 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
1051
1097
|
|
|
1052
1098
|
if compute_cum_sum:
|
|
1053
1099
|
# Compute the integrated presence of the neighboring cell B
|
|
1054
|
-
assert cl[1][
|
|
1055
|
-
|
|
1100
|
+
assert cl[1][
|
|
1101
|
+
'track'] == 'TRACK_ID', 'The set B does not seem to contain tracked data. The cumulative time will be meaningless.'
|
|
1102
|
+
past_neighs = [[ll['id'] for ll in l] if len(l) > 0 else [None] for l in setA.loc[
|
|
1103
|
+
(setA[cl[0]['track']] == ids_A[k]) & (setA[cl[0]['time']] <= t), neigh_col].to_numpy()]
|
|
1056
1104
|
past_neighs = [item for sublist in past_neighs for item in sublist]
|
|
1057
|
-
|
|
1105
|
+
|
|
1058
1106
|
if attention_weight:
|
|
1059
|
-
past_weights = [[ll['weight'] for ll in l] if len(l)>0 else [None] for l in setA.loc[
|
|
1107
|
+
past_weights = [[ll['weight'] for ll in l] if len(l) > 0 else [None] for l in setA.loc[
|
|
1108
|
+
(setA[cl[0]['track']] == ids_A[k]) & (
|
|
1109
|
+
setA[cl[0]['time']] <= t), neigh_col].to_numpy()]
|
|
1060
1110
|
past_weights = [item for sublist in past_weights for item in sublist]
|
|
1061
1111
|
|
|
1062
|
-
cum_sum = len(np.where(past_neighs==neighs_B[n])[0])
|
|
1063
|
-
neigh_dico.update({'cumulated_presence': cum_sum+1})
|
|
1064
|
-
|
|
1112
|
+
cum_sum = len(np.where(past_neighs == neighs_B[n])[0])
|
|
1113
|
+
neigh_dico.update({'cumulated_presence': cum_sum + 1})
|
|
1114
|
+
|
|
1065
1115
|
if attention_weight:
|
|
1066
|
-
cum_sum_weighted = np.sum(
|
|
1116
|
+
cum_sum_weighted = np.sum(
|
|
1117
|
+
[w if l == neighs_B[n] else 0 for l, w in zip(past_neighs, past_weights)])
|
|
1067
1118
|
neigh_dico.update({'cumulated_presence_weighted': cum_sum_weighted + weights[n_index]})
|
|
1068
1119
|
|
|
1069
1120
|
if symmetrize:
|
|
1070
1121
|
setB.at[index_B[n_index], neigh_col] = sym_neigh
|
|
1071
|
-
|
|
1122
|
+
|
|
1072
1123
|
neighs.append(neigh_dico)
|
|
1073
|
-
|
|
1124
|
+
|
|
1074
1125
|
setA.at[index_A[k], neigh_col] = neighs
|
|
1075
|
-
|
|
1126
|
+
|
|
1076
1127
|
return setA, setB
|
|
1077
1128
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1129
|
+
|
|
1130
|
+
def compute_contact_neighborhood_at_position(pos, distance, population=['targets', 'effectors'], theta_dist=None,
|
|
1131
|
+
img_shape=(2048, 2048), return_tables=False, clear_neigh=False,
|
|
1132
|
+
event_time_col=None,
|
|
1133
|
+
neighborhood_kwargs={'mode': 'two-pop', 'status': None,
|
|
1134
|
+
'not_status_option': None,
|
|
1135
|
+
'include_dead_weight': True, "compute_cum_sum": False,
|
|
1136
|
+
"attention_weight": True, 'symmetrize': True}):
|
|
1081
1137
|
"""
|
|
1082
1138
|
Computes neighborhood metrics for specified cell populations within a given position, based on distance criteria and additional parameters.
|
|
1083
1139
|
|
|
@@ -1115,12 +1171,12 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1115
1171
|
------
|
|
1116
1172
|
AssertionError
|
|
1117
1173
|
If the specified position path does not exist or if the number of distances and edge thresholds do not match.
|
|
1118
|
-
|
|
1174
|
+
|
|
1119
1175
|
"""
|
|
1120
1176
|
|
|
1121
|
-
pos = pos.replace('\\','/')
|
|
1177
|
+
pos = pos.replace('\\', '/')
|
|
1122
1178
|
pos = rf"{pos}"
|
|
1123
|
-
assert os.path.exists(pos),f'Position {pos} is not a valid path.'
|
|
1179
|
+
assert os.path.exists(pos), f'Position {pos} is not a valid path.'
|
|
1124
1180
|
|
|
1125
1181
|
if isinstance(population, str):
|
|
1126
1182
|
population = [population, population]
|
|
@@ -1131,12 +1187,12 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1131
1187
|
theta_dist = [theta_dist]
|
|
1132
1188
|
|
|
1133
1189
|
if theta_dist is None:
|
|
1134
|
-
theta_dist = [0 for d in distance]
|
|
1135
|
-
assert len(theta_dist)==len(distance),'Incompatible number of distances and number of edge thresholds.'
|
|
1190
|
+
theta_dist = [0 for d in distance] # 0.9*d
|
|
1191
|
+
assert len(theta_dist) == len(distance), 'Incompatible number of distances and number of edge thresholds.'
|
|
1136
1192
|
|
|
1137
|
-
if population[0]==population[1]:
|
|
1193
|
+
if population[0] == population[1]:
|
|
1138
1194
|
neighborhood_kwargs.update({'mode': 'self'})
|
|
1139
|
-
if population[1]!=population[0]:
|
|
1195
|
+
if population[1] != population[0]:
|
|
1140
1196
|
neighborhood_kwargs.update({'mode': 'two-pop'})
|
|
1141
1197
|
|
|
1142
1198
|
df_A, path_A = get_position_table(pos, population=population[0], return_path=True)
|
|
@@ -1144,6 +1200,15 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1144
1200
|
if df_A is None or df_B is None:
|
|
1145
1201
|
return None
|
|
1146
1202
|
|
|
1203
|
+
if clear_neigh:
|
|
1204
|
+
if os.path.exists(path_A.replace('.csv','.pkl')):
|
|
1205
|
+
os.remove(path_A.replace('.csv','.pkl'))
|
|
1206
|
+
if os.path.exists(path_B.replace('.csv','.pkl')):
|
|
1207
|
+
os.remove(path_B.replace('.csv','.pkl'))
|
|
1208
|
+
df_pair, pair_path = get_position_table(pos, population='pairs', return_path=True)
|
|
1209
|
+
if df_pair is not None:
|
|
1210
|
+
os.remove(pair_path)
|
|
1211
|
+
|
|
1147
1212
|
df_A_pkl = get_position_pickle(pos, population=population[0], return_path=False)
|
|
1148
1213
|
df_B_pkl = get_position_pickle(pos, population=population[1], return_path=False)
|
|
1149
1214
|
|
|
@@ -1152,12 +1217,9 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1152
1217
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
1153
1218
|
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
1154
1219
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
else:
|
|
1159
|
-
cols.append('ID')
|
|
1160
|
-
on_cols = ['ID','FRAME']
|
|
1220
|
+
id_col = extract_identity_col(df_A_pkl)
|
|
1221
|
+
cols.append(id_col)
|
|
1222
|
+
on_cols = [id_col, 'FRAME']
|
|
1161
1223
|
|
|
1162
1224
|
print(f'Recover {cols} from the pickle file...')
|
|
1163
1225
|
try:
|
|
@@ -1171,12 +1233,9 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1171
1233
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
1172
1234
|
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
1173
1235
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
else:
|
|
1178
|
-
cols.append('ID')
|
|
1179
|
-
on_cols = ['ID','FRAME']
|
|
1236
|
+
id_col = extract_identity_col(df_B_pkl)
|
|
1237
|
+
cols.append(id_col)
|
|
1238
|
+
on_cols = [id_col, 'FRAME']
|
|
1180
1239
|
|
|
1181
1240
|
print(f'Recover {cols} from the pickle file...')
|
|
1182
1241
|
try:
|
|
@@ -1185,7 +1244,7 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1185
1244
|
print(f'Failure to merge pickle and csv files: {e}')
|
|
1186
1245
|
|
|
1187
1246
|
labelsA = locate_labels(pos, population=population[0])
|
|
1188
|
-
if population[1]==population[0]:
|
|
1247
|
+
if population[1] == population[0]:
|
|
1189
1248
|
labelsB = None
|
|
1190
1249
|
else:
|
|
1191
1250
|
labelsB = locate_labels(pos, population=population[1])
|
|
@@ -1194,18 +1253,18 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1194
1253
|
unwanted = df_A.columns[df_A.columns.str.contains('neighborhood')]
|
|
1195
1254
|
df_A = df_A.drop(columns=unwanted)
|
|
1196
1255
|
unwanted = df_B.columns[df_B.columns.str.contains('neighborhood')]
|
|
1197
|
-
df_B = df_B.drop(columns=unwanted)
|
|
1256
|
+
df_B = df_B.drop(columns=unwanted)
|
|
1198
1257
|
|
|
1199
1258
|
print(f"Distance: {distance} for mask contact")
|
|
1200
|
-
df_A, df_B = mask_contact_neighborhood(df_A, df_B, labelsA, labelsB, distance
|
|
1201
|
-
if df_A is None or df_B is None:
|
|
1259
|
+
df_A, df_B = mask_contact_neighborhood(df_A, df_B, labelsA, labelsB, distance, **neighborhood_kwargs)
|
|
1260
|
+
if df_A is None or df_B is None or len(df_A)==0:
|
|
1202
1261
|
return None
|
|
1203
1262
|
|
|
1204
|
-
for td,d in zip(theta_dist, distance):
|
|
1263
|
+
for td, d in zip(theta_dist, distance):
|
|
1205
1264
|
|
|
1206
|
-
if neighborhood_kwargs['mode']=='two-pop':
|
|
1265
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
1207
1266
|
neigh_col = f'neighborhood_2_contact_{d}_px'
|
|
1208
|
-
elif neighborhood_kwargs['mode']=='self':
|
|
1267
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
1209
1268
|
neigh_col = f'neighborhood_self_contact_{d}_px'
|
|
1210
1269
|
|
|
1211
1270
|
df_A.loc[df_A['class_id'].isnull(),neigh_col] = np.nan
|
|
@@ -1215,26 +1274,34 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1215
1274
|
# df_A.loc[~edge_filter_A, neigh_col] = np.nan
|
|
1216
1275
|
# df_B.loc[~edge_filter_B, neigh_col] = np.nan
|
|
1217
1276
|
|
|
1218
|
-
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive','intermediate'],
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1277
|
+
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive', 'intermediate'],
|
|
1278
|
+
decompose_by_status=True)
|
|
1279
|
+
if 'TRACK_ID' in list(df_A.columns):
|
|
1280
|
+
if not np.all(df_A['TRACK_ID'].isnull()):
|
|
1281
|
+
df_A = mean_neighborhood_before_event(df_A, neigh_col, event_time_col, metrics=['inclusive','intermediate'])
|
|
1282
|
+
if event_time_col is not None:
|
|
1283
|
+
df_A = mean_neighborhood_after_event(df_A, neigh_col, event_time_col, metrics=['inclusive', 'intermediate'])
|
|
1284
|
+
print('Done...')
|
|
1285
|
+
|
|
1286
|
+
df_A.to_pickle(path_A.replace('.csv', '.pkl'))
|
|
1287
|
+
if not population[0] == population[1]:
|
|
1288
|
+
# Remove neighborhood column
|
|
1289
|
+
for td, d in zip(theta_dist, distance):
|
|
1290
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
1291
|
+
neigh_col = f'neighborhood_2_contact_{d}_px'
|
|
1292
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
1293
|
+
neigh_col = f'neighborhood_self_contact_{d}_px'
|
|
1294
|
+
df_B = df_B.drop(columns=[neigh_col])
|
|
1295
|
+
df_B.to_pickle(path_B.replace('.csv', '.pkl'))
|
|
1229
1296
|
|
|
1230
1297
|
unwanted = df_A.columns[df_A.columns.str.startswith('neighborhood_')]
|
|
1231
1298
|
df_A2 = df_A.drop(columns=unwanted)
|
|
1232
1299
|
df_A2.to_csv(path_A, index=False)
|
|
1233
1300
|
|
|
1234
|
-
if not population[0]==population[1]:
|
|
1301
|
+
if not population[0] == population[1]:
|
|
1235
1302
|
unwanted = df_B.columns[df_B.columns.str.startswith('neighborhood_')]
|
|
1236
1303
|
df_B_csv = df_B.drop(unwanted, axis=1, inplace=False)
|
|
1237
|
-
df_B_csv.to_csv(path_B,index=False)
|
|
1304
|
+
df_B_csv.to_csv(path_B, index=False)
|
|
1238
1305
|
|
|
1239
1306
|
if return_tables:
|
|
1240
1307
|
return df_A, df_B
|
|
@@ -1249,10 +1316,15 @@ if __name__ == "__main__":
|
|
|
1249
1316
|
|
|
1250
1317
|
print('None')
|
|
1251
1318
|
pos = "/home/torro/Documents/Experiments/NKratio_Exp/W5/500"
|
|
1252
|
-
|
|
1253
|
-
test,_ = compute_neighborhood_at_position(pos, [62], population=['targets','effectors'], theta_dist=None,
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1319
|
+
|
|
1320
|
+
test, _ = compute_neighborhood_at_position(pos, [62], population=['targets', 'effectors'], theta_dist=None,
|
|
1321
|
+
img_shape=(2048, 2048), return_tables=True, clear_neigh=True,
|
|
1322
|
+
neighborhood_kwargs={'mode': 'two-pop', 'status': ['class', None],
|
|
1323
|
+
'not_status_option': [True, False],
|
|
1324
|
+
'include_dead_weight': True,
|
|
1325
|
+
"compute_cum_sum": False, "attention_weight": True,
|
|
1326
|
+
'symmetrize': False})
|
|
1327
|
+
|
|
1328
|
+
# test = compute_neighborhood_metrics(test, 'neighborhood_self_circle_150_px', metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
1257
1329
|
print(test.columns)
|
|
1258
1330
|
#print(segment(None,'test'))
|