@@ -108,12 +108,59 @@ def do_count_event(sorting):
108108 return event_counts
109109
110110
111- try :
112- from numba import jit
111+ def get_optimized_compute_dot_product ():
112+ """
113+ This function wraps around the compute_dot_product function,
114+ which uses numba for JIT compilation. It caches the compiled function
115+ for improved performance on subsequent calls.
116+ """
117+
118+ if hasattr (get_optimized_compute_dot_product , "_cached_function" ):
119+ return get_optimized_compute_dot_product ._cached_function
120+
121+ import numba
122+
123+ @numba .jit (nopython = True , nogil = True )
124+ def compute_dot_product (sample_frames , unit_indices , num_units_sorting1 , num_units_sorting2 , delta_frames ):
125+ """
126+ Compute the dot product matrix for two spike trains.
127+
128+ Note that the sample frames and unit indices must be sorted by sample frames.
129+ Also note that the unit_indices corresponding to the second spike train must be
130+ shifted by num_units_sorting1.
131+
132+ This creates a matrix that can be divided in four quadrants. Clokwise from top left:
133+ 1. The dot product of spikes in the first spike train with themselves.
134+ 2. The dot product of spikes in the first spike train with spikes in the second spike train.
135+ 3. The dot product of spikes in the second spike train with spikes in the first spike train.
136+ 4. The dot product of spikes in the second spike train with themselves.
137+
138+ Note that the norms of of the spike trains are the diagonals of the 1 and 4 quadrants.
139+ That is the only part of those quadrants that we need to calculate.
140+
141+ Parameters
142+ ----------
143+ sample_frames : ndarray
144+ An array of integer frame numbers corresponding to spike times.
145+ unit_indices : ndarray
146+ An array of integers where unit_indices[i] gives the unit index associated with
147+ the spike at sample_frames[i]. Note that the unit_indices of the second spike train
148+ need to be shifted by num_units_sorting1.
149+ num_units_sorting1 : int
150+ The total count of unique units in the first spike train.
151+ num_units_sorting2 : int
152+ The total count of unique units in the second spike train.
153+ delta_frames : int
154+ The inclusive upper limit on the frame difference for which two spikes are considered in proximity.
155+
156+ Returns
157+ -------
158+ dot_product_matrix : ndarray
159+ A 2D numpy array of shape (num_units_sorting1 + num_units_sorting2, num_units_sorting1 + num_units_sorting2).
160+ Each element [i, j] represents the accumulated proximity score between unit i and unit j.
161+ """
113162
114- @jit (nopython = True , nogil = True )
115- def calculate_distance_matrix (sample_frames , unit_indices , num_units_sorting1 , num_units_sorting2 , delta_frames ):
116- dot_prouct_matrix = np .zeros (
163+ dot_product_matrix = np .zeros (
117164 (num_units_sorting1 + num_units_sorting2 , num_units_sorting1 + num_units_sorting2 ),
118165 dtype = np .float32 ,
119166 )
@@ -122,96 +169,100 @@ def calculate_distance_matrix(sample_frames, unit_indices, num_units_sorting1, n
122169 num_samples = len (sample_frames )
123170 for index1 in range (num_samples ):
124171 frame1 = sample_frames [index1 ]
125- unit_index = unit_indices [index1 ]
172+ unit_index1 = unit_indices [index1 ]
126173
127174 for index2 in range (minimal_search , num_samples ):
128175 frame2 = sample_frames [index2 ]
176+
129177 if frame2 < frame1 - delta_frames :
130178 minimal_search += 1
131179 continue
132180 elif frame2 > frame1 + delta_frames :
133181 break
134182 else :
135183 unit_index2 = unit_indices [index2 ]
136- dot_prouct_matrix [ unit_index , unit_index2 ] += delta_frames - abs (frame1 - frame2 )
184+ dot_product_matrix [ unit_index1 , unit_index2 ] += delta_frames - abs (frame1 - frame2 )
137185
138- # Diagonal is dot product of a spike with itself, hence norm
139- within_train1_dot_product = dot_prouct_matrix [:num_units_sorting1 , :num_units_sorting1 ]
140- within_train2_dot_product = dot_prouct_matrix [num_units_sorting1 :, num_units_sorting1 :]
141- norm2 = np .diag (within_train2_dot_product )
142- norm1 = np .diag (within_train1_dot_product )
186+ return dot_product_matrix
143187
144- # Assuming norm1 and norm2 are 1D arrays
145- norm1_reshaped = norm1 .reshape ((- 1 , 1 )) # Reshape to a column vector
146- norm2_reshaped = norm2 .reshape ((1 , - 1 )) # Reshape to a row vector
188+ # Cache the compiled function
189+ get_optimized_compute_dot_product ._cached_function = compute_dot_product
147190
148- # Now perform the addition
149- norm_matrix = norm1_reshaped + norm2_reshaped
191+ return compute_dot_product
150192
151- # Dot product are the matches between units in train1 and train2
152- dot_product12 = dot_prouct_matrix [:num_units_sorting1 , num_units_sorting1 :]
153- dot_product21 = dot_prouct_matrix [num_units_sorting1 :, :num_units_sorting1 ]
154193
155- dot_product = (dot_product12 + dot_product21 .T ) / 2
194+ def compute_distance_matrix (sorting1 , sorting2 , delta_frames ):
195+ num_units_sorting1 = sorting1 .get_num_units ()
196+ num_units_sorting2 = sorting2 .get_num_units ()
197+ distance_matrix = np .zeros ((num_units_sorting1 , num_units_sorting2 ), dtype = np .float32 )
156198
157- distance_matrix = norm_matrix - 2 * dot_product
199+ spike_vector1_segments = sorting1 .to_spike_vector (concatenated = False )
200+ spike_vector2_segments = sorting2 .to_spike_vector (concatenated = False )
158201
159- return distance_matrix
202+ num_segments_sorting1 = sorting1 .get_num_segments ()
203+ num_segments_sorting2 = sorting2 .get_num_segments ()
204+ assert (
205+ num_segments_sorting1 == num_segments_sorting2
206+ ), "make_match_count_matrix : sorting1 and sorting2 must have the same segment number"
160207
161- def compute_distance_matrix (sorting1 , sorting2 , delta_frames ):
162- num_units_sorting1 = sorting1 .get_num_units ()
163- num_units_sorting2 = sorting2 .get_num_units ()
164- distance_matrix = np .zeros ((num_units_sorting1 , num_units_sorting2 ), dtype = np .float32 )
208+ optimized_compute_dot_product = get_optimized_compute_dot_product ()
165209
166- spike_vector1_segments = sorting1 .to_spike_vector (concatenated = False )
167- spike_vector2_segments = sorting2 .to_spike_vector (concatenated = False )
210+ for segment_index in range (num_segments_sorting1 ):
211+ spike_vector1 = spike_vector1_segments [segment_index ]
212+ spike_vector2 = spike_vector2_segments [segment_index ]
168213
169- num_segments_sorting1 = sorting1 .get_num_segments ()
170- num_segments_sorting2 = sorting2 .get_num_segments ()
171- assert (
172- num_segments_sorting1 == num_segments_sorting2
173- ), "make_match_count_matrix : sorting1 and sorting2 must have the same segment number"
214+ sample_frames1_sorted = spike_vector1 ["sample_index" ]
215+ sample_frames2_sorted = spike_vector2 ["sample_index" ]
174216
175- for segment_index in range (num_segments_sorting1 ):
176- spike_vector1 = spike_vector1_segments [segment_index ]
177- spike_vector2 = spike_vector2_segments [segment_index ]
217+ # Concatenate
218+ sample_frames = np .concatenate ((sample_frames1_sorted , sample_frames2_sorted ))
219+ unit_indices2 = spike_vector2 ["unit_index" ] + num_units_sorting1
220+ unit_indices = np .concatenate ((spike_vector1 ["unit_index" ], unit_indices2 ))
178221
179- sample_frames1_sorted = spike_vector1 ["sample_index" ]
180- sample_frames2_sorted = spike_vector2 ["sample_index" ]
222+ # Sort by sample frames
223+ indices = sample_frames .argsort ()
224+ sample_frames_sorted = sample_frames [indices ]
225+ unit_indices_sorted = unit_indices [indices ]
181226
182- # Concatenate
183- sample_frames = np .concatenate ((sample_frames1_sorted , sample_frames2_sorted ))
184- unit_indices2 = spike_vector2 ["unit_index" ] + num_units_sorting1
185- unit_indices = np .concatenate ((spike_vector1 ["unit_index" ], unit_indices2 ))
227+ dot_product_matrix = optimized_compute_dot_product (
228+ sample_frames_sorted ,
229+ unit_indices_sorted ,
230+ num_units_sorting1 ,
231+ num_units_sorting2 ,
232+ delta_frames ,
233+ )
186234
187- # Sort by sample frames
188- indices = sample_frames .argsort ()
189- sample_frames_sorted = sample_frames [indices ]
190- unit_indices_sorted = unit_indices [indices ]
235+ # Diagonal is dot product of a spike with itself, hence norm
236+ within_train1_dot_product = dot_product_matrix [:num_units_sorting1 , :num_units_sorting1 ]
237+ within_train2_dot_product = dot_product_matrix [num_units_sorting1 :, num_units_sorting1 :]
238+ norm2 = np .diag (within_train2_dot_product )
239+ norm1 = np .diag (within_train1_dot_product )
191240
192- distance_matrix_segment = calculate_distance_matrix (
193- sample_frames_sorted ,
194- unit_indices_sorted ,
195- num_units_sorting1 ,
196- num_units_sorting2 ,
197- delta_frames ,
198- )
241+ # Assuming norm1 and norm2 are 1D arrays
242+ norm1_reshaped = norm1 .reshape ((- 1 , 1 )) # Reshape to a column vector
243+ norm2_reshaped = norm2 .reshape ((1 , - 1 )) # Reshape to a row vector
244+
245+ # Now perform the addition
246+ norm_matrix = norm1_reshaped + norm2_reshaped
199247
200- distance_matrix += distance_matrix_segment
248+ # Dot product are the matches between units in train1 and train2
249+ dot_product12 = dot_product_matrix [:num_units_sorting1 , num_units_sorting1 :]
250+ dot_product21 = dot_product_matrix [num_units_sorting1 :, :num_units_sorting1 ]
251+
252+ dot_product = (dot_product12 + dot_product21 .T ) / 2
201253
202- distance_matrix = np .sqrt (distance_matrix )
203- # Build a data frame from the matching matrix
204- import pandas as pd
254+ distance_matrix += norm_matrix - 2 * dot_product
205255
206- unit_ids_of_sorting1 = sorting1 .get_unit_ids ()
207- unit_ids_of_sorting2 = sorting2 .get_unit_ids ()
256+ # distance_matrix = np.sqrt(distance_matrix)
257+ # # Build a data frame from the matching matrix
258+ # import pandas as pd
208259
209- match_event_counts_df = pd .DataFrame (distance_matrix , index = unit_ids_of_sorting1 , columns = unit_ids_of_sorting2 )
260+ # unit_ids_of_sorting1 = sorting1.get_unit_ids()
261+ # unit_ids_of_sorting2 = sorting2.get_unit_ids()
210262
211- return match_event_counts_df
263+ # match_event_counts_df = pd.DataFrame(distance_matrix, index=unit_ids_of_sorting1, columns=unit_ids_of_sorting2)
212264
213- except :
214- pass # numba not installed
265+ return distance_matrix , dot_product
215266
216267
217268def get_optimized_compute_matching_matrix ():
0 commit comments