celldetective 1.1.1.post3__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/__main__.py +17 -0
- celldetective/extra_properties.py +62 -34
- celldetective/gui/__init__.py +1 -0
- celldetective/gui/analyze_block.py +2 -1
- celldetective/gui/classifier_widget.py +18 -10
- celldetective/gui/control_panel.py +57 -6
- celldetective/gui/layouts.py +14 -11
- celldetective/gui/neighborhood_options.py +21 -13
- 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 +110 -30
- 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 +248 -43
- celldetective/io.py +598 -416
- celldetective/measure.py +919 -969
- celldetective/models/pair_signal_detection/blank +0 -0
- celldetective/neighborhood.py +482 -340
- celldetective/preprocessing.py +81 -61
- 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/segmentation.py +67 -29
- celldetective/signals.py +402 -8
- celldetective/tracking.py +8 -2
- celldetective/utils.py +144 -12
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/METADATA +8 -8
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/RECORD +42 -38
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/WHEEL +1 -1
- tests/test_segmentation.py +1 -1
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.post3.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
|
|
|
@@ -48,45 +46,47 @@ def set_live_status(setA,setB,status, not_status_option):
|
|
|
48
46
|
|
|
49
47
|
"""
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
if status is None:
|
|
49
|
+
print(f"Provided statuses: {status}...")
|
|
50
|
+
if status is None or status==["live_status","live_status"] or status==[None,None]:
|
|
53
51
|
setA.loc[:,'live_status'] = 1
|
|
54
52
|
setB.loc[:,'live_status'] = 1
|
|
55
53
|
status = ['live_status', 'live_status']
|
|
56
54
|
elif isinstance(status,list):
|
|
57
55
|
assert len(status)==2,'Please provide only two columns to classify cells as alive or dead.'
|
|
58
|
-
if status[0] is None:
|
|
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
|
-
if status[1] is None:
|
|
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,75 +377,116 @@ 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
|
|
|
380
404
|
if df_A_pkl is not None:
|
|
381
405
|
pkl_columns = np.array(df_A_pkl.columns)
|
|
382
406
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
383
|
-
cols = list(pkl_columns[neigh_columns]) + ['
|
|
407
|
+
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
408
|
+
|
|
409
|
+
id_col = extract_identity_col(df_A_pkl)
|
|
410
|
+
cols.append(id_col)
|
|
411
|
+
on_cols = [id_col, 'FRAME']
|
|
412
|
+
|
|
384
413
|
print(f'Recover {cols} from the pickle file...')
|
|
385
|
-
|
|
386
|
-
|
|
414
|
+
try:
|
|
415
|
+
df_A = pd.merge(df_A, df_A_pkl.loc[:,cols], how="outer", on=on_cols)
|
|
416
|
+
print(df_A.columns)
|
|
417
|
+
except Exception as e:
|
|
418
|
+
print(f'Failure to merge pickle and csv files: {e}')
|
|
419
|
+
|
|
387
420
|
if df_B_pkl is not None and df_B is not None:
|
|
388
421
|
pkl_columns = np.array(df_B_pkl.columns)
|
|
389
422
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
390
|
-
cols = list(pkl_columns[neigh_columns]) + ['
|
|
423
|
+
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
424
|
+
|
|
425
|
+
id_col = extract_identity_col(df_B_pkl)
|
|
426
|
+
cols.append(id_col)
|
|
427
|
+
on_cols = [id_col, 'FRAME']
|
|
428
|
+
|
|
391
429
|
print(f'Recover {cols} from the pickle file...')
|
|
392
|
-
|
|
430
|
+
try:
|
|
431
|
+
df_B = pd.merge(df_B, df_B_pkl.loc[:,cols], how="outer", on=on_cols)
|
|
432
|
+
except Exception as e:
|
|
433
|
+
print(f'Failure to merge pickle and csv files: {e}')
|
|
393
434
|
|
|
394
435
|
if clear_neigh:
|
|
395
436
|
unwanted = df_A.columns[df_A.columns.str.contains('neighborhood')]
|
|
396
437
|
df_A = df_A.drop(columns=unwanted)
|
|
397
438
|
unwanted = df_B.columns[df_B.columns.str.contains('neighborhood')]
|
|
398
|
-
df_B = df_B.drop(columns=unwanted)
|
|
439
|
+
df_B = df_B.drop(columns=unwanted)
|
|
399
440
|
|
|
400
|
-
df_A, df_B = distance_cut_neighborhood(df_A,df_B, distance
|
|
401
|
-
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:
|
|
402
443
|
return None
|
|
403
444
|
|
|
404
|
-
for td,d in zip(theta_dist, distance):
|
|
445
|
+
for td, d in zip(theta_dist, distance):
|
|
405
446
|
|
|
406
|
-
if neighborhood_kwargs['mode']=='two-pop':
|
|
447
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
407
448
|
neigh_col = f'neighborhood_2_circle_{d}_px'
|
|
408
|
-
elif neighborhood_kwargs['mode']=='self':
|
|
449
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
409
450
|
neigh_col = f'neighborhood_self_circle_{d}_px'
|
|
410
451
|
|
|
411
|
-
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))
|
|
412
|
-
edge_filter_B = (df_B['POSITION_X'] > td)&(df_B['POSITION_Y'] > td)&(df_B['POSITION_Y'] < (img_shape[0] - td))&(df_B['POSITION_X'] < (img_shape[1] - td))
|
|
413
|
-
df_A.loc[~edge_filter_A, neigh_col] = np.nan
|
|
414
|
-
df_B.loc[~edge_filter_B, neigh_col] = np.nan
|
|
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))
|
|
453
|
+
# edge_filter_B = (df_B['POSITION_X'] > td)&(df_B['POSITION_Y'] > td)&(df_B['POSITION_Y'] < (img_shape[0] - td))&(df_B['POSITION_X'] < (img_shape[1] - td))
|
|
454
|
+
# df_A.loc[~edge_filter_A, neigh_col] = np.nan
|
|
455
|
+
# df_B.loc[~edge_filter_B, neigh_col] = np.nan
|
|
415
456
|
|
|
457
|
+
print('Count neighborhood...')
|
|
416
458
|
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
417
|
-
if neighborhood_kwargs['symmetrize']:
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
459
|
+
# if neighborhood_kwargs['symmetrize']:
|
|
460
|
+
# df_B = compute_neighborhood_metrics(df_B, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
|
|
461
|
+
print('Done...')
|
|
462
|
+
|
|
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'))
|
|
426
481
|
|
|
427
482
|
unwanted = df_A.columns[df_A.columns.str.startswith('neighborhood_')]
|
|
428
483
|
df_A2 = df_A.drop(columns=unwanted)
|
|
429
484
|
df_A2.to_csv(path_A, index=False)
|
|
430
485
|
|
|
431
|
-
if not population[0]==population[1]:
|
|
486
|
+
if not population[0] == population[1]:
|
|
432
487
|
unwanted = df_B.columns[df_B.columns.str.startswith('neighborhood_')]
|
|
433
488
|
df_B_csv = df_B.drop(unwanted, axis=1, inplace=False)
|
|
434
|
-
df_B_csv.to_csv(path_B,index=False)
|
|
489
|
+
df_B_csv.to_csv(path_B, index=False)
|
|
435
490
|
|
|
436
491
|
if return_tables:
|
|
437
492
|
return df_A, df_B
|
|
@@ -480,17 +535,21 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
480
535
|
>>> neigh_col = 'neighborhood_info'
|
|
481
536
|
>>> updated_neigh_table = compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive'], decompose_by_status=True)
|
|
482
537
|
# Computes the inclusive count of neighbors for each cell, decomposed by cell status.
|
|
483
|
-
|
|
538
|
+
|
|
484
539
|
"""
|
|
485
540
|
|
|
486
541
|
neigh_table = neigh_table.reset_index(drop=True)
|
|
487
542
|
if 'position' in list(neigh_table.columns):
|
|
488
|
-
groupbycols = ['position'
|
|
543
|
+
groupbycols = ['position']
|
|
489
544
|
else:
|
|
490
|
-
groupbycols = [
|
|
545
|
+
groupbycols = []
|
|
546
|
+
|
|
547
|
+
id_col = extract_identity_col(neigh_table)
|
|
548
|
+
groupbycols.append(id_col)
|
|
549
|
+
|
|
491
550
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
492
551
|
|
|
493
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
552
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
494
553
|
group = group.dropna(subset=neigh_col)
|
|
495
554
|
indices = list(group.index)
|
|
496
555
|
neighbors = group[neigh_col].to_numpy()
|
|
@@ -536,49 +595,52 @@ def compute_neighborhood_metrics(neigh_table, neigh_col, metrics=['inclusive','e
|
|
|
536
595
|
if 'intermediate' in metrics:
|
|
537
596
|
n_intermediate[t] = np.sum(weights_at_t)
|
|
538
597
|
if 'exclusive' in metrics:
|
|
539
|
-
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])
|
|
540
599
|
|
|
541
600
|
if decompose_by_status:
|
|
542
601
|
|
|
543
602
|
if 'inclusive' in metrics:
|
|
544
|
-
n_inclusive_status_0[t] = sum([s==0.0 for s in status_at_t])
|
|
545
|
-
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])
|
|
546
605
|
|
|
547
606
|
if 'intermediate' in metrics:
|
|
548
607
|
weights_at_t = np.array(weights_at_t)
|
|
549
|
-
|
|
608
|
+
|
|
550
609
|
# intermediate
|
|
551
|
-
weights_status_1 = weights_at_t[np.array([s==1.0 for s in status_at_t],dtype=bool)]
|
|
552
|
-
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)]
|
|
553
612
|
n_intermediate_status_1[t] = np.sum(weights_status_1)
|
|
554
613
|
n_intermediate_status_0[t] = np.sum(weights_status_0)
|
|
555
614
|
|
|
556
615
|
if 'exclusive' in metrics:
|
|
557
|
-
n_exclusive_status_0[t] = sum(
|
|
558
|
-
|
|
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)])
|
|
559
620
|
|
|
560
621
|
if 'inclusive' in metrics:
|
|
561
|
-
neigh_table.loc[indices, 'inclusive_count_'+neigh_col] = n_inclusive
|
|
622
|
+
neigh_table.loc[indices, 'inclusive_count_' + neigh_col] = n_inclusive
|
|
562
623
|
if 'intermediate' in metrics:
|
|
563
|
-
neigh_table.loc[indices, 'intermediate_count_'+neigh_col] = n_intermediate
|
|
624
|
+
neigh_table.loc[indices, 'intermediate_count_' + neigh_col] = n_intermediate
|
|
564
625
|
if 'exclusive' in metrics:
|
|
565
|
-
neigh_table.loc[indices, 'exclusive_count_'+neigh_col] = n_exclusive
|
|
626
|
+
neigh_table.loc[indices, 'exclusive_count_' + neigh_col] = n_exclusive
|
|
566
627
|
|
|
567
628
|
if decompose_by_status:
|
|
568
629
|
if 'inclusive' in metrics:
|
|
569
|
-
neigh_table.loc[indices, 'inclusive_count_s0_'+neigh_col] = n_inclusive_status_0
|
|
570
|
-
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
|
|
571
632
|
if 'intermediate' in metrics:
|
|
572
|
-
neigh_table.loc[indices, 'intermediate_count_s0_'+neigh_col] = n_intermediate_status_0
|
|
573
|
-
neigh_table.loc[indices, 'intermediate_count_s1_'+neigh_col] = n_intermediate_status_1
|
|
574
|
-
if 'exclusive' in metrics:
|
|
575
|
-
neigh_table.loc[indices, 'exclusive_count_s0_'+neigh_col] = n_exclusive_status_0
|
|
576
|
-
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
|
|
577
638
|
|
|
578
639
|
return neigh_table
|
|
579
640
|
|
|
580
|
-
|
|
581
|
-
|
|
641
|
+
|
|
642
|
+
def mean_neighborhood_before_event(neigh_table, neigh_col, event_time_col,
|
|
643
|
+
metrics=['inclusive', 'exclusive', 'intermediate']):
|
|
582
644
|
"""
|
|
583
645
|
Computes the mean neighborhood metrics for each cell track before a specified event time.
|
|
584
646
|
|
|
@@ -607,52 +669,64 @@ def mean_neighborhood_before_event(neigh_table, neigh_col, event_time_col, metri
|
|
|
607
669
|
"""
|
|
608
670
|
|
|
609
671
|
|
|
672
|
+
neigh_table = neigh_table.reset_index(drop=True)
|
|
610
673
|
if 'position' in list(neigh_table.columns):
|
|
611
|
-
groupbycols = ['position'
|
|
674
|
+
groupbycols = ['position']
|
|
612
675
|
else:
|
|
613
|
-
groupbycols = [
|
|
676
|
+
groupbycols = []
|
|
677
|
+
|
|
678
|
+
id_col = extract_identity_col(neigh_table)
|
|
679
|
+
groupbycols.append(id_col)
|
|
680
|
+
|
|
614
681
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
615
682
|
suffix = '_before_event'
|
|
616
|
-
|
|
683
|
+
|
|
617
684
|
if event_time_col is None:
|
|
618
685
|
print('No event time was provided... Estimating the mean neighborhood over the whole observation time...')
|
|
619
|
-
neigh_table.loc[:,'event_time_temp'] = neigh_table['FRAME'].max()
|
|
686
|
+
neigh_table.loc[:, 'event_time_temp'] = neigh_table['FRAME'].max()
|
|
620
687
|
event_time_col = 'event_time_temp'
|
|
621
688
|
suffix = ''
|
|
622
|
-
|
|
623
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
689
|
+
|
|
690
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
624
691
|
|
|
625
692
|
group = group.dropna(subset=neigh_col)
|
|
626
693
|
indices = list(group.index)
|
|
627
694
|
|
|
628
695
|
event_time_values = group[event_time_col].to_numpy()
|
|
629
|
-
if len(event_time_values)>0:
|
|
696
|
+
if len(event_time_values) > 0:
|
|
630
697
|
event_time = event_time_values[0]
|
|
631
698
|
else:
|
|
632
699
|
continue
|
|
633
700
|
|
|
634
|
-
if event_time<0.:
|
|
701
|
+
if event_time < 0.:
|
|
635
702
|
event_time = group['FRAME'].max()
|
|
636
703
|
|
|
637
704
|
if 'intermediate' in metrics:
|
|
638
|
-
valid_counts_intermediate = group.loc[
|
|
639
|
-
|
|
640
|
-
|
|
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)
|
|
641
710
|
if 'inclusive' in metrics:
|
|
642
|
-
valid_counts_inclusive = group.loc[
|
|
643
|
-
|
|
644
|
-
|
|
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)
|
|
645
716
|
if 'exclusive' in metrics:
|
|
646
|
-
valid_counts_exclusive = group.loc[
|
|
647
|
-
|
|
648
|
-
|
|
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)
|
|
649
722
|
|
|
650
|
-
if event_time_col=='event_time_temp':
|
|
723
|
+
if event_time_col == 'event_time_temp':
|
|
651
724
|
neigh_table = neigh_table.drop(columns='event_time_temp')
|
|
652
725
|
return neigh_table
|
|
653
726
|
|
|
654
|
-
|
|
655
|
-
|
|
727
|
+
|
|
728
|
+
def mean_neighborhood_after_event(neigh_table, neigh_col, event_time_col,
|
|
729
|
+
metrics=['inclusive', 'exclusive', 'intermediate']):
|
|
656
730
|
"""
|
|
657
731
|
Computes the mean neighborhood metrics for each cell track after a specified event time.
|
|
658
732
|
|
|
@@ -681,54 +755,67 @@ def mean_neighborhood_after_event(neigh_table, neigh_col, event_time_col, metric
|
|
|
681
755
|
"""
|
|
682
756
|
|
|
683
757
|
|
|
758
|
+
neigh_table = neigh_table.reset_index(drop=True)
|
|
684
759
|
if 'position' in list(neigh_table.columns):
|
|
685
|
-
groupbycols = ['position'
|
|
760
|
+
groupbycols = ['position']
|
|
686
761
|
else:
|
|
687
|
-
groupbycols = [
|
|
762
|
+
groupbycols = []
|
|
763
|
+
|
|
764
|
+
id_col = extract_identity_col(neigh_table)
|
|
765
|
+
groupbycols.append(id_col)
|
|
766
|
+
|
|
688
767
|
neigh_table.sort_values(by=groupbycols+['FRAME'],inplace=True)
|
|
689
768
|
suffix = '_after_event'
|
|
690
|
-
|
|
769
|
+
|
|
691
770
|
if event_time_col is None:
|
|
692
|
-
neigh_table.loc[:,'event_time_temp'] = None
|
|
771
|
+
neigh_table.loc[:, 'event_time_temp'] = None # neigh_table['FRAME'].max()
|
|
693
772
|
event_time_col = 'event_time_temp'
|
|
694
773
|
suffix = ''
|
|
695
|
-
|
|
696
|
-
for tid,group in neigh_table.groupby(groupbycols):
|
|
774
|
+
|
|
775
|
+
for tid, group in neigh_table.groupby(groupbycols):
|
|
697
776
|
|
|
698
777
|
group = group.dropna(subset=neigh_col)
|
|
699
778
|
indices = list(group.index)
|
|
700
779
|
|
|
701
780
|
event_time_values = group[event_time_col].to_numpy()
|
|
702
|
-
if len(event_time_values)>0:
|
|
781
|
+
if len(event_time_values) > 0:
|
|
703
782
|
event_time = event_time_values[0]
|
|
704
783
|
else:
|
|
705
784
|
continue
|
|
706
785
|
|
|
707
|
-
if event_time is not None and (event_time>=0.):
|
|
786
|
+
if event_time is not None and (event_time >= 0.):
|
|
708
787
|
|
|
709
788
|
if 'intermediate' in metrics:
|
|
710
|
-
valid_counts_intermediate = group.loc[
|
|
711
|
-
|
|
712
|
-
|
|
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)
|
|
713
794
|
if 'inclusive' in metrics:
|
|
714
|
-
valid_counts_inclusive = group.loc[
|
|
715
|
-
|
|
716
|
-
|
|
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)
|
|
717
800
|
if 'exclusive' in metrics:
|
|
718
|
-
valid_counts_exclusive = group.loc[
|
|
719
|
-
|
|
720
|
-
|
|
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)
|
|
721
806
|
|
|
722
|
-
if event_time_col=='event_time_temp':
|
|
807
|
+
if event_time_col == 'event_time_temp':
|
|
723
808
|
neigh_table = neigh_table.drop(columns='event_time_temp')
|
|
724
809
|
|
|
725
810
|
return neigh_table
|
|
726
811
|
|
|
812
|
+
|
|
727
813
|
# New functions for direct cell-cell contact neighborhood
|
|
728
814
|
|
|
729
815
|
def sign(num):
|
|
730
816
|
return -1 if num < 0 else 1
|
|
731
817
|
|
|
818
|
+
|
|
732
819
|
def contact_neighborhood(labelsA, labelsB=None, border=3, connectivity=2):
|
|
733
820
|
|
|
734
821
|
labelsA = labelsA.astype(float)
|
|
@@ -736,50 +823,50 @@ def contact_neighborhood(labelsA, labelsB=None, border=3, connectivity=2):
|
|
|
736
823
|
labelsB = labelsB.astype(float)
|
|
737
824
|
|
|
738
825
|
print(f"Border = {border}")
|
|
739
|
-
|
|
826
|
+
|
|
740
827
|
if border > 0:
|
|
741
828
|
print(labelsA.shape, border * (-1))
|
|
742
829
|
labelsA_edge = contour_of_instance_segmentation(label=labelsA, distance=border * (-1)).astype(float)
|
|
743
|
-
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)]
|
|
744
831
|
if labelsB is not None:
|
|
745
832
|
labelsB_edge = contour_of_instance_segmentation(label=labelsB, distance=border * (-1)).astype(float)
|
|
746
|
-
labelsB[np.where(labelsB_edge>0)] = labelsB_edge[np.where(labelsB_edge>0)]
|
|
747
|
-
|
|
833
|
+
labelsB[np.where(labelsB_edge > 0)] = labelsB_edge[np.where(labelsB_edge > 0)]
|
|
834
|
+
|
|
748
835
|
if labelsB is not None:
|
|
749
|
-
labelsA[labelsA!=0] = -labelsA[labelsA!=0]
|
|
836
|
+
labelsA[labelsA != 0] = -labelsA[labelsA != 0]
|
|
750
837
|
labelsAB = merge_labels(labelsA, labelsB)
|
|
751
838
|
labelsBA = merge_labels(labelsB, labelsA)
|
|
752
839
|
label_cases = [labelsAB, labelsBA]
|
|
753
840
|
else:
|
|
754
841
|
label_cases = [labelsA]
|
|
755
|
-
|
|
842
|
+
|
|
756
843
|
coocurrences = []
|
|
757
844
|
for lbl in label_cases:
|
|
758
845
|
coocurrences.extend(find_contact_neighbors(lbl, connectivity=connectivity))
|
|
759
|
-
|
|
760
|
-
unique_pairs = np.unique(coocurrences,axis=0)
|
|
761
|
-
|
|
846
|
+
|
|
847
|
+
unique_pairs = np.unique(coocurrences, axis=0)
|
|
848
|
+
|
|
762
849
|
if labelsB is not None:
|
|
763
|
-
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)
|
|
764
852
|
else:
|
|
765
|
-
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0]!=p[1]],axis=0)
|
|
766
|
-
|
|
853
|
+
neighs = np.unique([tuple(sorted(p)) for p in unique_pairs if p[0] != p[1]], axis=0)
|
|
854
|
+
|
|
767
855
|
return neighs
|
|
768
856
|
|
|
769
857
|
def merge_labels(labelsA, labelsB):
|
|
770
|
-
|
|
771
858
|
labelsA = labelsA.astype(float)
|
|
772
859
|
labelsB = labelsB.astype(float)
|
|
773
|
-
|
|
860
|
+
|
|
774
861
|
labelsAB = labelsA.copy()
|
|
775
|
-
labelsAB[np.where(labelsB!=0)] = labelsB[np.where(labelsB!=0)]
|
|
776
|
-
|
|
862
|
+
labelsAB[np.where(labelsB != 0)] = labelsB[np.where(labelsB != 0)]
|
|
863
|
+
|
|
777
864
|
return labelsAB
|
|
778
865
|
|
|
866
|
+
|
|
779
867
|
def find_contact_neighbors(labels, connectivity=2):
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
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)
|
|
783
870
|
g.eliminate_zeros()
|
|
784
871
|
|
|
785
872
|
coo = g.tocoo()
|
|
@@ -788,18 +875,19 @@ def find_contact_neighbors(labels, connectivity=2):
|
|
|
788
875
|
|
|
789
876
|
center_values = labels.ravel()[center_coords]
|
|
790
877
|
neighbor_values = labels.ravel()[neighbor_coords]
|
|
791
|
-
touching_masks = np.column_stack((center_values, neighbor_values))
|
|
792
|
-
|
|
878
|
+
touching_masks = np.column_stack((center_values, neighbor_values))
|
|
879
|
+
|
|
793
880
|
return touching_masks
|
|
794
881
|
|
|
795
882
|
|
|
796
|
-
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,
|
|
797
885
|
attention_weight=True, symmetrize=True, include_dead_weight=True,
|
|
798
|
-
column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y',
|
|
799
|
-
|
|
886
|
+
column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y',
|
|
887
|
+
'mask_id': 'class_id'}):
|
|
800
888
|
"""
|
|
801
889
|
|
|
802
|
-
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.
|
|
803
891
|
|
|
804
892
|
Parameters
|
|
805
893
|
----------
|
|
@@ -809,7 +897,7 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
809
897
|
Cut-distance in pixels to match neighboring pairs.
|
|
810
898
|
mode: str
|
|
811
899
|
neighboring mode, between 'two-pop' (e.g. target-effector) and 'self' (target-target or effector-effector).
|
|
812
|
-
status: None or status
|
|
900
|
+
status: None or status
|
|
813
901
|
name to look for cells to ignore (because they are dead). By default all cells are kept.
|
|
814
902
|
compute_cum_sum: bool,
|
|
815
903
|
compute cumulated time of presence of neighbours (only if trajectories available for both sets)
|
|
@@ -825,57 +913,59 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
825
913
|
if setA is not None and setB is not None:
|
|
826
914
|
setA, setB, status = set_live_status(setA, setB, status, not_status_option)
|
|
827
915
|
else:
|
|
828
|
-
return None,None
|
|
916
|
+
return None, None
|
|
829
917
|
|
|
830
|
-
# Check distance option
|
|
918
|
+
# Check distance option
|
|
831
919
|
if not isinstance(distance, list):
|
|
832
920
|
distance = [distance]
|
|
833
|
-
|
|
921
|
+
|
|
834
922
|
for d in distance:
|
|
835
923
|
# loop over each provided distance
|
|
836
|
-
|
|
837
|
-
if mode=='two-pop':
|
|
924
|
+
|
|
925
|
+
if mode == 'two-pop':
|
|
838
926
|
neigh_col = f'neighborhood_2_contact_{d}_px'
|
|
839
|
-
elif mode=='self':
|
|
927
|
+
elif mode == 'self':
|
|
840
928
|
neigh_col = f'neighborhood_self_contact_{d}_px'
|
|
841
|
-
|
|
929
|
+
|
|
842
930
|
cl = []
|
|
843
|
-
for s in [setA,setB]:
|
|
931
|
+
for s in [setA, setB]:
|
|
844
932
|
|
|
845
933
|
# Check whether data can be tracked
|
|
846
934
|
temp_column_labels = column_labels.copy()
|
|
847
935
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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
|
|
851
940
|
cl.append(temp_column_labels)
|
|
852
941
|
|
|
853
942
|
# Remove nan tracks (cells that do not belong to a track)
|
|
854
943
|
s[neigh_col] = np.nan
|
|
855
944
|
s[neigh_col] = s[neigh_col].astype(object)
|
|
856
|
-
s.dropna(subset=[cl[-1]['track']],inplace=True)
|
|
945
|
+
s.dropna(subset=[cl[-1]['track']], inplace=True)
|
|
857
946
|
|
|
858
947
|
# Loop over each available timestep
|
|
859
|
-
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)
|
|
860
950
|
for t in tqdm(timeline):
|
|
861
951
|
|
|
862
|
-
index_A = list(setA.loc[setA[cl[0]['time']]==t].index)
|
|
863
|
-
coordinates_A = setA.loc[setA[cl[0]['time']]==t,[cl[0]['x'], cl[0]['y']]].to_numpy()
|
|
864
|
-
ids_A = setA.loc[setA[cl[0]['time']]==t,cl[0]['track']].to_numpy()
|
|
865
|
-
mask_ids_A = setA.loc[setA[cl[0]['time']]==t,cl[0]['mask_id']].to_numpy()
|
|
866
|
-
status_A = setA.loc[setA[cl[0]['time']]==t,status[0]].to_numpy()
|
|
867
|
-
|
|
868
|
-
index_B = list(setB.loc[setB[cl[1]['time']]==t].index)
|
|
869
|
-
coordinates_B = setB.loc[setB[cl[1]['time']]==t,[cl[1]['x'], cl[1]['y']]].to_numpy()
|
|
870
|
-
ids_B = setB.loc[setB[cl[1]['time']]==t,cl[1]['track']].to_numpy()
|
|
871
|
-
mask_ids_B = setB.loc[setB[cl[1]['time']]==t,cl[1]['mask_id']].to_numpy()
|
|
872
|
-
status_B = setB.loc[setB[cl[1]['time']]==t,status[1]].to_numpy()
|
|
873
|
-
|
|
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
|
+
|
|
874
964
|
print(f"Frame {t}")
|
|
875
|
-
print(f"{mask_ids_A=}",f"{mask_ids_B}")
|
|
965
|
+
print(f"{mask_ids_A=}", f"{mask_ids_B}")
|
|
876
966
|
|
|
877
967
|
if len(ids_A) > 0 and len(ids_B) > 0:
|
|
878
|
-
|
|
968
|
+
|
|
879
969
|
# compute distance matrix
|
|
880
970
|
dist_map = cdist(coordinates_A, coordinates_B, metric="euclidean")
|
|
881
971
|
|
|
@@ -886,21 +976,21 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
886
976
|
lblB = labelsB
|
|
887
977
|
|
|
888
978
|
print(f"Distance {d} for contact as border")
|
|
889
|
-
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)
|
|
890
980
|
|
|
891
981
|
print(t, f"{np.unique(labelsA[t])=}")
|
|
892
982
|
print(f"Frame {t}: found the following contact pairs: {contact_pairs}...")
|
|
893
983
|
# Put infinite distance to all non-contact pairs (something like this)
|
|
894
|
-
plot_map=False
|
|
984
|
+
plot_map = False
|
|
895
985
|
|
|
896
|
-
if len(contact_pairs)>0:
|
|
986
|
+
if len(contact_pairs) > 0:
|
|
897
987
|
mask = np.ones_like(dist_map).astype(bool)
|
|
898
|
-
|
|
988
|
+
|
|
899
989
|
indices_to_keep = []
|
|
900
990
|
for cp in contact_pairs:
|
|
901
|
-
|
|
902
|
-
if np.any(cp<0):
|
|
903
|
-
if cp[0]<0:
|
|
991
|
+
|
|
992
|
+
if np.any(cp < 0):
|
|
993
|
+
if cp[0] < 0:
|
|
904
994
|
mask_A = cp[1]
|
|
905
995
|
mask_B = np.abs(cp[0])
|
|
906
996
|
else:
|
|
@@ -912,8 +1002,8 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
912
1002
|
|
|
913
1003
|
try:
|
|
914
1004
|
|
|
915
|
-
idx_A = np.where(mask_ids_A==int(mask_A))[0][0]
|
|
916
|
-
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]
|
|
917
1007
|
print(idx_A, idx_B)
|
|
918
1008
|
indices_to_keep.append([idx_A,idx_B])
|
|
919
1009
|
except Exception as e:
|
|
@@ -921,17 +1011,17 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
921
1011
|
pass
|
|
922
1012
|
|
|
923
1013
|
print(f'Indices to keep: {indices_to_keep}...')
|
|
924
|
-
if len(indices_to_keep)>0:
|
|
1014
|
+
if len(indices_to_keep) > 0:
|
|
925
1015
|
indices_to_keep = np.array(indices_to_keep)
|
|
926
|
-
mask[indices_to_keep[:,0],indices_to_keep[:,1]] = False
|
|
927
|
-
if mode=='self':
|
|
928
|
-
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
|
|
929
1019
|
dist_map[mask] = 1.0E06
|
|
930
1020
|
plot_map=True
|
|
931
1021
|
else:
|
|
932
1022
|
dist_map[:,:] = 1.0E06
|
|
933
1023
|
else:
|
|
934
|
-
dist_map[
|
|
1024
|
+
dist_map[:, :] = 1.0E06
|
|
935
1025
|
|
|
936
1026
|
# PROCEED all the same?? --> I guess so
|
|
937
1027
|
# if plot_map:
|
|
@@ -943,62 +1033,63 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
943
1033
|
|
|
944
1034
|
d_filter = 1.0E05
|
|
945
1035
|
if attention_weight:
|
|
946
|
-
weights, closest_A = compute_attention_weight(dist_map, d_filter, status_A, ids_A, axis=1,
|
|
947
|
-
|
|
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
|
+
|
|
948
1039
|
# Target centric
|
|
949
1040
|
for k in range(dist_map.shape[0]):
|
|
950
|
-
|
|
951
|
-
col = dist_map[k
|
|
952
|
-
col[col==0.] = 1.0E06
|
|
953
|
-
|
|
954
|
-
neighs_B = np.array([ids_B[i] for i in np.where((col<=d_filter))[0]])
|
|
955
|
-
status_neigh_B = np.array([status_B[i] for i in np.where((col<=d_filter))[0]])
|
|
956
|
-
dist_B = [round(col[i],2) for i in np.where((col<=d_filter))[0]]
|
|
957
|
-
if len(dist_B)>0:
|
|
958
|
-
closest_B_cell = neighs_B[np.argmin(dist_B)]
|
|
959
|
-
|
|
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
|
+
|
|
960
1051
|
if symmetrize and attention_weight:
|
|
961
1052
|
n_neighs = float(len(neighs_B))
|
|
962
1053
|
if not include_dead_weight:
|
|
963
|
-
n_neighs_alive = len(np.where(status_neigh_B==1)[0])
|
|
1054
|
+
n_neighs_alive = len(np.where(status_neigh_B == 1)[0])
|
|
964
1055
|
neigh_count = n_neighs_alive
|
|
965
1056
|
else:
|
|
966
1057
|
neigh_count = n_neighs
|
|
967
|
-
if neigh_count>0:
|
|
968
|
-
weight_A = 1
|
|
1058
|
+
if neigh_count > 0:
|
|
1059
|
+
weight_A = 1. / neigh_count
|
|
969
1060
|
else:
|
|
970
1061
|
weight_A = np.nan
|
|
971
1062
|
|
|
972
|
-
if not include_dead_weight and status_A[k]==0:
|
|
1063
|
+
if not include_dead_weight and status_A[k] == 0:
|
|
973
1064
|
weight_A = 0
|
|
974
|
-
|
|
1065
|
+
|
|
975
1066
|
neighs = []
|
|
976
1067
|
setA.at[index_A[k], neigh_col] = []
|
|
977
1068
|
for n in range(len(neighs_B)):
|
|
978
|
-
|
|
1069
|
+
|
|
979
1070
|
# index in setB
|
|
980
|
-
n_index = np.where(ids_B==neighs_B[n])[0][0]
|
|
1071
|
+
n_index = np.where(ids_B == neighs_B[n])[0][0]
|
|
981
1072
|
# Assess if neigh B is closest to A
|
|
982
1073
|
if attention_weight:
|
|
983
|
-
if closest_A[n_index]==ids_A[k]:
|
|
1074
|
+
if closest_A[n_index] == ids_A[k]:
|
|
984
1075
|
closest = True
|
|
985
1076
|
else:
|
|
986
1077
|
closest = False
|
|
987
|
-
|
|
1078
|
+
|
|
988
1079
|
if symmetrize:
|
|
989
1080
|
# Load neighborhood previous data
|
|
990
1081
|
sym_neigh = setB.loc[index_B[n_index], neigh_col]
|
|
991
|
-
if neighs_B[n]==closest_B_cell:
|
|
992
|
-
closest_b=True
|
|
1082
|
+
if neighs_B[n] == closest_B_cell:
|
|
1083
|
+
closest_b = True
|
|
993
1084
|
else:
|
|
994
|
-
closest_b=False
|
|
1085
|
+
closest_b = False
|
|
995
1086
|
if isinstance(sym_neigh, list):
|
|
996
1087
|
sym_neigh.append({'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]})
|
|
997
1088
|
else:
|
|
998
|
-
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]}]
|
|
999
1090
|
if attention_weight:
|
|
1000
1091
|
sym_neigh[-1].update({'weight': weight_A, 'closest': closest_b})
|
|
1001
|
-
|
|
1092
|
+
|
|
1002
1093
|
# Write the minimum info about neighborhing cell B
|
|
1003
1094
|
neigh_dico = {'id': neighs_B[n], 'distance': dist_B[n], 'status': status_neigh_B[n]}
|
|
1004
1095
|
if attention_weight:
|
|
@@ -1006,33 +1097,43 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
|
|
|
1006
1097
|
|
|
1007
1098
|
if compute_cum_sum:
|
|
1008
1099
|
# Compute the integrated presence of the neighboring cell B
|
|
1009
|
-
assert cl[1][
|
|
1010
|
-
|
|
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()]
|
|
1011
1104
|
past_neighs = [item for sublist in past_neighs for item in sublist]
|
|
1012
|
-
|
|
1105
|
+
|
|
1013
1106
|
if attention_weight:
|
|
1014
|
-
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()]
|
|
1015
1110
|
past_weights = [item for sublist in past_weights for item in sublist]
|
|
1016
1111
|
|
|
1017
|
-
cum_sum = len(np.where(past_neighs==neighs_B[n])[0])
|
|
1018
|
-
neigh_dico.update({'cumulated_presence': cum_sum+1})
|
|
1019
|
-
|
|
1112
|
+
cum_sum = len(np.where(past_neighs == neighs_B[n])[0])
|
|
1113
|
+
neigh_dico.update({'cumulated_presence': cum_sum + 1})
|
|
1114
|
+
|
|
1020
1115
|
if attention_weight:
|
|
1021
|
-
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)])
|
|
1022
1118
|
neigh_dico.update({'cumulated_presence_weighted': cum_sum_weighted + weights[n_index]})
|
|
1023
1119
|
|
|
1024
1120
|
if symmetrize:
|
|
1025
1121
|
setB.at[index_B[n_index], neigh_col] = sym_neigh
|
|
1026
|
-
|
|
1122
|
+
|
|
1027
1123
|
neighs.append(neigh_dico)
|
|
1028
|
-
|
|
1124
|
+
|
|
1029
1125
|
setA.at[index_A[k], neigh_col] = neighs
|
|
1030
|
-
|
|
1126
|
+
|
|
1031
1127
|
return setA, setB
|
|
1032
1128
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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}):
|
|
1036
1137
|
"""
|
|
1037
1138
|
Computes neighborhood metrics for specified cell populations within a given position, based on distance criteria and additional parameters.
|
|
1038
1139
|
|
|
@@ -1070,12 +1171,12 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1070
1171
|
------
|
|
1071
1172
|
AssertionError
|
|
1072
1173
|
If the specified position path does not exist or if the number of distances and edge thresholds do not match.
|
|
1073
|
-
|
|
1174
|
+
|
|
1074
1175
|
"""
|
|
1075
1176
|
|
|
1076
|
-
pos = pos.replace('\\','/')
|
|
1177
|
+
pos = pos.replace('\\', '/')
|
|
1077
1178
|
pos = rf"{pos}"
|
|
1078
|
-
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.'
|
|
1079
1180
|
|
|
1080
1181
|
if isinstance(population, str):
|
|
1081
1182
|
population = [population, population]
|
|
@@ -1086,16 +1187,27 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1086
1187
|
theta_dist = [theta_dist]
|
|
1087
1188
|
|
|
1088
1189
|
if theta_dist is None:
|
|
1089
|
-
theta_dist = [0 for d in distance]
|
|
1090
|
-
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.'
|
|
1091
1192
|
|
|
1092
|
-
if population[0]==population[1]:
|
|
1193
|
+
if population[0] == population[1]:
|
|
1093
1194
|
neighborhood_kwargs.update({'mode': 'self'})
|
|
1094
|
-
if population[1]!=population[0]:
|
|
1195
|
+
if population[1] != population[0]:
|
|
1095
1196
|
neighborhood_kwargs.update({'mode': 'two-pop'})
|
|
1096
1197
|
|
|
1097
1198
|
df_A, path_A = get_position_table(pos, population=population[0], return_path=True)
|
|
1098
1199
|
df_B, path_B = get_position_table(pos, population=population[1], return_path=True)
|
|
1200
|
+
if df_A is None or df_B is None:
|
|
1201
|
+
return None
|
|
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)
|
|
1099
1211
|
|
|
1100
1212
|
df_A_pkl = get_position_pickle(pos, population=population[0], return_path=False)
|
|
1101
1213
|
df_B_pkl = get_position_pickle(pos, population=population[1], return_path=False)
|
|
@@ -1103,19 +1215,36 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1103
1215
|
if df_A_pkl is not None:
|
|
1104
1216
|
pkl_columns = np.array(df_A_pkl.columns)
|
|
1105
1217
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
1106
|
-
cols = list(pkl_columns[neigh_columns]) + ['
|
|
1218
|
+
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
1219
|
+
|
|
1220
|
+
id_col = extract_identity_col(df_A_pkl)
|
|
1221
|
+
cols.append(id_col)
|
|
1222
|
+
on_cols = [id_col, 'FRAME']
|
|
1223
|
+
|
|
1107
1224
|
print(f'Recover {cols} from the pickle file...')
|
|
1108
|
-
|
|
1109
|
-
|
|
1225
|
+
try:
|
|
1226
|
+
df_A = pd.merge(df_A, df_A_pkl.loc[:,cols], how="outer", on=on_cols)
|
|
1227
|
+
print(df_A.columns)
|
|
1228
|
+
except Exception as e:
|
|
1229
|
+
print(f'Failure to merge pickle and csv files: {e}')
|
|
1230
|
+
|
|
1110
1231
|
if df_B_pkl is not None and df_B is not None:
|
|
1111
1232
|
pkl_columns = np.array(df_B_pkl.columns)
|
|
1112
1233
|
neigh_columns = np.array([c.startswith('neighborhood') for c in pkl_columns])
|
|
1113
|
-
cols = list(pkl_columns[neigh_columns]) + ['
|
|
1234
|
+
cols = list(pkl_columns[neigh_columns]) + ['FRAME']
|
|
1235
|
+
|
|
1236
|
+
id_col = extract_identity_col(df_B_pkl)
|
|
1237
|
+
cols.append(id_col)
|
|
1238
|
+
on_cols = [id_col, 'FRAME']
|
|
1239
|
+
|
|
1114
1240
|
print(f'Recover {cols} from the pickle file...')
|
|
1115
|
-
|
|
1241
|
+
try:
|
|
1242
|
+
df_B = pd.merge(df_B, df_B_pkl.loc[:,cols], how="outer", on=on_cols)
|
|
1243
|
+
except Exception as e:
|
|
1244
|
+
print(f'Failure to merge pickle and csv files: {e}')
|
|
1116
1245
|
|
|
1117
1246
|
labelsA = locate_labels(pos, population=population[0])
|
|
1118
|
-
if population[1]==population[0]:
|
|
1247
|
+
if population[1] == population[0]:
|
|
1119
1248
|
labelsB = None
|
|
1120
1249
|
else:
|
|
1121
1250
|
labelsB = locate_labels(pos, population=population[1])
|
|
@@ -1124,18 +1253,18 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1124
1253
|
unwanted = df_A.columns[df_A.columns.str.contains('neighborhood')]
|
|
1125
1254
|
df_A = df_A.drop(columns=unwanted)
|
|
1126
1255
|
unwanted = df_B.columns[df_B.columns.str.contains('neighborhood')]
|
|
1127
|
-
df_B = df_B.drop(columns=unwanted)
|
|
1256
|
+
df_B = df_B.drop(columns=unwanted)
|
|
1128
1257
|
|
|
1129
1258
|
print(f"Distance: {distance} for mask contact")
|
|
1130
|
-
df_A, df_B = mask_contact_neighborhood(df_A, df_B, labelsA, labelsB, distance
|
|
1131
|
-
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:
|
|
1132
1261
|
return None
|
|
1133
1262
|
|
|
1134
|
-
for td,d in zip(theta_dist, distance):
|
|
1263
|
+
for td, d in zip(theta_dist, distance):
|
|
1135
1264
|
|
|
1136
|
-
if neighborhood_kwargs['mode']=='two-pop':
|
|
1265
|
+
if neighborhood_kwargs['mode'] == 'two-pop':
|
|
1137
1266
|
neigh_col = f'neighborhood_2_contact_{d}_px'
|
|
1138
|
-
elif neighborhood_kwargs['mode']=='self':
|
|
1267
|
+
elif neighborhood_kwargs['mode'] == 'self':
|
|
1139
1268
|
neigh_col = f'neighborhood_self_contact_{d}_px'
|
|
1140
1269
|
|
|
1141
1270
|
df_A.loc[df_A['class_id'].isnull(),neigh_col] = np.nan
|
|
@@ -1145,26 +1274,34 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
|
|
|
1145
1274
|
# df_A.loc[~edge_filter_A, neigh_col] = np.nan
|
|
1146
1275
|
# df_B.loc[~edge_filter_B, neigh_col] = np.nan
|
|
1147
1276
|
|
|
1148
|
-
df_A = compute_neighborhood_metrics(df_A, neigh_col, metrics=['inclusive','intermediate'],
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
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'))
|
|
1159
1296
|
|
|
1160
1297
|
unwanted = df_A.columns[df_A.columns.str.startswith('neighborhood_')]
|
|
1161
1298
|
df_A2 = df_A.drop(columns=unwanted)
|
|
1162
1299
|
df_A2.to_csv(path_A, index=False)
|
|
1163
1300
|
|
|
1164
|
-
if not population[0]==population[1]:
|
|
1301
|
+
if not population[0] == population[1]:
|
|
1165
1302
|
unwanted = df_B.columns[df_B.columns.str.startswith('neighborhood_')]
|
|
1166
1303
|
df_B_csv = df_B.drop(unwanted, axis=1, inplace=False)
|
|
1167
|
-
df_B_csv.to_csv(path_B,index=False)
|
|
1304
|
+
df_B_csv.to_csv(path_B, index=False)
|
|
1168
1305
|
|
|
1169
1306
|
if return_tables:
|
|
1170
1307
|
return df_A, df_B
|
|
@@ -1179,10 +1316,15 @@ if __name__ == "__main__":
|
|
|
1179
1316
|
|
|
1180
1317
|
print('None')
|
|
1181
1318
|
pos = "/home/torro/Documents/Experiments/NKratio_Exp/W5/500"
|
|
1182
|
-
|
|
1183
|
-
test,_ = compute_neighborhood_at_position(pos, [62], population=['targets','effectors'], theta_dist=None,
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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)
|
|
1187
1329
|
print(test.columns)
|
|
1188
1330
|
#print(segment(None,'test'))
|