|
3 | 3 | import warnings |
4 | 4 | import numpy as np |
5 | 5 | from spikeinterface.core.sortinganalyzer import register_result_extension, AnalyzerExtension, SortingAnalyzer |
| 6 | +from copy import deepcopy |
6 | 7 |
|
7 | 8 | from spikeinterface.core.waveforms_extractor_backwards_compatibility import MockWaveformExtractor |
8 | 9 |
|
@@ -93,9 +94,98 @@ def _select_extension_data(self, unit_ids): |
93 | 94 | def _merge_extension_data( |
94 | 95 | self, merge_unit_groups, new_unit_ids, new_sorting_analyzer, censor_ms=None, verbose=False, **job_kwargs |
95 | 96 | ): |
96 | | - # recomputing correlogram is fast enough and much easier in this case |
97 | | - new_ccgs, new_bins = _compute_correlograms_on_sorting(new_sorting_analyzer.sorting, **self.params) |
98 | | - new_data = dict(ccgs=new_ccgs, bins=new_bins) |
| 97 | + """ |
| 98 | + When two units are merged, their cross-correlograms with other units become the sum |
| 99 | + of the previous cross-correlograms. More precisely, if units i and j get merged into |
| 100 | + unit k, then the new unit's cross-correlogram with any other unit l is: |
| 101 | + C_{k,l} = C_{i,l} + C_{j,l} |
| 102 | + C_{l,k} = C_{l,k} + C_{l,j} |
| 103 | + Here, we apply this formula to quickly compute correlograms for merged units. |
| 104 | + """ |
| 105 | + |
| 106 | + can_apply_soft_method = True |
| 107 | + if censor_ms is not None: |
| 108 | + # if censor_ms has no effect, can apply "soft" method. Check if any spikes have been removed |
| 109 | + for new_unit_id, merge_unit_group in zip(new_unit_ids, merge_unit_groups): |
| 110 | + |
| 111 | + num_segments = new_sorting_analyzer.get_num_segments() |
| 112 | + for segment_index in range(num_segments): |
| 113 | + |
| 114 | + merged_spike_train_length = len( |
| 115 | + new_sorting_analyzer.sorting.get_unit_spike_train(new_unit_id, segment_index=segment_index) |
| 116 | + ) |
| 117 | + |
| 118 | + old_spike_train_lengths = len( |
| 119 | + np.concatenate( |
| 120 | + [ |
| 121 | + self.sorting_analyzer.sorting.get_unit_spike_train(unit_id, segment_index=segment_index) |
| 122 | + for unit_id in merge_unit_group |
| 123 | + ] |
| 124 | + ) |
| 125 | + ) |
| 126 | + |
| 127 | + if merged_spike_train_length != old_spike_train_lengths: |
| 128 | + can_apply_soft_method = False |
| 129 | + break |
| 130 | + |
| 131 | + if can_apply_soft_method is False: |
| 132 | + new_ccgs, new_bins = _compute_correlograms_on_sorting(new_sorting_analyzer.sorting, **self.params) |
| 133 | + new_data = dict(ccgs=new_ccgs, bins=new_bins) |
| 134 | + else: |
| 135 | + |
| 136 | + # Make a transformation dict, which tells us how unit_indices from the |
| 137 | + # old to the new sorter are mapped. |
| 138 | + old_to_new_unit_index_map = {} |
| 139 | + for old_unit in self.sorting_analyzer.unit_ids: |
| 140 | + old_unit_index = self.sorting_analyzer.sorting.id_to_index(old_unit) |
| 141 | + unit_involved_in_merge = False |
| 142 | + for merge_unit_group, new_unit_id in zip(merge_unit_groups, new_unit_ids): |
| 143 | + new_unit_index = new_sorting_analyzer.sorting.id_to_index(new_unit_id) |
| 144 | + # check if the old_unit is involved in a merge |
| 145 | + if old_unit in merge_unit_group: |
| 146 | + # check if it is mapped to itself |
| 147 | + if old_unit == new_unit_id: |
| 148 | + old_to_new_unit_index_map[old_unit_index] = new_unit_index |
| 149 | + # or to a unit_id outwith the old ones |
| 150 | + elif new_unit_id not in self.sorting_analyzer.unit_ids: |
| 151 | + if new_unit_index not in old_to_new_unit_index_map.values(): |
| 152 | + old_to_new_unit_index_map[old_unit_index] = new_unit_index |
| 153 | + unit_involved_in_merge = True |
| 154 | + if unit_involved_in_merge is False: |
| 155 | + old_to_new_unit_index_map[old_unit_index] = new_sorting_analyzer.sorting.id_to_index(old_unit) |
| 156 | + |
| 157 | + need_to_append = False |
| 158 | + delete_from = 1 |
| 159 | + |
| 160 | + correlograms, new_bins = deepcopy(self.get_data()) |
| 161 | + |
| 162 | + for new_unit_id, merge_unit_group in zip(new_unit_ids, merge_unit_groups): |
| 163 | + |
| 164 | + merge_unit_group_indices = self.sorting_analyzer.sorting.ids_to_indices(merge_unit_group) |
| 165 | + |
| 166 | + # Sum unit rows of the correlogram matrix: C_{k,l} = C_{i,l} + C_{j,l} |
| 167 | + # and place this sum in all indices from the merge group |
| 168 | + new_col = np.sum(correlograms[merge_unit_group_indices, :, :], axis=0) |
| 169 | + # correlograms[merge_unit_group_indices[0], :, :] = new_col |
| 170 | + correlograms[merge_unit_group_indices, :, :] = new_col |
| 171 | + # correlograms[merge_unit_group_indices[1:], :, :] = 0 |
| 172 | + |
| 173 | + # Sum unit columns of the correlogram matrix: C_{l,k} = C_{l,i} + C_{l,j} |
| 174 | + # and put this sum in all indices from the merge group |
| 175 | + new_row = np.sum(correlograms[:, merge_unit_group_indices, :], axis=1) |
| 176 | + |
| 177 | + for merge_unit_group_index in merge_unit_group_indices: |
| 178 | + correlograms[:, merge_unit_group_index, :] = new_row |
| 179 | + |
| 180 | + new_correlograms = np.zeros( |
| 181 | + (len(new_sorting_analyzer.unit_ids), len(new_sorting_analyzer.unit_ids), correlograms.shape[2]) |
| 182 | + ) |
| 183 | + for old_index_1, new_index_1 in old_to_new_unit_index_map.items(): |
| 184 | + for old_index_2, new_index_2 in old_to_new_unit_index_map.items(): |
| 185 | + new_correlograms[new_index_1, new_index_2, :] = correlograms[old_index_1, old_index_2, :] |
| 186 | + new_correlograms[new_index_2, new_index_1, :] = correlograms[old_index_2, old_index_1, :] |
| 187 | + |
| 188 | + new_data = dict(ccgs=new_correlograms, bins=new_bins) |
99 | 189 | return new_data |
100 | 190 |
|
101 | 191 | def _run(self, verbose=False): |
|
0 commit comments