celldetective 1.1.1.post4__py3-none-any.whl → 1.2.1__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 +15 -9
- celldetective/gui/control_panel.py +50 -6
- celldetective/gui/layouts.py +5 -4
- celldetective/gui/neighborhood_options.py +13 -9
- 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 +29 -9
- 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 +222 -60
- celldetective/io.py +536 -420
- celldetective/measure.py +919 -969
- celldetective/models/pair_signal_detection/blank +0 -0
- celldetective/models/segmentation_effectors/ricm-bimodal/config_input.json +130 -0
- celldetective/models/segmentation_effectors/ricm-bimodal/ricm-bimodal +0 -0
- celldetective/models/segmentation_effectors/ricm-bimodal/training_instructions.json +37 -0
- celldetective/neighborhood.py +428 -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 +405 -8
- celldetective/tracking.py +8 -2
- celldetective/utils.py +178 -17
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.1.dist-info}/METADATA +8 -8
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.1.dist-info}/RECORD +41 -34
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.1.dist-info}/WHEEL +1 -1
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.1.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.post4.dist-info → celldetective-1.2.1.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,16 +377,28 @@ 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)
|
|
390
|
+
if df_A is None or df_B is None:
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
if clear_neigh:
|
|
394
|
+
if os.path.exists(path_A.replace('.csv','.pkl')):
|
|
395
|
+
os.remove(path_A.replace('.csv','.pkl'))
|
|
396
|
+
if os.path.exists(path_B.replace('.csv','.pkl')):
|
|
397
|
+
os.remove(path_B.replace('.csv','.pkl'))
|
|
398
|
+
df_pair, pair_path = get_position_table(pos, population='pairs', return_path=True)
|
|
399
|
+
if df_pair is not None:
|
|
400
|
+
os.remove(pair_path)
|
|
401
|
+
|
|
376
402
|
|
|
377
403
|
df_A_pkl = get_position_pickle(pos, population=population[0], return_path=False)
|
|
378
404
|
df_B_pkl = get_position_pickle(pos, population=population[1], return_path=False)
|
|
@@ -382,12 +408,9 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
382
408
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
383
409
|
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
384
410
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
else:
|
|
389
|
-
cols.append('ID')
|
|
390
|
-
on_cols = ['ID','FRAME']
|
|
411
|
+
id_col = extract_identity_col(df_A_pkl)
|
|
412
|
+
cols.append(id_col)
|
|
413
|
+
on_cols = [id_col, 'FRAME']
|
|
391
414
|
|
|
392
415
|
print(f'Recover {cols} from the pickle file...')
|
|
393
416
|
try:
|
|
@@ -401,12 +424,9 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
401
424
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
402
425
|
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
403
426
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
else:
|
|
408
|
-
cols.append('ID')
|
|
409
|
-
on_cols = ['ID','FRAME']
|
|
427
|
+
id_col = extract_identity_col(df_B_pkl)
|
|
428
|
+
cols.append(id_col)
|
|
429
|
+
on_cols = [id_col, 'FRAME']
|
|
410
430
|
|
|
411
431
|
print(f'Recover {cols} from the pickle file...')
|
|
412
432
|
try:
|
|
@@ -418,17 +438,17 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
418
438
|
unwanted = df_A.columns[df_A.columns.str.contains('neighborhood')]
|
|
419
439
|
df_A = df_A.drop(columns=unwanted)
|
|
420
440
|
unwanted = df_B.columns[df_B.columns.str.contains('neighborhood')]
|
|
421
|
-
df_B = df_B.drop(columns=unwanted)
|
|
441
|
+
df_B = df_B.drop(columns=unwanted)
|
|
422
442
|
|
|
423
|
-
df_A, df_B = distance_cut_neighborhood(df_A,df_B, distance
|
|
424
|
-
if df_A is None or df_B is None:
|
|
443
|
+
df_A, df_B = distance_cut_neighborhood(df_A, df_B, distance, **neighborhood_kwargs)
|
|
444
|
+
if df_A is None or df_B is None or len(df_A)==0:
|
|
425
445
|
return None
|
|
426
446
|
|
|
427
|
-
for td,d in zip(theta_dist, distance):
|
|
447
|
+
for td, d in zip(theta_dist, distance):
|
|
428
448
|
|
|
429
|
-
if neighborhood_kwargs['mode']=='two-pop':
|
|
449
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
430
450
|
neigh_col = f'neighborhood_2_circle_{d}_px'
|
|
431
|
-
elif neighborhood_kwargs['mode']=='self':
|
|
451
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
432
452
|
neigh_col = f'neighborhood_self_circle_{d}_px'
|
|
433
453
|
|
|
434
454
|
# 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 +458,37 @@ def compute_neighborhood_at_position(pos, distance, population=['targets','effec
|
|
|
438
458
|
|
|
439
459
|
print('Count neighborhood...')
|
|
440
460
|
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
441
|
-
if neighborhood_kwargs['symmetrize']:
|
|
442
|
-
|
|
461
|
+
# if neighborhood_kwargs['symmetrize']:
|
|
462
|
+
# df_B = compute_neighborhood_metrics(df_B, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
443
463
|
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
464
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
465
|
+
if 'TRACK_ID' in list(df_A.columns):
|
|
466
|
+
if not np.all(df_A['TRACK_ID'].isnull()):
|
|
467
|
+
print('Estimate average neighborhood before/after event...')
|
|
468
|
+
df_A = mean_neighborhood_before_event(df_A, neigh_col, event_time_col)
|
|
469
|
+
if event_time_col is not None:
|
|
470
|
+
df_A = mean_neighborhood_after_event(df_A, neigh_col, event_time_col)
|
|
471
|
+
print('Done...')
|
|
472
|
+
|
|
473
|
+
df_A.to_pickle(path_A.replace('.csv', '.pkl'))
|
|
474
|
+
if not population[0] == population[1]:
|
|
475
|
+
# Remove neighborhood column
|
|
476
|
+
for td, d in zip(theta_dist, distance):
|
|
477
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
478
|
+
neigh_col = f'neighborhood_2_circle_{d}_px'
|
|
479
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
480
|
+
neigh_col = f'neighborhood_self_circle_{d}_px'
|
|
481
|
+
df_B = df_B.drop(columns=[neigh_col])
|
|
482
|
+
df_B.to_pickle(path_B.replace('.csv', '.pkl'))
|
|
454
483
|
|
|
455
484
|
unwanted = df_A.columns[df_A.columns.str.startswith('neighborhood_')]
|
|
456
485
|
df_A2 = df_A.drop(columns=unwanted)
|
|
457
486
|
df_A2.to_csv(path_A, index=False)
|
|
458
487
|
|
|
459
|
-
if not population[0]==population[1]:
|
|
488
|
+
if not population[0] == population[1]:
|
|
460
489
|
unwanted = df_B.columns[df_B.columns.str.startswith('neighborhood_')]
|
|
461
490
|
df_B_csv = df_B.drop(unwanted, axis=1, inplace=False)
|
|
462
|
-
df_B_csv.to_csv(path_B,index=False)
|
|
491
|
+
df_B_csv.to_csv(path_B, index=False)
|
|
463
492
|
|
|
464
493
|
if return_tables:
|
|
465
494
|
return df_A, df_B
|
|
@@ -508,7 +537,7 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
508
537
|
>>> neigh_col = 'neighborhood_info'
|
|
509
538
|
>>> updated_neigh_table = compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive'], decompose_by_status=True)
|
|
510
539
|
# Computes the inclusive count of neighbors for each cell, decomposed by cell status.
|
|
511
|
-
|
|
540
|
+
|
|
512
541
|
"""
|
|
513
542
|
|
|
514
543
|
neigh_table = neigh_table.reset_index(drop=True)
|
|
@@ -516,14 +545,13 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
516
545
|
groupbycols = ['position']
|
|
517
546
|
else:
|
|
518
547
|
groupbycols = []
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
groupbycols.append('ID')
|
|
548
|
+
|
|
549
|
+
id_col = extract_identity_col(neigh_table)
|
|
550
|
+
groupbycols.append(id_col)
|
|
523
551
|
|
|
524
552
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
525
553
|
|
|
526
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
554
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
527
555
|
group = group.dropna(subset=neigh_col)
|
|
528
556
|
indices = list(group.index)
|
|
529
557
|
neighbors = group[neigh_col].to_numpy()
|
|
@@ -569,49 +597,52 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
569
597
|
if 'intermediate' in metrics:
|
|
570
598
|
n_intermediate[t] = np.sum(weights_at_t)
|
|
571
599
|
if 'exclusive' in metrics:
|
|
572
|
-
n_exclusive[t] = sum([c==1.0 for c in closest_at_t])
|
|
600
|
+
n_exclusive[t] = sum([c == 1.0 for c in closest_at_t])
|
|
573
601
|
|
|
574
602
|
if decompose_by_status:
|
|
575
603
|
|
|
576
604
|
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])
|
|
605
|
+
n_inclusive_status_0[t] = sum([s == 0.0 for s in status_at_t])
|
|
606
|
+
n_inclusive_status_1[t] = sum([s == 1.0 for s in status_at_t])
|
|
579
607
|
|
|
580
608
|
if 'intermediate' in metrics:
|
|
581
609
|
weights_at_t = np.array(weights_at_t)
|
|
582
|
-
|
|
610
|
+
|
|
583
611
|
# 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)]
|
|
612
|
+
weights_status_1 = weights_at_t[np.array([s == 1.0 for s in status_at_t], dtype=bool)]
|
|
613
|
+
weights_status_0 = weights_at_t[np.array([s == 0.0 for s in status_at_t], dtype=bool)]
|
|
586
614
|
n_intermediate_status_1[t] = np.sum(weights_status_1)
|
|
587
615
|
n_intermediate_status_0[t] = np.sum(weights_status_0)
|
|
588
616
|
|
|
589
617
|
if 'exclusive' in metrics:
|
|
590
|
-
n_exclusive_status_0[t] = sum(
|
|
591
|
-
|
|
618
|
+
n_exclusive_status_0[t] = sum(
|
|
619
|
+
[c == 1.0 if s == 0.0 else False for c, s in zip(closest_at_t, status_at_t)])
|
|
620
|
+
n_exclusive_status_1[t] = sum(
|
|
621
|
+
[c == 1.0 if s == 1.0 else False for c, s in zip(closest_at_t, status_at_t)])
|
|
592
622
|
|
|
593
623
|
if 'inclusive' in metrics:
|
|
594
|
-
neigh_table.loc[indices, 'inclusive_count_'+neigh_col] = n_inclusive
|
|
624
|
+
neigh_table.loc[indices, 'inclusive_count_' + neigh_col] = n_inclusive
|
|
595
625
|
if 'intermediate' in metrics:
|
|
596
|
-
neigh_table.loc[indices, 'intermediate_count_'+neigh_col] = n_intermediate
|
|
626
|
+
neigh_table.loc[indices, 'intermediate_count_' + neigh_col] = n_intermediate
|
|
597
627
|
if 'exclusive' in metrics:
|
|
598
|
-
neigh_table.loc[indices, 'exclusive_count_'+neigh_col] = n_exclusive
|
|
628
|
+
neigh_table.loc[indices, 'exclusive_count_' + neigh_col] = n_exclusive
|
|
599
629
|
|
|
600
630
|
if decompose_by_status:
|
|
601
631
|
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
|
|
632
|
+
neigh_table.loc[indices, 'inclusive_count_s0_' + neigh_col] = n_inclusive_status_0
|
|
633
|
+
neigh_table.loc[indices, 'inclusive_count_s1_' + neigh_col] = n_inclusive_status_1
|
|
604
634
|
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
|
|
635
|
+
neigh_table.loc[indices, 'intermediate_count_s0_' + neigh_col] = n_intermediate_status_0
|
|
636
|
+
neigh_table.loc[indices, 'intermediate_count_s1_' + neigh_col] = n_intermediate_status_1
|
|
637
|
+
if 'exclusive' in metrics:
|
|
638
|
+
neigh_table.loc[indices, 'exclusive_count_s0_' + neigh_col] = n_exclusive_status_0
|
|
639
|
+
neigh_table.loc[indices, 'exclusive_count_s1_' + neigh_col] = n_exclusive_status_1
|
|
610
640
|
|
|
611
641
|
return neigh_table
|
|
612
642
|
|
|
613
|
-
|
|
614
|
-
|
|
643
|
+
|
|
644
|
+
def mean_neighborhood_before_event(neigh_table, neigh_col, event_time_col,
|
|
645
|
+
metrics=['inclusive', 'exclusive', 'intermediate']):
|
|
615
646
|
"""
|
|
616
647
|
Computes the mean neighborhood metrics for each cell track before a specified event time.
|
|
617
648
|
|
|
@@ -645,53 +676,59 @@ def mean_neighborhood_before_event(neigh_table, neigh_col, event_time_col, metri
|
|
|
645
676
|
groupbycols = ['position']
|
|
646
677
|
else:
|
|
647
678
|
groupbycols = []
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
groupbycols.append('ID')
|
|
679
|
+
|
|
680
|
+
id_col = extract_identity_col(neigh_table)
|
|
681
|
+
groupbycols.append(id_col)
|
|
652
682
|
|
|
653
683
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
654
684
|
suffix = '_before_event'
|
|
655
|
-
|
|
685
|
+
|
|
656
686
|
if event_time_col is None:
|
|
657
687
|
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()
|
|
688
|
+
neigh_table.loc[:, 'event_time_temp'] = neigh_table['FRAME'].max()
|
|
659
689
|
event_time_col = 'event_time_temp'
|
|
660
690
|
suffix = ''
|
|
661
|
-
|
|
662
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
691
|
+
|
|
692
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
663
693
|
|
|
664
694
|
group = group.dropna(subset=neigh_col)
|
|
665
695
|
indices = list(group.index)
|
|
666
696
|
|
|
667
697
|
event_time_values = group[event_time_col].to_numpy()
|
|
668
|
-
if len(event_time_values)>0:
|
|
698
|
+
if len(event_time_values) > 0:
|
|
669
699
|
event_time = event_time_values[0]
|
|
670
700
|
else:
|
|
671
701
|
continue
|
|
672
702
|
|
|
673
|
-
if event_time<0.:
|
|
703
|
+
if event_time < 0.:
|
|
674
704
|
event_time = group['FRAME'].max()
|
|
675
705
|
|
|
676
706
|
if 'intermediate' in metrics:
|
|
677
|
-
valid_counts_intermediate = group.loc[
|
|
678
|
-
|
|
679
|
-
|
|
707
|
+
valid_counts_intermediate = group.loc[
|
|
708
|
+
group['FRAME'] <= event_time, 'intermediate_count_s1_' + neigh_col].to_numpy()
|
|
709
|
+
if len(valid_counts_intermediate[valid_counts_intermediate == valid_counts_intermediate]) > 0:
|
|
710
|
+
neigh_table.loc[indices, f'mean_count_intermediate_{neigh_col}{suffix}'] = np.nanmean(
|
|
711
|
+
valid_counts_intermediate)
|
|
680
712
|
if 'inclusive' in metrics:
|
|
681
|
-
valid_counts_inclusive = group.loc[
|
|
682
|
-
|
|
683
|
-
|
|
713
|
+
valid_counts_inclusive = group.loc[
|
|
714
|
+
group['FRAME'] <= event_time, 'inclusive_count_s1_' + neigh_col].to_numpy()
|
|
715
|
+
if len(valid_counts_inclusive[valid_counts_inclusive == valid_counts_inclusive]) > 0:
|
|
716
|
+
neigh_table.loc[indices, f'mean_count_inclusive_{neigh_col}{suffix}'] = np.nanmean(
|
|
717
|
+
valid_counts_inclusive)
|
|
684
718
|
if 'exclusive' in metrics:
|
|
685
|
-
valid_counts_exclusive = group.loc[
|
|
686
|
-
|
|
687
|
-
|
|
719
|
+
valid_counts_exclusive = group.loc[
|
|
720
|
+
group['FRAME'] <= event_time, 'exclusive_count_s1_' + neigh_col].to_numpy()
|
|
721
|
+
if len(valid_counts_exclusive[valid_counts_exclusive == valid_counts_exclusive]) > 0:
|
|
722
|
+
neigh_table.loc[indices, f'mean_count_exclusive_{neigh_col}{suffix}'] = np.nanmean(
|
|
723
|
+
valid_counts_exclusive)
|
|
688
724
|
|
|
689
|
-
if event_time_col=='event_time_temp':
|
|
725
|
+
if event_time_col == 'event_time_temp':
|
|
690
726
|
neigh_table = neigh_table.drop(columns='event_time_temp')
|
|
691
727
|
return neigh_table
|
|
692
728
|
|
|
693
|
-
|
|
694
|
-
|
|
729
|
+
|
|
730
|
+
def mean_neighborhood_after_event(neigh_table, neigh_col, event_time_col,
|
|
731
|
+
metrics=['inclusive', 'exclusive', 'intermediate']):
|
|
695
732
|
"""
|
|
696
733
|
Computes the mean neighborhood metrics for each cell track after a specified event time.
|
|
697
734
|
|
|
@@ -725,55 +762,62 @@ def mean_neighborhood_after_event(neigh_table, neigh_col, event_time_col, metric
|
|
|
725
762
|
groupbycols = ['position']
|
|
726
763
|
else:
|
|
727
764
|
groupbycols = []
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
groupbycols.append('ID')
|
|
765
|
+
|
|
766
|
+
id_col = extract_identity_col(neigh_table)
|
|
767
|
+
groupbycols.append(id_col)
|
|
732
768
|
|
|
733
769
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
734
770
|
suffix = '_after_event'
|
|
735
|
-
|
|
771
|
+
|
|
736
772
|
if event_time_col is None:
|
|
737
|
-
neigh_table.loc[:,'event_time_temp'] = None
|
|
773
|
+
neigh_table.loc[:, 'event_time_temp'] = None # neigh_table['FRAME'].max()
|
|
738
774
|
event_time_col = 'event_time_temp'
|
|
739
775
|
suffix = ''
|
|
740
|
-
|
|
741
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
776
|
+
|
|
777
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
742
778
|
|
|
743
779
|
group = group.dropna(subset=neigh_col)
|
|
744
780
|
indices = list(group.index)
|
|
745
781
|
|
|
746
782
|
event_time_values = group[event_time_col].to_numpy()
|
|
747
|
-
if len(event_time_values)>0:
|
|
783
|
+
if len(event_time_values) > 0:
|
|
748
784
|
event_time = event_time_values[0]
|
|
749
785
|
else:
|
|
750
786
|
continue
|
|
751
787
|
|
|
752
|
-
if event_time is not None and (event_time>=0.):
|
|
788
|
+
if event_time is not None and (event_time >= 0.):
|
|
753
789
|
|
|
754
790
|
if 'intermediate' in metrics:
|
|
755
|
-
valid_counts_intermediate = group.loc[
|
|
756
|
-
|
|
757
|
-
|
|
791
|
+
valid_counts_intermediate = group.loc[
|
|
792
|
+
group['FRAME'] > event_time, 'intermediate_count_s1_' + neigh_col].to_numpy()
|
|
793
|
+
if len(valid_counts_intermediate[valid_counts_intermediate == valid_counts_intermediate]) > 0:
|
|
794
|
+
neigh_table.loc[indices, f'mean_count_intermediate_{neigh_col}{suffix}'] = np.nanmean(
|
|
795
|
+
valid_counts_intermediate)
|
|
758
796
|
if 'inclusive' in metrics:
|
|
759
|
-
valid_counts_inclusive = group.loc[
|
|
760
|
-
|
|
761
|
-
|
|
797
|
+
valid_counts_inclusive = group.loc[
|
|
798
|
+
group['FRAME'] > event_time, 'inclusive_count_s1_' + neigh_col].to_numpy()
|
|
799
|
+
if len(valid_counts_inclusive[valid_counts_inclusive == valid_counts_inclusive]) > 0:
|
|
800
|
+
neigh_table.loc[indices, f'mean_count_inclusive_{neigh_col}{suffix}'] = np.nanmean(
|
|
801
|
+
valid_counts_inclusive)
|
|
762
802
|
if 'exclusive' in metrics:
|
|
763
|
-
valid_counts_exclusive = group.loc[
|
|
764
|
-
|
|
765
|
-
|
|
803
|
+
valid_counts_exclusive = group.loc[
|
|
804
|
+
group['FRAME'] > event_time, 'exclusive_count_s1_' + neigh_col].to_numpy()
|
|
805
|
+
if len(valid_counts_exclusive[valid_counts_exclusive == valid_counts_exclusive]) > 0:
|
|
806
|
+
neigh_table.loc[indices, f'mean_count_exclusive_{neigh_col}{suffix}'] = np.nanmean(
|
|
807
|
+
valid_counts_exclusive)
|
|
766
808
|
|
|
767
|
-
if event_time_col=='event_time_temp':
|
|
809
|
+
if event_time_col == 'event_time_temp':
|
|
768
810
|
neigh_table = neigh_table.drop(columns='event_time_temp')
|
|
769
811
|
|
|
770
812
|
return neigh_table
|
|
771
813
|
|
|
814
|
+
|
|
772
815
|
# New functions for direct cell-cell contact neighborhood
|
|
773
816
|
|
|
774
817
|
def sign(num):
|
|
775
818
|
return -1 if num < 0 else 1
|
|
776
819
|
|
|
820
|
+
|
|
777
821
|
def contact_neighborhood(labelsA, labelsB=None, border=3, connectivity=2):
|
|
778
822
|
|
|
779
823
|
labelsA = labelsA.astype(float)
|
|
@@ -781,50 +825,50 @@ def contact_neighborhood(labelsA, labelsB=None, border=3, connectivity=2):
|
|
|
781
825
|
labelsB = labelsB.astype(float)
|
|
782
826
|
|
|
783
827
|
print(f"Border = {border}")
|
|
784
|
-
|
|
828
|
+
|
|
785
829
|
if border > 0:
|
|
786
830
|
print(labelsA.shape, border * (-1))
|
|
787
831
|
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)]
|
|
832
|
+
labelsA[np.where(labelsA_edge > 0)] = labelsA_edge[np.where(labelsA_edge > 0)]
|
|
789
833
|
if labelsB is not None:
|
|
790
834
|
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
|
-
|
|
835
|
+
labelsB[np.where(labelsB_edge > 0)] = labelsB_edge[np.where(labelsB_edge > 0)]
|
|
836
|
+
|
|
793
837
|
if labelsB is not None:
|
|
794
|
-
labelsA[labelsA!=0] = -labelsA[labelsA!=0]
|
|
838
|
+
labelsA[labelsA != 0] = -labelsA[labelsA != 0]
|
|
795
839
|
labelsAB = merge_labels(labelsA, labelsB)
|
|
796
840
|
labelsBA = merge_labels(labelsB, labelsA)
|
|
797
841
|
label_cases = [labelsAB, labelsBA]
|
|
798
842
|
else:
|
|
799
843
|
label_cases = [labelsA]
|
|
800
|
-
|
|
844
|
+
|
|
801
845
|
coocurrences = []
|
|
802
846
|
for lbl in label_cases:
|
|
803
847
|
coocurrences.extend(find_contact_neighbors(lbl, connectivity=connectivity))
|
|
804
|
-
|
|
805
|
-
unique_pairs = np.unique(coocurrences,axis=0)
|
|
806
|
-
|
|
848
|
+
|
|
849
|
+
unique_pairs = np.unique(coocurrences, axis=0)
|
|
850
|
+
|
|
807
851
|
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])],
|
|
852
|
+
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0] != p[1] and sign(p[0]) != sign(p[1])],
|
|
853
|
+
axis=0)
|
|
809
854
|
else:
|
|
810
|
-
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0]!=p[1]],axis=0)
|
|
811
|
-
|
|
855
|
+
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0] != p[1]], axis=0)
|
|
856
|
+
|
|
812
857
|
return neighs
|
|
813
858
|
|
|
814
859
|
def merge_labels(labelsA, labelsB):
|
|
815
|
-
|
|
816
860
|
labelsA = labelsA.astype(float)
|
|
817
861
|
labelsB = labelsB.astype(float)
|
|
818
|
-
|
|
862
|
+
|
|
819
863
|
labelsAB = labelsA.copy()
|
|
820
|
-
labelsAB[np.where(labelsB!=0)] = labelsB[np.where(labelsB!=0)]
|
|
821
|
-
|
|
864
|
+
labelsAB[np.where(labelsB != 0)] = labelsB[np.where(labelsB != 0)]
|
|
865
|
+
|
|
822
866
|
return labelsAB
|
|
823
867
|
|
|
868
|
+
|
|
824
869
|
def find_contact_neighbors(labels, connectivity=2):
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
g, nodes = pixel_graph(labels, mask=labels.astype(bool),connectivity=connectivity)
|
|
870
|
+
assert labels.ndim == 2, "Wrong dimension for labels..."
|
|
871
|
+
g, nodes = pixel_graph(labels, mask=labels.astype(bool), connectivity=connectivity)
|
|
828
872
|
g.eliminate_zeros()
|
|
829
873
|
|
|
830
874
|
coo = g.tocoo()
|
|
@@ -833,18 +877,19 @@ def find_contact_neighbors(labels, connectivity=2):
|
|
|
833
877
|
|
|
834
878
|
center_values = labels.ravel()[center_coords]
|
|
835
879
|
neighbor_values = labels.ravel()[neighbor_coords]
|
|
836
|
-
touching_masks = np.column_stack((center_values, neighbor_values))
|
|
837
|
-
|
|
880
|
+
touching_masks = np.column_stack((center_values, neighbor_values))
|
|
881
|
+
|
|
838
882
|
return touching_masks
|
|
839
883
|
|
|
840
884
|
|
|
841
|
-
def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-pop', status=None,
|
|
885
|
+
def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-pop', status=None,
|
|
886
|
+
not_status_option=None, compute_cum_sum=True,
|
|
842
887
|
attention_weight=True, symmetrize=True, include_dead_weight=True,
|
|
843
|
-
column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y',
|
|
844
|
-
|
|
888
|
+
column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y',
|
|
889
|
+
'mask_id': 'class_id'}):
|
|
845
890
|
"""
|
|
846
891
|
|
|
847
|
-
Match neighbors in set A and B within a circle of radius d.
|
|
892
|
+
Match neighbors in set A and B within a circle of radius d.
|
|
848
893
|
|
|
849
894
|
Parameters
|
|
850
895
|
----------
|
|
@@ -854,7 +899,7 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
854
899
|
Cut-distance in pixels to match neighboring pairs.
|
|
855
900
|
mode: str
|
|
856
901
|
neighboring mode, between 'two-pop' (e.g. target-effector) and 'self' (target-target or effector-effector).
|
|
857
|
-
status: None or status
|
|
902
|
+
status: None or status
|
|
858
903
|
name to look for cells to ignore (because they are dead). By default all cells are kept.
|
|
859
904
|
compute_cum_sum: bool,
|
|
860
905
|
compute cumulated time of presence of neighbours (only if trajectories available for both sets)
|
|
@@ -870,57 +915,59 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
870
915
|
if setA is not None and setB is not None:
|
|
871
916
|
setA, setB, status = set_live_status(setA, setB, status, not_status_option)
|
|
872
917
|
else:
|
|
873
|
-
return None,None
|
|
918
|
+
return None, None
|
|
874
919
|
|
|
875
|
-
# Check distance option
|
|
920
|
+
# Check distance option
|
|
876
921
|
if not isinstance(distance, list):
|
|
877
922
|
distance = [distance]
|
|
878
|
-
|
|
923
|
+
|
|
879
924
|
for d in distance:
|
|
880
925
|
# loop over each provided distance
|
|
881
|
-
|
|
882
|
-
if mode=='two-pop':
|
|
926
|
+
|
|
927
|
+
if mode == 'two-pop':
|
|
883
928
|
neigh_col = f'neighborhood_2_contact_{d}_px'
|
|
884
|
-
elif mode=='self':
|
|
929
|
+
elif mode == 'self':
|
|
885
930
|
neigh_col = f'neighborhood_self_contact_{d}_px'
|
|
886
|
-
|
|
931
|
+
|
|
887
932
|
cl = []
|
|
888
|
-
for s in [setA,setB]:
|
|
933
|
+
for s in [setA, setB]:
|
|
889
934
|
|
|
890
935
|
# Check whether data can be tracked
|
|
891
936
|
temp_column_labels = column_labels.copy()
|
|
892
937
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
938
|
+
id_col = extract_identity_col(s)
|
|
939
|
+
temp_column_labels.update({'track': id_col})
|
|
940
|
+
if id_col=='ID':
|
|
941
|
+
compute_cum_sum = False # if no tracking data then cum_sum is not relevant
|
|
896
942
|
cl.append(temp_column_labels)
|
|
897
943
|
|
|
898
944
|
# Remove nan tracks (cells that do not belong to a track)
|
|
899
945
|
s[neigh_col] = np.nan
|
|
900
946
|
s[neigh_col] = s[neigh_col].astype(object)
|
|
901
|
-
s.dropna(subset=[cl[-1]['track']],inplace=True)
|
|
947
|
+
s.dropna(subset=[cl[-1]['track']], inplace=True)
|
|
902
948
|
|
|
903
949
|
# Loop over each available timestep
|
|
904
|
-
timeline = np.unique(np.concatenate([setA[cl[0]['time']].to_numpy(), setB[cl[1]['time']].to_numpy()])).astype(
|
|
950
|
+
timeline = np.unique(np.concatenate([setA[cl[0]['time']].to_numpy(), setB[cl[1]['time']].to_numpy()])).astype(
|
|
951
|
+
int)
|
|
905
952
|
for t in tqdm(timeline):
|
|
906
953
|
|
|
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
|
-
|
|
954
|
+
index_A = list(setA.loc[setA[cl[0]['time']] == t].index)
|
|
955
|
+
coordinates_A = setA.loc[setA[cl[0]['time']] == t, [cl[0]['x'], cl[0]['y']]].to_numpy()
|
|
956
|
+
ids_A = setA.loc[setA[cl[0]['time']] == t, cl[0]['track']].to_numpy()
|
|
957
|
+
mask_ids_A = setA.loc[setA[cl[0]['time']] == t, cl[0]['mask_id']].to_numpy()
|
|
958
|
+
status_A = setA.loc[setA[cl[0]['time']] == t, status[0]].to_numpy()
|
|
959
|
+
|
|
960
|
+
index_B = list(setB.loc[setB[cl[1]['time']] == t].index)
|
|
961
|
+
coordinates_B = setB.loc[setB[cl[1]['time']] == t, [cl[1]['x'], cl[1]['y']]].to_numpy()
|
|
962
|
+
ids_B = setB.loc[setB[cl[1]['time']] == t, cl[1]['track']].to_numpy()
|
|
963
|
+
mask_ids_B = setB.loc[setB[cl[1]['time']] == t, cl[1]['mask_id']].to_numpy()
|
|
964
|
+
status_B = setB.loc[setB[cl[1]['time']] == t, status[1]].to_numpy()
|
|
965
|
+
|
|
919
966
|
print(f"Frame {t}")
|
|
920
|
-
print(f"{mask_ids_A=}",f"{mask_ids_B}")
|
|
967
|
+
print(f"{mask_ids_A=}", f"{mask_ids_B}")
|
|
921
968
|
|
|
922
969
|
if len(ids_A) > 0 and len(ids_B) > 0:
|
|
923
|
-
|
|
970
|
+
|
|
924
971
|
# compute distance matrix
|
|
925
972
|
dist_map = cdist(coordinates_A, coordinates_B, metric="euclidean")
|
|
926
973
|
|
|
@@ -931,21 +978,21 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
931
978
|
lblB = labelsB
|
|
932
979
|
|
|
933
980
|
print(f"Distance {d} for contact as border")
|
|
934
|
-
contact_pairs = contact_neighborhood(labelsA[t], labelsB=lblB, border=d, connectivity=2)
|
|
981
|
+
contact_pairs = contact_neighborhood(labelsA[t], labelsB=lblB, border=d, connectivity=2)
|
|
935
982
|
|
|
936
983
|
print(t, f"{np.unique(labelsA[t])=}")
|
|
937
984
|
print(f"Frame {t}: found the following contact pairs: {contact_pairs}...")
|
|
938
985
|
# Put infinite distance to all non-contact pairs (something like this)
|
|
939
|
-
plot_map=False
|
|
986
|
+
plot_map = False
|
|
940
987
|
|
|
941
|
-
if len(contact_pairs)>0:
|
|
988
|
+
if len(contact_pairs) > 0:
|
|
942
989
|
mask = np.ones_like(dist_map).astype(bool)
|
|
943
|
-
|
|
990
|
+
|
|
944
991
|
indices_to_keep = []
|
|
945
992
|
for cp in contact_pairs:
|
|
946
|
-
|
|
947
|
-
if np.any(cp<0):
|
|
948
|
-
if cp[0]<0:
|
|
993
|
+
|
|
994
|
+
if np.any(cp < 0):
|
|
995
|
+
if cp[0] < 0:
|
|
949
996
|
mask_A = cp[1]
|
|
950
997
|
mask_B = np.abs(cp[0])
|
|
951
998
|
else:
|
|
@@ -957,8 +1004,8 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
957
1004
|
|
|
958
1005
|
try:
|
|
959
1006
|
|
|
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]
|
|
1007
|
+
idx_A = np.where(mask_ids_A == int(mask_A))[0][0]
|
|
1008
|
+
idx_B = np.where(mask_ids_B == int(mask_B))[0][0]
|
|
962
1009
|
print(idx_A, idx_B)
|
|
963
1010
|
indices_to_keep.append([idx_A,idx_B])
|
|
964
1011
|
except Exception as e:
|
|
@@ -966,17 +1013,17 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
966
1013
|
pass
|
|
967
1014
|
|
|
968
1015
|
print(f'Indices to keep: {indices_to_keep}...')
|
|
969
|
-
if len(indices_to_keep)>0:
|
|
1016
|
+
if len(indices_to_keep) > 0:
|
|
970
1017
|
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
|
|
1018
|
+
mask[indices_to_keep[:, 0], indices_to_keep[:, 1]] = False
|
|
1019
|
+
if mode == 'self':
|
|
1020
|
+
mask[indices_to_keep[:, 1], indices_to_keep[:, 0]] = False
|
|
974
1021
|
dist_map[mask] = 1.0E06
|
|
975
1022
|
plot_map=True
|
|
976
1023
|
else:
|
|
977
1024
|
dist_map[:,:] = 1.0E06
|
|
978
1025
|
else:
|
|
979
|
-
dist_map[
|
|
1026
|
+
dist_map[:, :] = 1.0E06
|
|
980
1027
|
|
|
981
1028
|
# PROCEED all the same?? --> I guess so
|
|
982
1029
|
# if plot_map:
|
|
@@ -988,62 +1035,63 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
988
1035
|
|
|
989
1036
|
d_filter = 1.0E05
|
|
990
1037
|
if attention_weight:
|
|
991
|
-
weights, closest_A = compute_attention_weight(dist_map, d_filter, status_A, ids_A, axis=1,
|
|
992
|
-
|
|
1038
|
+
weights, closest_A = compute_attention_weight(dist_map, d_filter, status_A, ids_A, axis=1,
|
|
1039
|
+
include_dead_weight=include_dead_weight)
|
|
1040
|
+
|
|
993
1041
|
# Target centric
|
|
994
1042
|
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
|
-
|
|
1043
|
+
|
|
1044
|
+
col = dist_map[k, :]
|
|
1045
|
+
col[col == 0.] = 1.0E06
|
|
1046
|
+
|
|
1047
|
+
neighs_B = np.array([ids_B[i] for i in np.where((col <= d_filter))[0]])
|
|
1048
|
+
status_neigh_B = np.array([status_B[i] for i in np.where((col <= d_filter))[0]])
|
|
1049
|
+
dist_B = [round(col[i], 2) for i in np.where((col <= d_filter))[0]]
|
|
1050
|
+
if len(dist_B) > 0:
|
|
1051
|
+
closest_B_cell = neighs_B[np.argmin(dist_B)]
|
|
1052
|
+
|
|
1005
1053
|
if symmetrize and attention_weight:
|
|
1006
1054
|
n_neighs = float(len(neighs_B))
|
|
1007
1055
|
if not include_dead_weight:
|
|
1008
|
-
n_neighs_alive = len(np.where(status_neigh_B==1)[0])
|
|
1056
|
+
n_neighs_alive = len(np.where(status_neigh_B == 1)[0])
|
|
1009
1057
|
neigh_count = n_neighs_alive
|
|
1010
1058
|
else:
|
|
1011
1059
|
neigh_count = n_neighs
|
|
1012
|
-
if neigh_count>0:
|
|
1013
|
-
weight_A = 1
|
|
1060
|
+
if neigh_count > 0:
|
|
1061
|
+
weight_A = 1. / neigh_count
|
|
1014
1062
|
else:
|
|
1015
1063
|
weight_A = np.nan
|
|
1016
1064
|
|
|
1017
|
-
if not include_dead_weight and status_A[k]==0:
|
|
1065
|
+
if not include_dead_weight and status_A[k] == 0:
|
|
1018
1066
|
weight_A = 0
|
|
1019
|
-
|
|
1067
|
+
|
|
1020
1068
|
neighs = []
|
|
1021
1069
|
setA.at[index_A[k], neigh_col] = []
|
|
1022
1070
|
for n in range(len(neighs_B)):
|
|
1023
|
-
|
|
1071
|
+
|
|
1024
1072
|
# index in setB
|
|
1025
|
-
n_index = np.where(ids_B==neighs_B[n])[0][0]
|
|
1073
|
+
n_index = np.where(ids_B == neighs_B[n])[0][0]
|
|
1026
1074
|
# Assess if neigh B is closest to A
|
|
1027
1075
|
if attention_weight:
|
|
1028
|
-
if closest_A[n_index]==ids_A[k]:
|
|
1076
|
+
if closest_A[n_index] == ids_A[k]:
|
|
1029
1077
|
closest = True
|
|
1030
1078
|
else:
|
|
1031
1079
|
closest = False
|
|
1032
|
-
|
|
1080
|
+
|
|
1033
1081
|
if symmetrize:
|
|
1034
1082
|
# Load neighborhood previous data
|
|
1035
1083
|
sym_neigh = setB.loc[index_B[n_index], neigh_col]
|
|
1036
|
-
if neighs_B[n]==closest_B_cell:
|
|
1037
|
-
closest_b=True
|
|
1084
|
+
if neighs_B[n] == closest_B_cell:
|
|
1085
|
+
closest_b = True
|
|
1038
1086
|
else:
|
|
1039
|
-
closest_b=False
|
|
1087
|
+
closest_b = False
|
|
1040
1088
|
if isinstance(sym_neigh, list):
|
|
1041
1089
|
sym_neigh.append({'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]})
|
|
1042
1090
|
else:
|
|
1043
|
-
sym_neigh = [{'id': ids_A[k], 'distance': dist_B[n],'status': status_A[k]}]
|
|
1091
|
+
sym_neigh = [{'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]}]
|
|
1044
1092
|
if attention_weight:
|
|
1045
1093
|
sym_neigh[-1].update({'weight': weight_A, 'closest': closest_b})
|
|
1046
|
-
|
|
1094
|
+
|
|
1047
1095
|
# Write the minimum info about neighborhing cell B
|
|
1048
1096
|
neigh_dico = {'id': neighs_B[n], 'distance': dist_B[n], 'status': status_neigh_B[n]}
|
|
1049
1097
|
if attention_weight:
|
|
@@ -1051,33 +1099,43 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
1051
1099
|
|
|
1052
1100
|
if compute_cum_sum:
|
|
1053
1101
|
# Compute the integrated presence of the neighboring cell B
|
|
1054
|
-
assert cl[1][
|
|
1055
|
-
|
|
1102
|
+
assert cl[1][
|
|
1103
|
+
'track'] == 'TRACK_ID', 'The set B does not seem to contain tracked data. The cumulative time will be meaningless.'
|
|
1104
|
+
past_neighs = [[ll['id'] for ll in l] if len(l) > 0 else [None] for l in setA.loc[
|
|
1105
|
+
(setA[cl[0]['track']] == ids_A[k]) & (setA[cl[0]['time']] <= t), neigh_col].to_numpy()]
|
|
1056
1106
|
past_neighs = [item for sublist in past_neighs for item in sublist]
|
|
1057
|
-
|
|
1107
|
+
|
|
1058
1108
|
if attention_weight:
|
|
1059
|
-
past_weights = [[ll['weight'] for ll in l] if len(l)>0 else [None] for l in setA.loc[
|
|
1109
|
+
past_weights = [[ll['weight'] for ll in l] if len(l) > 0 else [None] for l in setA.loc[
|
|
1110
|
+
(setA[cl[0]['track']] == ids_A[k]) & (
|
|
1111
|
+
setA[cl[0]['time']] <= t), neigh_col].to_numpy()]
|
|
1060
1112
|
past_weights = [item for sublist in past_weights for item in sublist]
|
|
1061
1113
|
|
|
1062
|
-
cum_sum = len(np.where(past_neighs==neighs_B[n])[0])
|
|
1063
|
-
neigh_dico.update({'cumulated_presence': cum_sum+1})
|
|
1064
|
-
|
|
1114
|
+
cum_sum = len(np.where(past_neighs == neighs_B[n])[0])
|
|
1115
|
+
neigh_dico.update({'cumulated_presence': cum_sum + 1})
|
|
1116
|
+
|
|
1065
1117
|
if attention_weight:
|
|
1066
|
-
cum_sum_weighted = np.sum(
|
|
1118
|
+
cum_sum_weighted = np.sum(
|
|
1119
|
+
[w if l == neighs_B[n] else 0 for l, w in zip(past_neighs, past_weights)])
|
|
1067
1120
|
neigh_dico.update({'cumulated_presence_weighted': cum_sum_weighted + weights[n_index]})
|
|
1068
1121
|
|
|
1069
1122
|
if symmetrize:
|
|
1070
1123
|
setB.at[index_B[n_index], neigh_col] = sym_neigh
|
|
1071
|
-
|
|
1124
|
+
|
|
1072
1125
|
neighs.append(neigh_dico)
|
|
1073
|
-
|
|
1126
|
+
|
|
1074
1127
|
setA.at[index_A[k], neigh_col] = neighs
|
|
1075
|
-
|
|
1128
|
+
|
|
1076
1129
|
return setA, setB
|
|
1077
1130
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1131
|
+
|
|
1132
|
+
def compute_contact_neighborhood_at_position(pos, distance, population=['targets', 'effectors'], theta_dist=None,
|
|
1133
|
+
img_shape=(2048, 2048), return_tables=False, clear_neigh=False,
|
|
1134
|
+
event_time_col=None,
|
|
1135
|
+
neighborhood_kwargs={'mode': 'two-pop', 'status': None,
|
|
1136
|
+
'not_status_option': None,
|
|
1137
|
+
'include_dead_weight': True, "compute_cum_sum": False,
|
|
1138
|
+
"attention_weight": True, 'symmetrize': True}):
|
|
1081
1139
|
"""
|
|
1082
1140
|
Computes neighborhood metrics for specified cell populations within a given position, based on distance criteria and additional parameters.
|
|
1083
1141
|
|
|
@@ -1115,12 +1173,12 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1115
1173
|
------
|
|
1116
1174
|
AssertionError
|
|
1117
1175
|
If the specified position path does not exist or if the number of distances and edge thresholds do not match.
|
|
1118
|
-
|
|
1176
|
+
|
|
1119
1177
|
"""
|
|
1120
1178
|
|
|
1121
|
-
pos = pos.replace('\\','/')
|
|
1179
|
+
pos = pos.replace('\\', '/')
|
|
1122
1180
|
pos = rf"{pos}"
|
|
1123
|
-
assert os.path.exists(pos),f'Position {pos} is not a valid path.'
|
|
1181
|
+
assert os.path.exists(pos), f'Position {pos} is not a valid path.'
|
|
1124
1182
|
|
|
1125
1183
|
if isinstance(population, str):
|
|
1126
1184
|
population = [population, population]
|
|
@@ -1131,12 +1189,12 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1131
1189
|
theta_dist = [theta_dist]
|
|
1132
1190
|
|
|
1133
1191
|
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.'
|
|
1192
|
+
theta_dist = [0 for d in distance] # 0.9*d
|
|
1193
|
+
assert len(theta_dist) == len(distance), 'Incompatible number of distances and number of edge thresholds.'
|
|
1136
1194
|
|
|
1137
|
-
if population[0]==population[1]:
|
|
1195
|
+
if population[0] == population[1]:
|
|
1138
1196
|
neighborhood_kwargs.update({'mode': 'self'})
|
|
1139
|
-
if population[1]!=population[0]:
|
|
1197
|
+
if population[1] != population[0]:
|
|
1140
1198
|
neighborhood_kwargs.update({'mode': 'two-pop'})
|
|
1141
1199
|
|
|
1142
1200
|
df_A, path_A = get_position_table(pos, population=population[0], return_path=True)
|
|
@@ -1144,6 +1202,15 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1144
1202
|
if df_A is None or df_B is None:
|
|
1145
1203
|
return None
|
|
1146
1204
|
|
|
1205
|
+
if clear_neigh:
|
|
1206
|
+
if os.path.exists(path_A.replace('.csv','.pkl')):
|
|
1207
|
+
os.remove(path_A.replace('.csv','.pkl'))
|
|
1208
|
+
if os.path.exists(path_B.replace('.csv','.pkl')):
|
|
1209
|
+
os.remove(path_B.replace('.csv','.pkl'))
|
|
1210
|
+
df_pair, pair_path = get_position_table(pos, population='pairs', return_path=True)
|
|
1211
|
+
if df_pair is not None:
|
|
1212
|
+
os.remove(pair_path)
|
|
1213
|
+
|
|
1147
1214
|
df_A_pkl = get_position_pickle(pos, population=population[0], return_path=False)
|
|
1148
1215
|
df_B_pkl = get_position_pickle(pos, population=population[1], return_path=False)
|
|
1149
1216
|
|
|
@@ -1152,12 +1219,9 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1152
1219
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
1153
1220
|
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
1154
1221
|
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
else:
|
|
1159
|
-
cols.append('ID')
|
|
1160
|
-
on_cols = ['ID','FRAME']
|
|
1222
|
+
id_col = extract_identity_col(df_A_pkl)
|
|
1223
|
+
cols.append(id_col)
|
|
1224
|
+
on_cols = [id_col, 'FRAME']
|
|
1161
1225
|
|
|
1162
1226
|
print(f'Recover {cols} from the pickle file...')
|
|
1163
1227
|
try:
|
|
@@ -1171,12 +1235,9 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1171
1235
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
1172
1236
|
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
1173
1237
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
else:
|
|
1178
|
-
cols.append('ID')
|
|
1179
|
-
on_cols = ['ID','FRAME']
|
|
1238
|
+
id_col = extract_identity_col(df_B_pkl)
|
|
1239
|
+
cols.append(id_col)
|
|
1240
|
+
on_cols = [id_col, 'FRAME']
|
|
1180
1241
|
|
|
1181
1242
|
print(f'Recover {cols} from the pickle file...')
|
|
1182
1243
|
try:
|
|
@@ -1185,7 +1246,7 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1185
1246
|
print(f'Failure to merge pickle and csv files: {e}')
|
|
1186
1247
|
|
|
1187
1248
|
labelsA = locate_labels(pos, population=population[0])
|
|
1188
|
-
if population[1]==population[0]:
|
|
1249
|
+
if population[1] == population[0]:
|
|
1189
1250
|
labelsB = None
|
|
1190
1251
|
else:
|
|
1191
1252
|
labelsB = locate_labels(pos, population=population[1])
|
|
@@ -1194,18 +1255,18 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1194
1255
|
unwanted = df_A.columns[df_A.columns.str.contains('neighborhood')]
|
|
1195
1256
|
df_A = df_A.drop(columns=unwanted)
|
|
1196
1257
|
unwanted = df_B.columns[df_B.columns.str.contains('neighborhood')]
|
|
1197
|
-
df_B = df_B.drop(columns=unwanted)
|
|
1258
|
+
df_B = df_B.drop(columns=unwanted)
|
|
1198
1259
|
|
|
1199
1260
|
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:
|
|
1261
|
+
df_A, df_B = mask_contact_neighborhood(df_A, df_B, labelsA, labelsB, distance, **neighborhood_kwargs)
|
|
1262
|
+
if df_A is None or df_B is None or len(df_A)==0:
|
|
1202
1263
|
return None
|
|
1203
1264
|
|
|
1204
|
-
for td,d in zip(theta_dist, distance):
|
|
1265
|
+
for td, d in zip(theta_dist, distance):
|
|
1205
1266
|
|
|
1206
|
-
if neighborhood_kwargs['mode']=='two-pop':
|
|
1267
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
1207
1268
|
neigh_col = f'neighborhood_2_contact_{d}_px'
|
|
1208
|
-
elif neighborhood_kwargs['mode']=='self':
|
|
1269
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
1209
1270
|
neigh_col = f'neighborhood_self_contact_{d}_px'
|
|
1210
1271
|
|
|
1211
1272
|
df_A.loc[df_A['class_id'].isnull(),neigh_col] = np.nan
|
|
@@ -1215,26 +1276,34 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1215
1276
|
# df_A.loc[~edge_filter_A, neigh_col] = np.nan
|
|
1216
1277
|
# df_B.loc[~edge_filter_B, neigh_col] = np.nan
|
|
1217
1278
|
|
|
1218
|
-
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive','intermediate'],
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1279
|
+
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive', 'intermediate'],
|
|
1280
|
+
decompose_by_status=True)
|
|
1281
|
+
if 'TRACK_ID' in list(df_A.columns):
|
|
1282
|
+
if not np.all(df_A['TRACK_ID'].isnull()):
|
|
1283
|
+
df_A = mean_neighborhood_before_event(df_A, neigh_col, event_time_col, metrics=['inclusive','intermediate'])
|
|
1284
|
+
if event_time_col is not None:
|
|
1285
|
+
df_A = mean_neighborhood_after_event(df_A, neigh_col, event_time_col, metrics=['inclusive', 'intermediate'])
|
|
1286
|
+
print('Done...')
|
|
1287
|
+
|
|
1288
|
+
df_A.to_pickle(path_A.replace('.csv', '.pkl'))
|
|
1289
|
+
if not population[0] == population[1]:
|
|
1290
|
+
# Remove neighborhood column
|
|
1291
|
+
for td, d in zip(theta_dist, distance):
|
|
1292
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
1293
|
+
neigh_col = f'neighborhood_2_contact_{d}_px'
|
|
1294
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
1295
|
+
neigh_col = f'neighborhood_self_contact_{d}_px'
|
|
1296
|
+
df_B = df_B.drop(columns=[neigh_col])
|
|
1297
|
+
df_B.to_pickle(path_B.replace('.csv', '.pkl'))
|
|
1229
1298
|
|
|
1230
1299
|
unwanted = df_A.columns[df_A.columns.str.startswith('neighborhood_')]
|
|
1231
1300
|
df_A2 = df_A.drop(columns=unwanted)
|
|
1232
1301
|
df_A2.to_csv(path_A, index=False)
|
|
1233
1302
|
|
|
1234
|
-
if not population[0]==population[1]:
|
|
1303
|
+
if not population[0] == population[1]:
|
|
1235
1304
|
unwanted = df_B.columns[df_B.columns.str.startswith('neighborhood_')]
|
|
1236
1305
|
df_B_csv = df_B.drop(unwanted, axis=1, inplace=False)
|
|
1237
|
-
df_B_csv.to_csv(path_B,index=False)
|
|
1306
|
+
df_B_csv.to_csv(path_B, index=False)
|
|
1238
1307
|
|
|
1239
1308
|
if return_tables:
|
|
1240
1309
|
return df_A, df_B
|
|
@@ -1249,10 +1318,15 @@ if __name__ == "__main__":
|
|
|
1249
1318
|
|
|
1250
1319
|
print('None')
|
|
1251
1320
|
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
|
-
|
|
1321
|
+
|
|
1322
|
+
test, _ = compute_neighborhood_at_position(pos, [62], population=['targets', 'effectors'], theta_dist=None,
|
|
1323
|
+
img_shape=(2048, 2048), return_tables=True, clear_neigh=True,
|
|
1324
|
+
neighborhood_kwargs={'mode': 'two-pop', 'status': ['class', None],
|
|
1325
|
+
'not_status_option': [True, False],
|
|
1326
|
+
'include_dead_weight': True,
|
|
1327
|
+
"compute_cum_sum": False, "attention_weight": True,
|
|
1328
|
+
'symmetrize': False})
|
|
1329
|
+
|
|
1330
|
+
# test = compute_neighborhood_metrics(test, 'neighborhood_self_circle_150_px', metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
1257
1331
|
print(test.columns)
|
|
1258
1332
|
#print(segment(None,'test'))
|