22
33import numpy as np
44
5- from spikeinterface .core .core_tools import define_function_from_class
5+ from ..core import BaseSorting
6+ from ..core .core_tools import define_function_from_class
67from .basecomparison import BasePairComparison , MixinSpikeTrainComparison , MixinTemplateComparison
78from .comparisontools import (
89 do_count_event ,
1213 do_confusion_matrix ,
1314 do_count_score ,
1415 compute_performance ,
16+ compute_distance_matrix ,
1517)
1618from ..postprocessing import compute_template_similarity_by_pair
1719
@@ -23,16 +25,17 @@ class BasePairSorterComparison(BasePairComparison, MixinSpikeTrainComparison):
2325
2426 def __init__ (
2527 self ,
26- sorting1 ,
27- sorting2 ,
28- sorting1_name = None ,
29- sorting2_name = None ,
30- delta_time = 0.4 ,
31- match_score = 0.5 ,
32- chance_score = 0.1 ,
33- ensure_symmetry = False ,
34- n_jobs = 1 ,
35- verbose = False ,
28+ sorting1 : BaseSorting ,
29+ sorting2 : BaseSorting ,
30+ sorting1_name : str | None = None ,
31+ sorting2_name : str | None = None ,
32+ delta_time : float = 0.4 ,
33+ match_score : float = 0.5 ,
34+ chance_score : float = 0.1 ,
35+ ensure_symmetry : bool = False ,
36+ agreement_method : str = "from_count" ,
37+ n_jobs : int = 1 ,
38+ verbose : bool = False ,
3639 ):
3740 if sorting1_name is None :
3841 sorting1_name = "sorting1"
@@ -59,6 +62,7 @@ def __init__(
5962 self .unit2_ids = self .sorting2 .get_unit_ids ()
6063
6164 self .ensure_symmetry = ensure_symmetry
65+ self .agreement_method = agreement_method
6266
6367 self ._do_agreement ()
6468 self ._do_matching ()
@@ -85,18 +89,30 @@ def _do_agreement(self):
8589
8690 # common to GroundTruthComparison and SymmetricSortingComparison
8791 # spike count for each spike train
88- self .event_counts1 = do_count_event (self .sorting1 )
89- self .event_counts2 = do_count_event (self .sorting2 )
92+ if self .agreement_method == "from_count" :
93+ self .event_counts1 = do_count_event (self .sorting1 )
94+ self .event_counts2 = do_count_event (self .sorting2 )
9095
91- # matrix of event match count for each pair
92- self .match_event_count = make_match_count_matrix (
93- self .sorting1 , self .sorting2 , self .delta_frames , ensure_symmetry = self .ensure_symmetry
94- )
96+ # matrix of event match count for each pair
97+ self .match_event_count = make_match_count_matrix (
98+ self .sorting1 , self .sorting2 , self .delta_frames , ensure_symmetry = self .ensure_symmetry
99+ )
95100
96- # agreement matrix score for each pair
97- self .agreement_scores = make_agreement_scores_from_count (
98- self .match_event_count , self .event_counts1 , self .event_counts2
99- )
101+ # agreement matrix score for each pair
102+ self .agreement_scores = make_agreement_scores_from_count (
103+ self .match_event_count , self .event_counts1 , self .event_counts2
104+ )
105+ elif self .agreement_method == "distance_function" :
106+ import pandas as pd
107+
108+ _ , agreement_matrix = compute_distance_matrix (self .sorting1 , self .sorting2 , self .delta_frames )
109+
110+ self .agreement_scores = pd .DataFrame (
111+ agreement_matrix , index = self .sorting1 .unit_ids , columns = self .sorting2 .unit_ids
112+ )
113+
114+ else :
115+ raise ValueError ("agreement_method must be 'from_count' or 'distance_matrix'" )
100116
101117
102118class SymmetricSortingComparison (BasePairSorterComparison ):
@@ -112,9 +128,9 @@ class SymmetricSortingComparison(BasePairSorterComparison):
112128
113129 Parameters
114130 ----------
115- sorting1 : SortingExtractor
131+ sorting1 : BaseSorting
116132 The first sorting for the comparison
117- sorting2 : SortingExtractor
133+ sorting2 : BaseSorting
118134 The second sorting for the comparison
119135 sorting1_name : str, default: None
120136 The name of sorter 1
@@ -126,6 +142,9 @@ class SymmetricSortingComparison(BasePairSorterComparison):
126142 Minimum agreement score to match units
127143 chance_score : float, default: 0.1
128144 Minimum agreement score to for a possible match
145+ agreement_method : "from_count" | "distance_function", default: "from_count"
146+ The method to compute agreement scores. The "from_count" method computes agreement scores from spike counts.
147+ The "distance_function" method computes agreement scores from spike time distance functions.
129148 n_jobs : int, default: -1
130149 Number of cores to use in parallel. Uses all available if -1
131150 verbose : bool, default: False
@@ -139,15 +158,16 @@ class SymmetricSortingComparison(BasePairSorterComparison):
139158
140159 def __init__ (
141160 self ,
142- sorting1 ,
143- sorting2 ,
144- sorting1_name = None ,
145- sorting2_name = None ,
146- delta_time = 0.4 ,
147- match_score = 0.5 ,
148- chance_score = 0.1 ,
149- n_jobs = - 1 ,
150- verbose = False ,
161+ sorting1 : BaseSorting ,
162+ sorting2 : BaseSorting ,
163+ sorting1_name : str | None = None ,
164+ sorting2_name : str | None = None ,
165+ delta_time : float = 0.4 ,
166+ match_score : float = 0.5 ,
167+ chance_score : float = 0.1 ,
168+ agreement_method : str = "from_count" ,
169+ n_jobs : int = - 1 ,
170+ verbose : bool = False ,
151171 ):
152172 BasePairSorterComparison .__init__ (
153173 self ,
@@ -159,6 +179,7 @@ def __init__(
159179 match_score = match_score ,
160180 chance_score = chance_score ,
161181 ensure_symmetry = True ,
182+ agreement_method = agreement_method ,
162183 n_jobs = n_jobs ,
163184 verbose = verbose ,
164185 )
@@ -167,10 +188,13 @@ def get_matching(self):
167188 return self .hungarian_match_12 , self .hungarian_match_21
168189
169190 def get_matching_event_count (self , unit1 , unit2 ):
170- if (unit1 is not None ) and (unit2 is not None ):
171- return self .match_event_count .at [unit1 , unit2 ]
191+ if self .agreement_method == "from_count" :
192+ if (unit1 is not None ) and (unit2 is not None ):
193+ return self .match_event_count .at [unit1 , unit2 ]
194+ else :
195+ raise Exception ("get_matching_event_count: unit1 and unit2 must not be None." )
172196 else :
173- raise Exception ("get_matching_event_count: unit1 and unit2 must not be None. " )
197+ raise Exception ("get_matching_event_count is valid only if agreement_method='from_count' " )
174198
175199 def get_best_unit_match1 (self , unit1 ):
176200 return self .best_match_12 [unit1 ]
@@ -215,9 +239,9 @@ class GroundTruthComparison(BasePairSorterComparison):
215239
216240 Parameters
217241 ----------
218- gt_sorting : SortingExtractor
242+ gt_sorting : BaseSorting
219243 The first sorting for the comparison
220- tested_sorting : SortingExtractor
244+ tested_sorting : BaseSorting
221245 The second sorting for the comparison
222246 gt_name : str, default: None
223247 The name of sorter 1
@@ -243,6 +267,9 @@ class GroundTruthComparison(BasePairSorterComparison):
243267 For instance, MEArec simulated dataset have exhaustive_gt=True
244268 match_mode : "hungarian" | "best", default: "hungarian"
245269 The method to match units
270+ agreement_method : "from_count" | "distance_function", default: "from_count"
271+ The method to compute agreement scores. The "from_count" method computes agreement scores from spike counts.
272+ The "distance_function" method computes agreement scores from spike time distance functions.
246273 n_jobs : int, default: -1
247274 Number of cores to use in parallel. Uses all available if -1
248275 compute_labels : bool, default: False
@@ -260,22 +287,23 @@ class GroundTruthComparison(BasePairSorterComparison):
260287
261288 def __init__ (
262289 self ,
263- gt_sorting ,
264- tested_sorting ,
265- gt_name = None ,
266- tested_name = None ,
267- delta_time = 0.4 ,
268- match_score = 0.5 ,
269- well_detected_score = 0.8 ,
270- redundant_score = 0.2 ,
271- overmerged_score = 0.2 ,
272- chance_score = 0.1 ,
273- exhaustive_gt = False ,
274- n_jobs = - 1 ,
275- match_mode = "hungarian" ,
276- compute_labels = False ,
277- compute_misclassifications = False ,
278- verbose = False ,
290+ gt_sorting : BaseSorting ,
291+ tested_sorting : BaseSorting ,
292+ gt_name : str | None = None ,
293+ tested_name : str | None = None ,
294+ delta_time : float = 0.4 ,
295+ match_score : float = 0.5 ,
296+ well_detected_score : float = 0.8 ,
297+ redundant_score : float = 0.2 ,
298+ overmerged_score : float = 0.2 ,
299+ chance_score : float = 0.1 ,
300+ exhaustive_gt : bool = False ,
301+ agreement_method : str = "from_count" ,
302+ n_jobs : int = - 1 ,
303+ match_mode : str = "hungarian" ,
304+ compute_labels : bool = False ,
305+ compute_misclassifications : bool = False ,
306+ verbose : bool = False ,
279307 ):
280308 import pandas as pd
281309
@@ -293,6 +321,7 @@ def __init__(
293321 match_score = match_score ,
294322 chance_score = chance_score ,
295323 ensure_symmetry = False ,
324+ agreement_method = agreement_method ,
296325 n_jobs = n_jobs ,
297326 verbose = verbose ,
298327 )
0 commit comments