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.
Files changed (42) hide show
  1. celldetective/__init__.py +2 -1
  2. celldetective/__main__.py +17 -0
  3. celldetective/extra_properties.py +62 -34
  4. celldetective/gui/__init__.py +1 -0
  5. celldetective/gui/analyze_block.py +2 -1
  6. celldetective/gui/classifier_widget.py +18 -10
  7. celldetective/gui/control_panel.py +57 -6
  8. celldetective/gui/layouts.py +14 -11
  9. celldetective/gui/neighborhood_options.py +21 -13
  10. celldetective/gui/plot_signals_ui.py +39 -11
  11. celldetective/gui/process_block.py +413 -95
  12. celldetective/gui/retrain_segmentation_model_options.py +17 -4
  13. celldetective/gui/retrain_signal_model_options.py +106 -6
  14. celldetective/gui/signal_annotator.py +110 -30
  15. celldetective/gui/signal_annotator2.py +2708 -0
  16. celldetective/gui/signal_annotator_options.py +3 -1
  17. celldetective/gui/survival_ui.py +15 -6
  18. celldetective/gui/tableUI.py +248 -43
  19. celldetective/io.py +598 -416
  20. celldetective/measure.py +919 -969
  21. celldetective/models/pair_signal_detection/blank +0 -0
  22. celldetective/neighborhood.py +482 -340
  23. celldetective/preprocessing.py +81 -61
  24. celldetective/relative_measurements.py +648 -0
  25. celldetective/scripts/analyze_signals.py +1 -1
  26. celldetective/scripts/measure_cells.py +28 -8
  27. celldetective/scripts/measure_relative.py +103 -0
  28. celldetective/scripts/segment_cells.py +5 -5
  29. celldetective/scripts/track_cells.py +4 -1
  30. celldetective/scripts/train_segmentation_model.py +23 -18
  31. celldetective/scripts/train_signal_model.py +33 -0
  32. celldetective/segmentation.py +67 -29
  33. celldetective/signals.py +402 -8
  34. celldetective/tracking.py +8 -2
  35. celldetective/utils.py +144 -12
  36. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/METADATA +8 -8
  37. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/RECORD +42 -38
  38. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/WHEEL +1 -1
  39. tests/test_segmentation.py +1 -1
  40. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/LICENSE +0 -0
  41. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/entry_points.txt +0 -0
  42. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/top_level.txt +0 -0
