Skip to content

Commit ab5925b

Browse files
committed
profile more flexible version
1 parent 7d2f017 commit ab5925b

1 file changed

Lines changed: 153 additions & 18 deletions

File tree

src/spikeinterface/comparison/comparisontools.py

Lines changed: 153 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,9 @@ def compute_dot_product(
977977
The dot product gives a measure of the similarity between two spike trains. Each match is weighted by the
978978
delta_frames - abs(frame1 - frame2) where frame1 and frame2 are the frames of the matching spikes.
979979
980+
Note that the maximum weight of a match is delta_frames. This happens when the two spikes are exactly
981+
delta_frames apart. The minimum weight is 0 which happens when the two spikes are more than delta_frames appart.
982+
980983
Note the function assumes that the spike frames are sorted in ascending order.
981984
982985
Parameters
@@ -1115,10 +1118,53 @@ def compute_square_norm(sample_frames, unit_indices, num_units, delta_frames):
11151118
return compute_square_norm
11161119

11171120

1118-
def _compute_spike_vector_squared_norm(spike_vector_per_segment, num_units, delta_frames):
1121+
def _compute_spike_vector_squared_norm(
1122+
spike_vector_per_segment: list[np.ndarray],
1123+
num_units: int,
1124+
delta_frames: int,
1125+
) -> np.ndarray:
1126+
"""
1127+
Computes the squared norm of spike vectors for each unit across multiple segments.
1128+
1129+
This function calculates the squared norm for each unit in the provided spike vectors,
1130+
summing across different segments.
1131+
1132+
The norm is defined in the context of spike trains considered as box-car functions with
1133+
a specified width (delta_frames). The squared norm represents the integral of the squared spike train
1134+
when viewed as such a function.
1135+
1136+
The squared norm comprises two components:
1137+
1138+
||x||^2 = num_spikes * delta_frames + self_match_component
1139+
1140+
1. A sum of the number of spikes for a given unit multiplied by delta_frames, representing the total 'active'
1141+
duration of the spike train.
1142+
2. A weighted sum of 'self-matches' within spikes from the same unit, where each match's weight depends on
1143+
the proximity of the spikes.
1144+
1145+
If no two spikes in a train are closer than delta_frames apart, the squared norm simplifies to the number of
1146+
spikes multiplied by delta_frames: ||x||^2 = delta_frames * num_spikes.
1147+
1148+
1149+
Parameters
1150+
----------
1151+
spike_vector_per_segment : list of np.ndarray
1152+
A list containing spike vectors for each segment. Each spike vector is a structured numpy array with fields 'sample_index' and 'unit_index'.
1153+
num_units : int
1154+
The total number of units represented in the spike vectors.
1155+
delta_frames : int
1156+
The width of the box-car function, used in defining the norm.
1157+
1158+
Returns
1159+
-------
1160+
np.ndarray
1161+
A 1D numpy array of length `num_units`, where each entry represents the squared norm of the corresponding unit across all segments.
1162+
1163+
"""
11191164
compute_squared_norm = get_compute_square_norm_function()
11201165

11211166
squared_norm = np.zeros(num_units, dtype=np.uint64)
1167+
11221168
# Note that the squared norms are integrals and can be added over segments
11231169
for spike_vector in spike_vector_per_segment:
11241170
sample_frames = spike_vector["sample_index"]
@@ -1134,12 +1180,42 @@ def _compute_spike_vector_squared_norm(spike_vector_per_segment, num_units, delt
11341180

11351181

11361182
def _compute_spike_vector_dot_product(
1137-
spike_vector_per_segment1,
1138-
spike_vector_per_segment2,
1139-
num_units1,
1140-
num_units2,
1141-
delta_frames,
1142-
):
1183+
spike_vector_per_segment1: list[np.ndarray], # TODO Add a propert type to spike vector that we can reference
1184+
spike_vector_per_segment2: list[np.ndarray],
1185+
num_units1: int,
1186+
num_units2: int,
1187+
delta_frames: int,
1188+
) -> np.ndarray:
1189+
"""
1190+
This function calculates the dot product for each pair of units between two sets of spike trains,
1191+
summing the results across different segments.
1192+
1193+
The dot product gives a measure of the similarity between two spike trains. The dot product here is induced by the
1194+
L2 norm in the Hilbert space of the spikes viewed as a box-car functions with width delta frames. Each match is
1195+
weighted by the delta_frames - abs(frame1 - frame2) where frame1 and frame2 are the frames of the matching spikes.
1196+
1197+
Note that the maximum weight of a match is delta_frames. This happens when the two spikes are exactly
1198+
delta_frames apart. The minimum weight is 0 which happens when the two spikes are more than delta_frames appart.
1199+
1200+
1201+
Parameters
1202+
----------
1203+
spike_vector_per_segment1 : list of ndarray
1204+
A list of spike vectors for each segment of the first spike_vector.
1205+
spike_vector_per_segment2 : list of ndarray
1206+
A list of spike vectors for each segment of the second spike_vector.
1207+
num_units1 : int
1208+
The number of units in the first spike_vectors.
1209+
num_units2 : int
1210+
The number of units in the second spike_vectors.
1211+
delta_frames : int
1212+
The frame width to consider for the dot product calculation.
1213+
1214+
Returns
1215+
-------
1216+
dot_product_matrix : ndarray
1217+
A matrix containing the dot product for each pair of units between the two spike_vectors.
1218+
"""
11431219
dot_product_matrix = np.zeros((num_units1, num_units2), dtype=np.uint64)
11441220

11451221
compute_dot_product = get_compute_dot_product_function()
@@ -1165,7 +1241,31 @@ def _compute_spike_vector_dot_product(
11651241
return dot_product_matrix
11661242

11671243

1168-
def compute_distance_matrix(sorting1: BaseSorting, sorting2: BaseSorting, delta_frames: int):
1244+
def compute_distance_matrix(sorting1: BaseSorting, sorting2: BaseSorting, delta_frames: int) -> np.ndarray:
1245+
"""
1246+
Computes a distance matrix between two sorting objects
1247+
1248+
This function calculates the L2 distance matrix between the spike train corresponding to units of
1249+
of the sorting extractors.
1250+
1251+
Each spike is considered as a box-car function with width delta_frames. The distance between two units is the
1252+
L2 distance between the two spike trains viewed as box-car functions. The distance then can be interpreted as
1253+
the integral of the squared difference between the two spike trains.
1254+
1255+
Parameters
1256+
----------
1257+
sorting1 : BaseSorting
1258+
The first spike train set to compare.
1259+
sorting2 : BaseSorting
1260+
The second spike train set to compare.
1261+
delta_frames : int
1262+
The frame width to consider in distance calculations.
1263+
1264+
Returns
1265+
-------
1266+
distance_matrix : (num_units1, num_units2) ndarray (float)
1267+
A matrix representing the pairwise L2 distances between units of sorting objects.
1268+
"""
11691269
num_units1 = sorting1.get_num_units()
11701270
num_units2 = sorting2.get_num_units()
11711271

@@ -1198,7 +1298,44 @@ def compute_distance_matrix(sorting1: BaseSorting, sorting2: BaseSorting, delta_
11981298
return distance_metrix
11991299

12001300

1201-
def calculate_generalized_metrics(sorting1: BaseSorting, sorting2: BaseSorting, delta_frames: int) -> dict[np.ndarray]:
1301+
def calculate_generalized_comparison_metrics(
1302+
sorting1: BaseSorting, sorting2: BaseSorting, delta_frames: int
1303+
) -> dict[np.ndarray]:
1304+
"""
1305+
Calculates generalized metrics between two sorting objects.
1306+
1307+
This function computes several metrics, including generalized accuracy, recall, precision, and cosine similarity
1308+
between the spike trains of two sorting objects. The calculations are based on the dot product and squared norms
1309+
of the spike vectors, where spikes are viewed as box-car functions with a width of delta_frames.
1310+
1311+
The generalized accuracy is a measure of the overall match between two sets of spike trains. Generalized recall
1312+
and precision are useful in scenarios where one of the sortings is considered as ground truth, and the other is
1313+
being evaluated against it. Cosine similarity gives a normalized measure of similarity between two spike trains.
1314+
1315+
Parameters
1316+
----------
1317+
sorting1 : BaseSorting
1318+
The first set of spike trains, can be considered as the ground truth in recall calculation.
1319+
sorting2 : BaseSorting
1320+
The second set of spike trains, typically the set being evaluated.
1321+
delta_frames : int
1322+
The width of the box-car function, used in defining the spike train representation.
1323+
1324+
Returns
1325+
-------
1326+
dict of np.ndarray
1327+
A dictionary containing the computed metrics:
1328+
- 'accuracy': Generalized accuracy between the two sets of spike trains.
1329+
- 'recall': Generalized recall, assuming sorting1 as ground truth.
1330+
- 'precision': Generalized precision, evaluating sorting2 against sorting1.
1331+
- 'cosine_similarity': Cosine similarity between the spike trains of sorting1 and sorting2.
1332+
1333+
Notes
1334+
-----
1335+
- The metrics are calculated based on the dot product and squared norms of the spike trains, which are represented
1336+
as box-car functions.
1337+
- The function assumes that both sorting objects have the same number of segments.
1338+
"""
12021339
num_units1 = sorting1.get_num_units()
12031340
num_units2 = sorting2.get_num_units()
12041341

@@ -1211,8 +1348,8 @@ def calculate_generalized_metrics(sorting1: BaseSorting, sorting2: BaseSorting,
12111348
num_segments_sorting1 == num_segments_sorting2
12121349
), "make_match_count_matrix : sorting1 and sorting2 must have the same number of segments"
12131350

1214-
squared_norm_1 = _compute_spike_vector_squared_norm(spike_vector1_segments, num_units2, delta_frames)
1215-
squared_norm_2 = _compute_spike_vector_squared_norm(spike_vector2_segments, num_units2, delta_frames)
1351+
squared_norm1 = _compute_spike_vector_squared_norm(spike_vector1_segments, num_units2, delta_frames)
1352+
squared_norm2 = _compute_spike_vector_squared_norm(spike_vector2_segments, num_units2, delta_frames)
12161353

12171354
dot_product = _compute_spike_vector_dot_product(
12181355
spike_vector1_segments,
@@ -1222,15 +1359,13 @@ def calculate_generalized_metrics(sorting1: BaseSorting, sorting2: BaseSorting,
12221359
delta_frames,
12231360
)
12241361

1225-
generalized_accuracy = dot_product / (squared_norm_1 + squared_norm_2 - dot_product)
1226-
cosine_similarity = dot_product / np.sqrt(squared_norm_1 * squared_norm_2)
1362+
generalized_accuracy = dot_product / (squared_norm1 + squared_norm2 - dot_product)
1363+
cosine_similarity = dot_product / np.sqrt(squared_norm1 * squared_norm2)
12271364

1228-
generalized_recall = dot_product / squared_norm_1**2 # Assumes sorting1 is the ground truth
1229-
generalized_precision = dot_product / squared_norm_2**2 # Assumes sorting2 is the sorting that is being evaluated
1365+
generalized_recall = dot_product / squared_norm1 # Assumes sorting1 is the ground truth
1366+
generalized_precision = dot_product / squared_norm2 # Assumes sorting2 is the sorting that is being evaluated
12301367

1231-
# Note that the generalized and recall can be written in terms of the cosine similarity
1232-
# generalized_recall = cosine_similarity / (1 + cosine_similarity)
1233-
# generalized_precision = cosine_similarity / (1 + cosine_similarity)
1368+
# TODO: Maybe distance should be here? who wants a distance by itself?
12341369

12351370
metrics = dict(
12361371
accuracy=generalized_accuracy,

0 commit comments

Comments
 (0)