@@ -63,6 +63,9 @@ def compute_agreement_score(num_matches, num1, num2):
6363def do_count_event (sorting ):
6464 """
6565 Count event for each units in a sorting.
66+
67+ Kept for backward compatibility sorting.count_num_spikes_per_unit() is doing the same.
68+
6669 Parameters
6770 ----------
6871 sorting: SortingExtractor
@@ -75,14 +78,7 @@ def do_count_event(sorting):
7578 """
7679 import pandas as pd
7780
78- unit_ids = sorting .get_unit_ids ()
79- ev_counts = np .zeros (len (unit_ids ), dtype = "int64" )
80- for segment_index in range (sorting .get_num_segments ()):
81- ev_counts += np .array (
82- [len (sorting .get_unit_spike_train (u , segment_index = segment_index )) for u in unit_ids ], dtype = "int64"
83- )
84- event_counts = pd .Series (ev_counts , index = unit_ids )
85- return event_counts
81+ return pd .Series (sorting .count_num_spikes_per_unit ())
8682
8783
8884def count_match_spikes (times1 , all_times2 , delta_frames ): # , event_counts1, event_counts2 unit2_ids,
@@ -133,11 +129,9 @@ def compute_matching_matrix(
133129 delta_frames ,
134130 ):
135131 """
136- Compute a matrix representing the matches between two spike trains.
137-
138- Given two spike trains, this function finds matching spikes based on a temporal proximity criterion
139- defined by `delta_frames`. The resulting matrix indicates the number of matches between units
140- in `spike_frames_train1` and `spike_frames_train2`.
132+ Internal function used by `make_match_count_matrix()`.
133+ This function is for one segment only.
134+ The loop over segment is done in `make_match_count_matrix()`
141135
142136 Parameters
143137 ----------
@@ -164,31 +158,9 @@ def compute_matching_matrix(
164158 A 2D numpy array of shape `(num_units_train1, num_units_train2)`. Each element `[i, j]` represents
165159 the count of matching spike pairs between unit `i` from `spike_frames_train1` and unit `j` from `spike_frames_train2`.
166160
167-
168- Notes
169- -----
170- This algorithm identifies matching spikes between two ordered spike trains.
171- By iterating through each spike in the first train, it compares them against spikes in the second train,
172- determining matches based on the two spikes frames being within `delta_frames` of each other.
173-
174- To avoid redundant comparisons the algorithm maintains a reference, `second_train_search_start `,
175- which signifies the minimal index in the second spike train that might match the upcoming spike
176- in the first train.
177-
178- The logic can be summarized as follows:
179- 1. Iterate through each spike in the first train
180- 2. For each spike, find the first match in the second train.
181- 3. Save the index of the first match as the new `second_train_search_start `
182- 3. For each match, find as many matches as possible from the first match onwards.
183-
184- An important condition here is that the same spike is not matched twice. This is managed by keeping track
185- of the last matched frame for each unit pair in `last_match_frame1` and `last_match_frame2`
186-
187- For more details on the rationale behind this approach, refer to the documentation of this module and/or
188- the metrics section in SpikeForest documentation.
189161 """
190162
191- matching_matrix = np .zeros ((num_units_train1 , num_units_train2 ), dtype = np .uint16 )
163+ matching_matrix = np .zeros ((num_units_train1 , num_units_train2 ), dtype = np .uint64 )
192164
193165 # Used to avoid the same spike matching twice
194166 last_match_frame1 = - np .ones_like (matching_matrix , dtype = np .int64 )
@@ -216,11 +188,11 @@ def compute_matching_matrix(
216188 unit_index1 , unit_index2 = unit_indices1 [index1 ], unit_indices2 [index2 ]
217189
218190 if (
219- frame1 != last_match_frame1 [unit_index1 , unit_index2 ]
220- and frame2 != last_match_frame2 [unit_index1 , unit_index2 ]
191+ index1 != last_match_frame1 [unit_index1 , unit_index2 ]
192+ and index2 != last_match_frame2 [unit_index1 , unit_index2 ]
221193 ):
222- last_match_frame1 [unit_index1 , unit_index2 ] = frame1
223- last_match_frame2 [unit_index1 , unit_index2 ] = frame2
194+ last_match_frame1 [unit_index1 , unit_index2 ] = index1
195+ last_match_frame2 [unit_index1 , unit_index2 ] = index2
224196
225197 matching_matrix [unit_index1 , unit_index2 ] += 1
226198
@@ -232,10 +204,65 @@ def compute_matching_matrix(
232204 return compute_matching_matrix
233205
234206
235- def make_match_count_matrix (sorting1 , sorting2 , delta_frames ):
207+ def make_match_count_matrix (sorting1 , sorting2 , delta_frames , ensure_symmetry = False ):
208+ """
209+ Computes a matrix representing the matches between two Sorting objects.
210+
211+ Given two spike trains, this function finds matching spikes based on a temporal proximity criterion
212+ defined by `delta_frames`. The resulting matrix indicates the number of matches between units
213+ in `spike_frames_train1` and `spike_frames_train2` for each pair of units.
214+
215+ Note that this algo is not symmetric and is biased with `sorting1` representing ground truth for the comparison
216+
217+ Parameters
218+ ----------
219+ sorting1 : Sorting
220+ An array of integer frame numbers corresponding to spike times for the first train. Must be in ascending order.
221+ sorting2 : Sorting
222+ An array of integer frame numbers corresponding to spike times for the second train. Must be in ascending order.
223+ delta_frames : int
224+ The inclusive upper limit on the frame difference for which two spikes are considered matching. That is
225+ if `abs(spike_frames_train1[i] - spike_frames_train2[j]) <= delta_frames` then the spikes at
226+ `spike_frames_train1[i]` and `spike_frames_train2[j]` are considered matching.
227+ ensure_symmetry: bool, default False
228+ If ensure_symmetry=True, then the algo is run two times by switching sorting1 and sorting2.
229+ And the minimum of the two results is taken.
230+ Returns
231+ -------
232+ matching_matrix : ndarray
233+ A 2D numpy array of shape `(num_units_train1, num_units_train2)`. Each element `[i, j]` represents
234+ the count of matching spike pairs between unit `i` from `spike_frames_train1` and unit `j` from `spike_frames_train2`.
235+
236+ Notes
237+ -----
238+ This algorithm identifies matching spikes between two ordered spike trains.
239+ By iterating through each spike in the first train, it compares them against spikes in the second train,
240+ determining matches based on the two spikes frames being within `delta_frames` of each other.
241+
242+ To avoid redundant comparisons the algorithm maintains a reference, `second_train_search_start `,
243+ which signifies the minimal index in the second spike train that might match the upcoming spike
244+ in the first train.
245+
246+ The logic can be summarized as follows:
247+ 1. Iterate through each spike in the first train
248+ 2. For each spike, find the first match in the second train.
249+ 3. Save the index of the first match as the new `second_train_search_start `
250+ 3. For each match, find as many matches as possible from the first match onwards.
251+
252+ An important condition here is that the same spike is not matched twice. This is managed by keeping track
253+ of the last matched frame for each unit pair in `last_match_frame1` and `last_match_frame2`
254+ There are corner cases where a spike can be counted twice in the spiketrain 2 if there are bouts of bursting activity
255+ (below delta_frames) in the spiketrain 1. To ensure that the number of matches does not exceed the number of spikes,
256+ we apply a final clip.
257+
258+
259+ For more details on the rationale behind this approach, refer to the documentation of this module and/or
260+ the metrics section in SpikeForest documentation.
261+ """
262+
236263 num_units_sorting1 = sorting1 .get_num_units ()
237264 num_units_sorting2 = sorting2 .get_num_units ()
238- matching_matrix = np .zeros ((num_units_sorting1 , num_units_sorting2 ), dtype = np .uint16 )
265+ matching_matrix = np .zeros ((num_units_sorting1 , num_units_sorting2 ), dtype = np .uint64 )
239266
240267 spike_vector1_segments = sorting1 .to_spike_vector (concatenated = False )
241268 spike_vector2_segments = sorting2 .to_spike_vector (concatenated = False )
@@ -257,7 +284,7 @@ def make_match_count_matrix(sorting1, sorting2, delta_frames):
257284 unit_indices1_sorted = spike_vector1 ["unit_index" ]
258285 unit_indices2_sorted = spike_vector2 ["unit_index" ]
259286
260- matching_matrix + = get_optimized_compute_matching_matrix ()(
287+ matching_matrix_seg = get_optimized_compute_matching_matrix ()(
261288 sample_frames1_sorted ,
262289 sample_frames2_sorted ,
263290 unit_indices1_sorted ,
@@ -267,6 +294,26 @@ def make_match_count_matrix(sorting1, sorting2, delta_frames):
267294 delta_frames ,
268295 )
269296
297+ if ensure_symmetry :
298+ matching_matrix_seg_switch = get_optimized_compute_matching_matrix ()(
299+ sample_frames2_sorted ,
300+ sample_frames1_sorted ,
301+ unit_indices2_sorted ,
302+ unit_indices1_sorted ,
303+ num_units_sorting2 ,
304+ num_units_sorting1 ,
305+ delta_frames ,
306+ )
307+ matching_matrix_seg = np .maximum (matching_matrix_seg , matching_matrix_seg_switch .T )
308+
309+ matching_matrix += matching_matrix_seg
310+
311+ # ensure the number of match do not exceed the number of spike in train 2
312+ # this is a simple way to handle corner cases for bursting in sorting1
313+ spike_count2 = np .array (list (sorting2 .count_num_spikes_per_unit ().values ()))
314+ spike_count2 = spike_count2 [np .newaxis , :]
315+ matching_matrix = np .clip (matching_matrix , None , spike_count2 )
316+
270317 # Build a data frame from the matching matrix
271318 import pandas as pd
272319
@@ -277,12 +324,12 @@ def make_match_count_matrix(sorting1, sorting2, delta_frames):
277324 return match_event_counts_df
278325
279326
280- def make_agreement_scores (sorting1 , sorting2 , delta_frames ):
327+ def make_agreement_scores (sorting1 , sorting2 , delta_frames , ensure_symmetry = True ):
281328 """
282329 Make the agreement matrix.
283330 No threshold (min_score) is applied at this step.
284331
285- Note : this computation is symmetric.
332+ Note : this computation is symmetric by default .
286333 Inverting sorting1 and sorting2 give the transposed matrix.
287334
288335 Parameters
@@ -293,7 +340,9 @@ def make_agreement_scores(sorting1, sorting2, delta_frames):
293340 The second sorting extractor
294341 delta_frames: int
295342 Number of frames to consider spikes coincident
296-
343+ ensure_symmetry: bool, default: True
344+ If ensure_symmetry is True, then the algo is run two times by switching sorting1 and sorting2.
345+ And the minimum of the two results is taken.
297346 Returns
298347 -------
299348 agreement_scores: array (float)
@@ -309,7 +358,7 @@ def make_agreement_scores(sorting1, sorting2, delta_frames):
309358 event_counts1 = pd .Series (ev_counts1 , index = unit1_ids )
310359 event_counts2 = pd .Series (ev_counts2 , index = unit2_ids )
311360
312- match_event_count = make_match_count_matrix (sorting1 , sorting2 , delta_frames )
361+ match_event_count = make_match_count_matrix (sorting1 , sorting2 , delta_frames , ensure_symmetry = ensure_symmetry )
313362
314363 agreement_scores = make_agreement_scores_from_count (match_event_count , event_counts1 , event_counts2 )
315364
0 commit comments