@@ -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 #already happened events become event
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 #already happened events become event
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 setB.loc[:,status[1]].values]
73
- status[1] = 'not_'+status[1]
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
- def compute_attention_weight(dist_matrix, cut_distance, opposite_cell_status, opposite_cell_ids, axis=1, include_dead_weight=True):
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./float(nbr_opposite)
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
- def distance_cut_neighborhood(setA, setB, distance, mode='two-pop', status=None, not_status_option=None, compute_cum_sum=True,
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', 'y': 'POSITION_Y'}):
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
- if not 'TRACK_ID' in list(s.columns):
194
- temp_column_labels.update({'track': 'ID'})
195
- compute_cum_sum = False # if no tracking data then cum_sum is not relevant
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(int)
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, include_dead_weight=include_dead_weight)
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./neigh_count
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]['track'] == 'TRACK_ID','The set B does not seem to contain tracked data. The cumulative time will be meaningless.'
287
- past_neighs = [[ll['id'] for ll in l] if len(l)>0 else [None] for l in setA.loc[(setA[cl[0]['track']]==ids_A[k])&(setA[cl[0]['time']]<=t), neigh_col].to_numpy()]
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[(setA[cl[0]['track']]==ids_A[k])&(setA[cl[0]['time']]<=t), neigh_col].to_numpy()]
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([w if l==neighs_B[n] else 0 for l,w in zip(past_neighs, past_weights)])
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
- def compute_neighborhood_at_position(pos, distance, population=['targets','effectors'], theta_dist=None, img_shape=(2048,2048), return_tables=False, clear_neigh=False, event_time_col=None,
311
- neighborhood_kwargs={'mode': 'two-pop','status': None, 'not_status_option': None,'include_dead_weight': True,"compute_cum_sum": False,"attention_weight": True, 'symmetrize': True}):
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]) + ['TRACK_ID','FRAME']
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
- df_A = pd.merge(df_A, df_A_pkl.loc[:,cols], how="outer", on=['TRACK_ID','FRAME'])
386
- print(df_A.columns)
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]) + ['TRACK_ID','FRAME']
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
- df_B = pd.merge(df_B, df_B_pkl.loc[:,cols], how="outer", on=['TRACK_ID','FRAME'])
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,**neighborhood_kwargs)
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
- df_B = compute_neighborhood_metrics(df_B, neigh_col, metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
419
-
420
- df_A = mean_neighborhood_before_event(df_A, neigh_col, event_time_col)
421
- df_A = mean_neighborhood_after_event(df_A, neigh_col, event_time_col)
422
-
423
- df_A.to_pickle(path_A.replace('.csv','.pkl'))
424
- if not population[0]==population[1]:
425
- df_B.to_pickle(path_B.replace('.csv','.pkl'))
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','TRACK_ID']
543
+ groupbycols = ['position']
489
544
  else:
490
- groupbycols = ['TRACK_ID']
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([c==1.0 if s==0.0 else False for c,s in zip(closest_at_t,status_at_t)])
558
- n_exclusive_status_1[t] = sum([c==1.0 if s==1.0 else False for c,s in zip(closest_at_t,status_at_t)])
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
- def mean_neighborhood_before_event(neigh_table, neigh_col, event_time_col, metrics=['inclusive','exclusive','intermediate']):
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','TRACK_ID']
674
+ groupbycols = ['position']
612
675
  else:
613
- groupbycols = ['TRACK_ID']
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[group['FRAME']<=event_time,'intermediate_count_s1_'+neigh_col].to_numpy()
639
- if len(valid_counts_intermediate[valid_counts_intermediate==valid_counts_intermediate])>0:
640
- neigh_table.loc[indices, f'mean_count_intermediate_{neigh_col}{suffix}'] = np.nanmean(valid_counts_intermediate)
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[group['FRAME']<=event_time,'inclusive_count_s1_'+neigh_col].to_numpy()
643
- if len(valid_counts_inclusive[valid_counts_inclusive==valid_counts_inclusive])>0:
644
- neigh_table.loc[indices, f'mean_count_inclusive_{neigh_col}{suffix}'] = np.nanmean(valid_counts_inclusive)
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[group['FRAME']<=event_time,'exclusive_count_s1_'+neigh_col].to_numpy()
647
- if len(valid_counts_exclusive[valid_counts_exclusive==valid_counts_exclusive])>0:
648
- neigh_table.loc[indices, f'mean_count_exclusive_{neigh_col}{suffix}'] = np.nanmean(valid_counts_exclusive)
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
- def mean_neighborhood_after_event(neigh_table, neigh_col, event_time_col, metrics=['inclusive','exclusive','intermediate']):
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','TRACK_ID']
760
+ groupbycols = ['position']
686
761
  else:
687
- groupbycols = ['TRACK_ID']
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 #neigh_table['FRAME'].max()
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[group['FRAME']>event_time,'intermediate_count_s1_'+neigh_col].to_numpy()
711
- if len(valid_counts_intermediate[valid_counts_intermediate==valid_counts_intermediate])>0:
712
- neigh_table.loc[indices, f'mean_count_intermediate_{neigh_col}{suffix}'] = np.nanmean(valid_counts_intermediate)
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[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(valid_counts_inclusive)
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[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(valid_counts_exclusive)
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])],axis=0)
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
- assert labels.ndim==2,"Wrong dimension for labels..."
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, not_status_option=None, compute_cum_sum=True,
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', 'mask_id': 'class_id'}):
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
- if not 'TRACK_ID' in list(s.columns):
849
- temp_column_labels.update({'track': 'ID'})
850
- compute_cum_sum = False # if no tracking data then cum_sum is not relevant
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(int)
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[:,:] = 1.0E06
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, include_dead_weight=include_dead_weight)
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./neigh_count
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]['track'] == 'TRACK_ID','The set B does not seem to contain tracked data. The cumulative time will be meaningless.'
1010
- past_neighs = [[ll['id'] for ll in l] if len(l)>0 else [None] for l in setA.loc[(setA[cl[0]['track']]==ids_A[k])&(setA[cl[0]['time']]<=t), neigh_col].to_numpy()]
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[(setA[cl[0]['track']]==ids_A[k])&(setA[cl[0]['time']]<=t), neigh_col].to_numpy()]
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([w if l==neighs_B[n] else 0 for l,w in zip(past_neighs, past_weights)])
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
- def compute_contact_neighborhood_at_position(pos, distance, population=['targets','effectors'], theta_dist=None, img_shape=(2048,2048), return_tables=False, clear_neigh=False, event_time_col=None,
1034
- neighborhood_kwargs={'mode': 'two-pop','status': None, 'not_status_option': None,'include_dead_weight': True,"compute_cum_sum": False,"attention_weight": True, 'symmetrize': True}):
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] #0.9*d
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]) + ['TRACK_ID','FRAME']
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
- df_A = pd.merge(df_A, df_A_pkl.loc[:,cols], how="outer", on=['TRACK_ID','FRAME'])
1109
- print(df_A.columns)
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]) + ['TRACK_ID','FRAME']
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
- df_B = pd.merge(df_B, df_B_pkl.loc[:,cols], how="outer", on=['TRACK_ID','FRAME'])
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,**neighborhood_kwargs)
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'], decompose_by_status=True)
1149
- if neighborhood_kwargs['symmetrize']:
1150
- df_B = compute_neighborhood_metrics(df_B, neigh_col, metrics=['inclusive','intermediate'], decompose_by_status=True)
1151
-
1152
- df_A = mean_neighborhood_before_event(df_A, neigh_col, event_time_col, metrics=['inclusive','intermediate'])
1153
- if event_time_col is not None:
1154
- df_A = mean_neighborhood_after_event(df_A, neigh_col, event_time_col, metrics=['inclusive','intermediate'])
1155
-
1156
- df_A.to_pickle(path_A.replace('.csv','.pkl'))
1157
- if not population[0]==population[1]:
1158
- df_B.to_pickle(path_B.replace('.csv','.pkl'))
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, img_shape=(2048,2048), return_tables=True, clear_neigh=True,
1184
- neighborhood_kwargs={'mode': 'two-pop','status': ['class', None],'not_status_option': [True, False],'include_dead_weight': True,"compute_cum_sum": False,"attention_weight": True, 'symmetrize': False})
1185
-
1186
- #test = compute_neighborhood_metrics(test, 'neighborhood_self_circle_150_px', metrics=['inclusive','exclusive','intermediate'], decompose_by_status=True)
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'))