celldetective 1.3.0.post1__py3-none-any.whl → 1.3.2__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 (36) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/events.py +88 -11
  3. celldetective/extra_properties.py +5 -1
  4. celldetective/gui/InitWindow.py +35 -9
  5. celldetective/gui/classifier_widget.py +99 -23
  6. celldetective/gui/control_panel.py +7 -1
  7. celldetective/gui/generic_signal_plot.py +161 -2
  8. celldetective/gui/gui_utils.py +90 -1
  9. celldetective/gui/layouts.py +128 -7
  10. celldetective/gui/measurement_options.py +3 -3
  11. celldetective/gui/plot_signals_ui.py +8 -3
  12. celldetective/gui/process_block.py +77 -32
  13. celldetective/gui/retrain_segmentation_model_options.py +24 -10
  14. celldetective/gui/signal_annotator.py +53 -26
  15. celldetective/gui/signal_annotator2.py +17 -30
  16. celldetective/gui/survival_ui.py +24 -3
  17. celldetective/gui/tableUI.py +300 -183
  18. celldetective/gui/viewers.py +263 -3
  19. celldetective/io.py +56 -3
  20. celldetective/links/zenodo.json +136 -123
  21. celldetective/measure.py +3 -0
  22. celldetective/models/tracking_configs/biased_motion.json +68 -0
  23. celldetective/models/tracking_configs/no_z_motion.json +202 -0
  24. celldetective/neighborhood.py +154 -69
  25. celldetective/preprocessing.py +172 -3
  26. celldetective/relative_measurements.py +128 -4
  27. celldetective/scripts/measure_cells.py +3 -3
  28. celldetective/signals.py +212 -215
  29. celldetective/tracking.py +7 -3
  30. celldetective/utils.py +22 -6
  31. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/METADATA +3 -3
  32. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/RECORD +36 -34
  33. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/WHEEL +1 -1
  34. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/LICENSE +0 -0
  35. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/entry_points.txt +0 -0
  36. {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,68 @@
1
+ {
2
+ "TrackerConfig":
3
+ {
4
+ "MotionModel":
5
+ {
6
+ "name": "biased_motion",
7
+ "dt": 1.0,
8
+ "measurements": 3,
9
+ "states": 6,
10
+ "accuracy": 7.5,
11
+ "prob_not_assign": 0.001,
12
+ "max_lost": 5,
13
+ "A": {
14
+ "matrix": [1,0,0,1,0,0,
15
+ 0,1,0,0,0,0,
16
+ 0,0,1,0,0,1,
17
+ 0,0,0,1,0,0,
18
+ 0,0,0,0,0,0,
19
+ 0,0,0,0,0,1]
20
+ },
21
+ "H": {
22
+ "matrix": [1,0,0,0,0,0,
23
+ 0,1,0,0,0,0,
24
+ 0,0,1,0,0,0]
25
+ },
26
+ "P": {
27
+ "sigma": 150.0,
28
+ "matrix": [0.1,0,0,0,0,0,
29
+ 0,0.1,0,0,0,0,
30
+ 0,0,0.1,0,0,0,
31
+ 0,0,0,1,0,0,
32
+ 0,0,0,0,1,0,
33
+ 0,0,0,0,0,1]
34
+ },
35
+ "G": {
36
+ "sigma": 15.0,
37
+ "matrix": [0.5,0.5,0.5,1,1,1]
38
+
39
+ },
40
+ "R": {
41
+ "sigma": 5.0,
42
+ "matrix": [1,0,0,
43
+ 0,1,0,
44
+ 0,0,1]
45
+ }
46
+ },
47
+ "ObjectModel":
48
+ {},
49
+ "HypothesisModel":
50
+ {
51
+ "name": "cell_hypothesis",
52
+ "hypotheses": ["P_FP", "P_init", "P_term", "P_branch","P_link", "P_merge"],
53
+ "lambda_time": 5.0,
54
+ "lambda_dist": 3.0,
55
+ "lambda_link": 10.0,
56
+ "lambda_branch": 1.0,
57
+ "eta": 1e-10,
58
+ "theta_dist": 20.0,
59
+ "theta_time": 5.0,
60
+ "dist_thresh": 35.0,
61
+ "time_thresh": 20.0,
62
+ "apop_thresh": 10,
63
+ "segmentation_miss_rate": 0.05,
64
+ "apoptosis_rate": 0.0005,
65
+ "relax": true
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,202 @@
1
+ {
2
+ "name": "no_z_motion",
3
+ "verbose": false,
4
+ "motion_model": {
5
+ "measurements": 3,
6
+ "states": 6,
7
+ "A": [
8
+ 1.0,
9
+ 0.0,
10
+ 0.0,
11
+ 1.0,
12
+ 0.0,
13
+ 0.0,
14
+ 0.0,
15
+ 1.0,
16
+ 0.0,
17
+ 0.0,
18
+ 1.0,
19
+ 0.0,
20
+ 0.0,
21
+ 0.0,
22
+ 0.0,
23
+ 0.0,
24
+ 0.0,
25
+ 0.0,
26
+ 0.0,
27
+ 0.0,
28
+ 0.0,
29
+ 1.0,
30
+ 0.0,
31
+ 0.0,
32
+ 0.0,
33
+ 0.0,
34
+ 0.0,
35
+ 0.0,
36
+ 1.0,
37
+ 0.0,
38
+ 0.0,
39
+ 0.0,
40
+ 0.0,
41
+ 0.0,
42
+ 0.0,
43
+ 0.0
44
+ ],
45
+ "H": [
46
+ 1.0,
47
+ 0.0,
48
+ 0.0,
49
+ 0.0,
50
+ 0.0,
51
+ 0.0,
52
+ 0.0,
53
+ 1.0,
54
+ 0.0,
55
+ 0.0,
56
+ 0.0,
57
+ 0.0,
58
+ 0.0,
59
+ 0.0,
60
+ 1.0,
61
+ 0.0,
62
+ 0.0,
63
+ 0.0
64
+ ],
65
+ "P": [
66
+ 30.0,
67
+ 0.0,
68
+ 0.0,
69
+ 0.0,
70
+ 0.0,
71
+ 0.0,
72
+ 0.0,
73
+ 30.0,
74
+ 0.0,
75
+ 0.0,
76
+ 0.0,
77
+ 0.0,
78
+ 0.0,
79
+ 0.0,
80
+ 30.0,
81
+ 0.0,
82
+ 0.0,
83
+ 0.0,
84
+ 0.0,
85
+ 0.0,
86
+ 0.0,
87
+ 300.0,
88
+ 0.0,
89
+ 0.0,
90
+ 0.0,
91
+ 0.0,
92
+ 0.0,
93
+ 0.0,
94
+ 300.0,
95
+ 0.0,
96
+ 0.0,
97
+ 0.0,
98
+ 0.0,
99
+ 0.0,
100
+ 0.0,
101
+ 300.0
102
+ ],
103
+ "R": [
104
+ 10.0,
105
+ 0.0,
106
+ 0.0,
107
+ 0.0,
108
+ 10.0,
109
+ 0.0,
110
+ 0.0,
111
+ 0.0,
112
+ 10.0
113
+ ],
114
+ "G": [
115
+ 15.0,
116
+ 15.0,
117
+ 15.0,
118
+ 30.0,
119
+ 30.0,
120
+ 30.0
121
+ ],
122
+ "Q": [
123
+ 225.0,
124
+ 225.0,
125
+ 225.0,
126
+ 450.0,
127
+ 450.0,
128
+ 450.0,
129
+ 225.0,
130
+ 225.0,
131
+ 225.0,
132
+ 450.0,
133
+ 450.0,
134
+ 450.0,
135
+ 225.0,
136
+ 225.0,
137
+ 225.0,
138
+ 450.0,
139
+ 450.0,
140
+ 450.0,
141
+ 450.0,
142
+ 450.0,
143
+ 450.0,
144
+ 900.0,
145
+ 900.0,
146
+ 900.0,
147
+ 450.0,
148
+ 450.0,
149
+ 450.0,
150
+ 900.0,
151
+ 900.0,
152
+ 900.0,
153
+ 450.0,
154
+ 450.0,
155
+ 450.0,
156
+ 900.0,
157
+ 900.0,
158
+ 900.0
159
+ ],
160
+ "dt": 1.0,
161
+ "accuracy": 5.0,
162
+ "max_lost": 5,
163
+ "prob_not_assign": 0.1,
164
+ "name": "ricm2"
165
+ },
166
+ "object_model": null,
167
+ "hypothesis_model": {
168
+ "hypotheses": [
169
+ "P_FP",
170
+ "P_init",
171
+ "P_term",
172
+ "P_link"
173
+ ],
174
+ "lambda_time": 5.0,
175
+ "lambda_dist": 3.0,
176
+ "lambda_link": 50.0,
177
+ "lambda_branch": 50.0,
178
+ "eta": 1e-10,
179
+ "theta_dist": 20.0,
180
+ "theta_time": 5.0,
181
+ "dist_thresh": 75.0,
182
+ "time_thresh": 5.0,
183
+ "apop_thresh": 5,
184
+ "segmentation_miss_rate": 0.1,
185
+ "apoptosis_rate": 0.001,
186
+ "relax": true,
187
+ "name": "ricm2"
188
+ },
189
+ "max_search_radius": 200.0,
190
+ "return_kalman": false,
191
+ "store_candidate_graph": false,
192
+ "volume": null,
193
+ "update_method": 0,
194
+ "optimizer_options": {
195
+ "tm_lim": 60000
196
+ },
197
+ "features": [],
198
+ "tracking_updates": [
199
+ 1
200
+ ],
201
+ "enable_optimisation": true
202
+ }
@@ -7,6 +7,8 @@ from celldetective.utils import contour_of_instance_segmentation, extract_identi
7
7
  from scipy.spatial.distance import cdist
8
8
  from celldetective.io import locate_labels, get_position_pickle, get_position_table
9
9
 
10
+ import matplotlib.pyplot as plt
11
+
10
12
  abs_path = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], 'celldetective'])
11
13
 
12
14
 
@@ -818,10 +820,7 @@ def contact_neighborhood(labelsA, labelsB=None, border=3, connectivity=2):
818
820
  if labelsB is not None:
819
821
  labelsB = labelsB.astype(float)
820
822
 
821
- print(f"Border = {border}")
822
-
823
823
  if border > 0:
824
- print(labelsA.shape, border * (-1))
825
824
  labelsA_edge = contour_of_instance_segmentation(label=labelsA, distance=border * (-1)).astype(float)
826
825
  labelsA[np.where(labelsA_edge > 0)] = labelsA_edge[np.where(labelsA_edge > 0)]
827
826
  if labelsB is not None:
@@ -851,6 +850,7 @@ def contact_neighborhood(labelsA, labelsB=None, border=3, connectivity=2):
851
850
  return neighs
852
851
 
853
852
  def merge_labels(labelsA, labelsB):
853
+
854
854
  labelsA = labelsA.astype(float)
855
855
  labelsB = labelsB.astype(float)
856
856
 
@@ -861,6 +861,7 @@ def merge_labels(labelsA, labelsB):
861
861
 
862
862
 
863
863
  def find_contact_neighbors(labels, connectivity=2):
864
+
864
865
  assert labels.ndim == 2, "Wrong dimension for labels..."
865
866
  g, nodes = pixel_graph(labels, mask=labels.astype(bool), connectivity=connectivity)
866
867
  g.eliminate_zeros()
@@ -906,6 +907,15 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
906
907
  """
907
908
 
908
909
  # Check live_status option
910
+ # if setA is not None:
911
+ # setA_id = extract_identity_col(setA)
912
+ # if setA_id=="TRACK_ID":
913
+ # setA = setA.loc[~setA['TRACK_ID'].isnull(),:].copy()
914
+ # if setB is not None:
915
+ # setB_id = extract_identity_col(setB)
916
+ # if setB_id=="TRACK_ID":
917
+ # setB = setB.loc[~setB['TRACK_ID'].isnull(),:].copy()
918
+
909
919
  if setA is not None and setB is not None:
910
920
  setA, setB, status = set_live_status(setA, setB, status, not_status_option)
911
921
  else:
@@ -915,6 +925,25 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
915
925
  if not isinstance(distance, list):
916
926
  distance = [distance]
917
927
 
928
+ cl = []
929
+ for s in [setA, setB]:
930
+
931
+ # Check whether data can be tracked
932
+ temp_column_labels = column_labels.copy()
933
+
934
+ id_col = extract_identity_col(s)
935
+ temp_column_labels.update({'track': id_col})
936
+ if id_col=='ID':
937
+ compute_cum_sum = False
938
+
939
+ cl.append(temp_column_labels)
940
+
941
+ setA = setA.loc[~setA[cl[0]['track']].isnull(),:].copy()
942
+ setB = setB.loc[~setB[cl[1]['track']].isnull(),:].copy()
943
+
944
+ if labelsB is None:
945
+ labelsB = [None] * len(labelsA)
946
+
918
947
  for d in distance:
919
948
  # loop over each provided distance
920
949
 
@@ -923,22 +952,11 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
923
952
  elif mode == 'self':
924
953
  neigh_col = f'neighborhood_self_contact_{d}_px'
925
954
 
926
- cl = []
927
- for s in [setA, setB]:
955
+ setA[neigh_col] = np.nan
956
+ setA[neigh_col] = setA[neigh_col].astype(object)
928
957
 
929
- # Check whether data can be tracked
930
- temp_column_labels = column_labels.copy()
931
-
932
- id_col = extract_identity_col(s)
933
- temp_column_labels.update({'track': id_col})
934
- if id_col=='ID':
935
- compute_cum_sum = False # if no tracking data then cum_sum is not relevant
936
- cl.append(temp_column_labels)
937
-
938
- # Remove nan tracks (cells that do not belong to a track)
939
- s[neigh_col] = np.nan
940
- s[neigh_col] = s[neigh_col].astype(object)
941
- s.dropna(subset=[cl[-1]['track']], inplace=True)
958
+ setB[neigh_col] = np.nan
959
+ setB[neigh_col] = setB[neigh_col].astype(object)
942
960
 
943
961
  # Loop over each available timestep
944
962
  timeline = np.unique(np.concatenate([setA[cl[0]['time']].to_numpy(), setB[cl[1]['time']].to_numpy()])).astype(
@@ -946,38 +964,34 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
946
964
  for t in tqdm(timeline):
947
965
 
948
966
  index_A = list(setA.loc[setA[cl[0]['time']] == t].index)
949
- coordinates_A = setA.loc[setA[cl[0]['time']] == t, [cl[0]['x'], cl[0]['y']]].to_numpy()
950
- ids_A = setA.loc[setA[cl[0]['time']] == t, cl[0]['track']].to_numpy()
951
- mask_ids_A = setA.loc[setA[cl[0]['time']] == t, cl[0]['mask_id']].to_numpy()
952
- status_A = setA.loc[setA[cl[0]['time']] == t, status[0]].to_numpy()
967
+ dataA = setA.loc[setA[cl[0]['time']] == t, [cl[0]['x'], cl[0]['y'], cl[0]['track'], cl[0]['mask_id'], status[0]]].to_numpy()
968
+ coordinates_A = dataA[:,[0,1]]; ids_A = dataA[:,2]; mask_ids_A = dataA[:,3]; status_A = dataA[:,4];
953
969
 
954
970
  index_B = list(setB.loc[setB[cl[1]['time']] == t].index)
955
- coordinates_B = setB.loc[setB[cl[1]['time']] == t, [cl[1]['x'], cl[1]['y']]].to_numpy()
956
- ids_B = setB.loc[setB[cl[1]['time']] == t, cl[1]['track']].to_numpy()
957
- mask_ids_B = setB.loc[setB[cl[1]['time']] == t, cl[1]['mask_id']].to_numpy()
958
- status_B = setB.loc[setB[cl[1]['time']] == t, status[1]].to_numpy()
971
+ dataB = setB.loc[setB[cl[1]['time']] == t, [cl[1]['x'], cl[1]['y'], cl[1]['track'], cl[1]['mask_id'], status[1]]].to_numpy()
972
+ coordinates_B = dataB[:,[0,1]]; ids_B = dataB[:,2]; mask_ids_B = dataB[:,3]; status_B = dataB[:,4]
959
973
 
960
- print(f"Frame {t}")
961
- print(f"{mask_ids_A=}", f"{mask_ids_B}")
962
-
963
- if len(ids_A) > 0 and len(ids_B) > 0:
974
+ if len(coordinates_A) > 0 and len(coordinates_B) > 0:
964
975
 
965
976
  # compute distance matrix
966
977
  dist_map = cdist(coordinates_A, coordinates_B, metric="euclidean")
978
+ intersection_map = np.zeros_like(dist_map).astype(float)
967
979
 
968
980
  # Do the mask contact computation
969
- if labelsB is not None:
970
- lblB = labelsB[t]
971
- else:
972
- lblB = labelsB
981
+ lblA = labelsA[t]
982
+ lblA = np.where(np.isin(lblA, mask_ids_A), lblA, 0.)
983
+
984
+ lblB = labelsB[t]
985
+ if lblB is not None:
986
+ lblB = np.where(np.isin(lblB, mask_ids_B), lblB, 0.)
973
987
 
974
- print(f"Distance {d} for contact as border")
975
- contact_pairs = contact_neighborhood(labelsA[t], labelsB=lblB, border=d, connectivity=2)
988
+ contact_pairs = contact_neighborhood(lblA, labelsB=lblB, border=d, connectivity=2)
976
989
 
977
- print(t, f"{np.unique(labelsA[t])=}")
978
- print(f"Frame {t}: found the following contact pairs: {contact_pairs}...")
979
990
  # Put infinite distance to all non-contact pairs (something like this)
980
991
  plot_map = False
992
+ flatA = lblA.flatten()
993
+ if lblB is not None:
994
+ flatB = lblB.flatten()
981
995
 
982
996
  if len(contact_pairs) > 0:
983
997
  mask = np.ones_like(dist_map).astype(bool)
@@ -985,48 +999,32 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
985
999
  indices_to_keep = []
986
1000
  for cp in contact_pairs:
987
1001
 
988
- if np.any(cp < 0):
989
- if cp[0] < 0:
990
- mask_A = cp[1]
991
- mask_B = np.abs(cp[0])
992
- else:
993
- mask_A = cp[0]
994
- mask_B = np.abs(cp[1])
995
- else:
996
- mask_A = cp[0]
997
- mask_B = cp[1]
998
-
999
- try:
1002
+ cp = np.abs(cp)
1003
+ mask_A, mask_B = cp
1004
+ idx_A = np.where(mask_ids_A == int(mask_A))[0][0]
1005
+ idx_B = np.where(mask_ids_B == int(mask_B))[0][0]
1006
+
1007
+ intersection = 0
1008
+ if lblB is not None:
1009
+ intersection = len(flatA[(flatA==int(mask_A))&(flatB==int(mask_B))])
1000
1010
 
1001
- idx_A = np.where(mask_ids_A == int(mask_A))[0][0]
1002
- idx_B = np.where(mask_ids_B == int(mask_B))[0][0]
1003
- print(idx_A, idx_B)
1004
- indices_to_keep.append([idx_A,idx_B])
1005
- except Exception as e:
1006
- print(f'{e} {t=} error something happened!!')
1007
- pass
1011
+ indices_to_keep.append([idx_A,idx_B, intersection])
1012
+ print(f'Ref cell #{ids_A[idx_A]} matched with neigh. cell #{ids_B[idx_B]}...')
1013
+ print(f'Computed intersection: {intersection} px...')
1008
1014
 
1009
- print(f'Indices to keep: {indices_to_keep}...')
1010
1015
  if len(indices_to_keep) > 0:
1011
1016
  indices_to_keep = np.array(indices_to_keep)
1012
1017
  mask[indices_to_keep[:, 0], indices_to_keep[:, 1]] = False
1013
1018
  if mode == 'self':
1014
1019
  mask[indices_to_keep[:, 1], indices_to_keep[:, 0]] = False
1015
1020
  dist_map[mask] = 1.0E06
1021
+ intersection_map[indices_to_keep[:,0], indices_to_keep[:,1]] = indices_to_keep[:,2]
1016
1022
  plot_map=True
1017
1023
  else:
1018
1024
  dist_map[:,:] = 1.0E06
1019
1025
  else:
1020
1026
  dist_map[:, :] = 1.0E06
1021
1027
 
1022
- # PROCEED all the same?? --> I guess so
1023
- # if plot_map:
1024
- # import matplotlib.pyplot as plt
1025
- # print(indices_to_keep)
1026
- # plt.imshow(dist_map)
1027
- # plt.pause(5)
1028
- # plt.close()
1029
-
1030
1028
  d_filter = 1.0E05
1031
1029
  if attention_weight:
1032
1030
  weights, closest_A = compute_attention_weight(dist_map, d_filter, status_A, ids_A, axis=1,
@@ -1036,11 +1034,14 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
1036
1034
  for k in range(dist_map.shape[0]):
1037
1035
 
1038
1036
  col = dist_map[k, :]
1037
+ col_inter = intersection_map[k, :]
1039
1038
  col[col == 0.] = 1.0E06
1040
1039
 
1041
1040
  neighs_B = np.array([ids_B[i] for i in np.where((col <= d_filter))[0]])
1042
1041
  status_neigh_B = np.array([status_B[i] for i in np.where((col <= d_filter))[0]])
1043
1042
  dist_B = [round(col[i], 2) for i in np.where((col <= d_filter))[0]]
1043
+ intersect_B = [round(col_inter[i], 2) for i in np.where((col <= d_filter))[0]]
1044
+
1044
1045
  if len(dist_B) > 0:
1045
1046
  closest_B_cell = neighs_B[np.argmin(dist_B)]
1046
1047
 
@@ -1080,14 +1081,14 @@ def mask_contact_neighborhood(setA, setB, labelsA, labelsB, distance, mode='two-
1080
1081
  else:
1081
1082
  closest_b = False
1082
1083
  if isinstance(sym_neigh, list):
1083
- sym_neigh.append({'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]})
1084
+ sym_neigh.append({'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k], 'intersection': intersect_B[n]})
1084
1085
  else:
1085
- sym_neigh = [{'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k]}]
1086
+ sym_neigh = [{'id': ids_A[k], 'distance': dist_B[n], 'status': status_A[k], 'intersection': intersect_B[n]}]
1086
1087
  if attention_weight:
1087
1088
  sym_neigh[-1].update({'weight': weight_A, 'closest': closest_b})
1088
1089
 
1089
1090
  # Write the minimum info about neighborhing cell B
1090
- neigh_dico = {'id': neighs_B[n], 'distance': dist_B[n], 'status': status_neigh_B[n]}
1091
+ neigh_dico = {'id': neighs_B[n], 'distance': dist_B[n], 'status': status_neigh_B[n], 'intersection': intersect_B[n]}
1091
1092
  if attention_weight:
1092
1093
  neigh_dico.update({'weight': weights[n_index], 'closest': closest})
1093
1094
 
@@ -1303,6 +1304,90 @@ def compute_contact_neighborhood_at_position(pos, distance, population=['targets
1303
1304
  return df_A, df_B
1304
1305
 
1305
1306
 
1307
+ def extract_neighborhood_in_pair_table(df, distance=None, reference_population="targets", neighbor_population="effectors", mode="circle", neighborhood_key=None, contact_only=True,):
1308
+
1309
+ """
1310
+ Extracts data from a pair table that matches specific neighborhood criteria based on reference and neighbor
1311
+ populations, distance, and mode of neighborhood computation (e.g., circular or contact-based).
1312
+
1313
+ Parameters
1314
+ ----------
1315
+ df : pandas.DataFrame
1316
+ DataFrame containing the pair table, which includes columns for 'reference_population', 'neighbor_population',
1317
+ and a column for neighborhood status.
1318
+ distance : int, optional
1319
+ Radius in pixels for neighborhood calculation, used only if `neighborhood_key` is not provided.
1320
+ reference_population : str, default="targets"
1321
+ The reference population to consider. Must be either "targets" or "effectors".
1322
+ neighbor_population : str, default="effectors"
1323
+ The neighbor population to consider. Must be either "targets" or "effectors", used only if `neighborhood_key` is not provided.
1324
+ mode : str, default="circle"
1325
+ Neighborhood computation mode. Options are "circle" for radius-based or "contact" for contact-based neighborhood, used only if `neighborhood_key` is not provided.
1326
+ neighborhood_key : str, optional
1327
+ A precomputed neighborhood key to identify specific neighborhoods. If provided, this key overrides `distance`,
1328
+ `mode`, and `neighbor_population`.
1329
+ contact_only : bool, default=True
1330
+ If True, only rows indicating contact with the neighbor population (status=1) are kept; if False, both
1331
+ contact (status=1) and no-contact (status=0) rows are included.
1332
+
1333
+ Returns
1334
+ -------
1335
+ pandas.DataFrame
1336
+ Filtered DataFrame containing rows that meet the specified neighborhood criteria.
1337
+
1338
+ Notes
1339
+ -----
1340
+ - When `neighborhood_key` is None, the neighborhood column is generated based on the provided `reference_population`,
1341
+ `neighbor_population`, `distance`, and `mode`.
1342
+ - The function uses `status_<neigh_col>` to filter rows based on `contact_only` criteria.
1343
+ - Ensures that `reference_population` and `neighbor_population` are valid inputs and consistent with the neighborhood
1344
+ mode and key.
1345
+
1346
+ Example
1347
+ -------
1348
+ >>> neighborhood_data = extract_neighborhood_in_pair_table(df, distance=50, reference_population="targets",
1349
+ neighbor_population="effectors", mode="circle")
1350
+ >>> neighborhood_data.head()
1351
+
1352
+ Raises
1353
+ ------
1354
+ AssertionError
1355
+ If `reference_population` or `neighbor_population` is not valid, or if the required neighborhood status
1356
+ column does not exist in `df`.
1357
+ """
1358
+
1359
+
1360
+ assert reference_population in ["targets", "effectors"], "Please set a valid reference population ('targets' or 'effectors')"
1361
+ if neighborhood_key is None:
1362
+ assert neighbor_population in ["targets", "effectors"], "Please set a valid neighbor population ('targets' or 'effectors')"
1363
+ assert mode in ["circle", "contact"], "Please set a valid neighborhood computation mode ('circle' or 'contact')"
1364
+ if reference_population==neighbor_population:
1365
+ type = "self"
1366
+ else:
1367
+ type = "2"
1368
+
1369
+ neigh_col = f"neighborhood_{type}_{mode}_{distance}_px"
1370
+ else:
1371
+ neigh_col = neighborhood_key.replace('status_','')
1372
+ if 'self' in neigh_col:
1373
+ neighbor_population = reference_population
1374
+ else:
1375
+ if reference_population=="effectors":
1376
+ neighbor_population=='targets'
1377
+ else:
1378
+ neighbor_population=='effectors'
1379
+
1380
+ assert "status_"+neigh_col in list(df.columns),"The selected neighborhood does not appear in the data..."
1381
+
1382
+ if contact_only:
1383
+ s_keep = [1]
1384
+ else:
1385
+ s_keep = [0,1]
1386
+
1387
+ data = df.loc[(df['reference_population']==reference_population)&(df['neighbor_population']==neighbor_population)&(df["status_"+neigh_col].isin(s_keep))]
1388
+
1389
+ return data
1390
+
1306
1391
 
1307
1392
  # def mask_intersection_neighborhood(setA, labelsA, setB, labelsB, threshold_iou=0.5, viewpoint='B'):
1308
1393
  # # do whatever to match objects in A and B