The working version of the realtime decoder that worked in 2025 (with SC101,SC104 ...) - #15
The working version of the realtime decoder that worked in 2025 (with SC101,SC104 ...)#15donghoon-shin wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to bring back a known-working (2025-era) realtime decoder/stimulation workflow while making it more configurable across different lab setups (e.g., varying position/arm segment mappings and model buffer behavior).
Changes:
- Updated stimulation record labeling/buffer sizing and replay detection/threshold-update logic, including additional replay event heuristics.
- Modified encoder model loading/saving and mark buffering behavior, with additional early-save/downsampling logic.
- Added/adjusted debugging output in ripple processing and other runtime paths.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
realtime_decoder/stimulation.py |
Makes stimulation outputs and replay computations more configurable (arm IDs, region/base probability sums) and adjusts replay/threshold behavior. |
realtime_decoder/encoder_process.py |
Changes encoder mark buffering/model loading logic and adds early-save logic intended to keep saved models usable across runs/setups. |
realtime_decoder/ripple_process.py |
Adds additional debug visibility during ripple stats reset / filtering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| arm_ids = config['encoder']['position']['arm_ids'] | ||
|
|
||
| rls = ['region_box' if arm_id == 0 else f'region_arm{arm_id}' for arm_id in arm_ids] | ||
| region_labels = [f'{rl}_{x}' for x in range(num_decoders) for rl in rls] | ||
|
|
| ps_arm1 = prob[arm1_detection_start_bin:(arm1_end+1)].sum() #NOTE(DS): originally, 20-25 | ||
| ps_arm2 = prob[arm2_detection_start_bin:(arm2_end+1)].sum() #NOTE(DS): originally, 36-41 | ||
| ps_arm1_base = prob[arm1_start:arm1_detection_start_bin].sum() | ||
| ps_arm2_base = prob[arm1_start:arm2_detection_start_bin].sum() |
There was a problem hiding this comment.
@donghoon-shin you might want to double check this one as it may affect your results
There was a problem hiding this comment.
oops. the base probability might be saved wrong...
There was a problem hiding this comment.
ps_arm2_base was not used for detecting remote representation but corrected.
| potentially_duplicated_spikes = False | ||
| if num_unique == 2: | ||
| if np.abs(np.diff(np.unique(trodes_of_spike))) == 1: | ||
| potentially_duplicated_spikes = True |
| # dim 2 is 3 because 3 regions - box, arm1, arm2 | ||
| # _ps_ - shorthand for probability sum | ||
| self._arm_ps_buff = np.zeros((num_decoders, N, 3)) | ||
| self._arm_ps_buff = np.zeros((num_decoders, N, len(self._config['encoder']['position']['arm_ids']))) | ||
| self._region_ps_buff = np.zeros_like(self._arm_ps_buff) | ||
| self._region_ps_base_buff = np.zeros_like(self._arm_ps_buff) |
| print(f['mark_idx']) | ||
| if f['mark_idx'][0] < self._config['encoder']['bufsize']: #NOTE(DS): it seem to be a offset of 1. | ||
| self._mark_idx = f['mark_idx'][0]-1 | ||
| else: | ||
| self._mark_idx = self._config['encoder']['bufsize']-1 |
| if self._mark_idx == self._marks.shape[0]: | ||
| # NOTE(DS): This make buf_size meaningless | ||
| self._marks = np.vstack(( | ||
| self._marks, | ||
| np.zeros_like(self._marks) | ||
| )) | ||
| self._positions = np.hstack(( | ||
| self._positions, | ||
| np.zeros_like(self._positions) | ||
| )) | ||
|
|
||
| # this is where the mark_size increases over time | ||
| self._marks[self._mark_idx] = mark | ||
| self._positions[self._mark_idx] = self._position | ||
| self._mark_idx += 1 |
| if len(mark_vec) is not 8: | ||
| print(f"******************mark_vec: {len(mark_vec)}*******************") |
| num_signals = len(trodes) | ||
| print(f"num_signals: {len(trodes)}") | ||
| # stats for individual traces |
| def _update_scm_threshold(self): | ||
|
|
||
| #NOTE(DS): This is old version -- for SC92 that I determine the number of events/minutess | ||
| desired_number_of_scm = np.ceil(self._num_scm_each_arm_per_minute * self._elapsed_minutes) |
| n_spikes_currently_in_buffer = np.min([encoder._mark_idx,encoder._marks.shape[0]-1]) | ||
| print(f"n_spikes_current_in_buffer in encoder {encoder._trode}: {n_spikes_currently_in_buffer}") | ||
| n_spikes_capacity_buffer = self._config['encoder']['bufsize'] | ||
| if n_spikes_currently_in_buffer > n_spikes_capacity_buffer: | ||
| encoder._chosen_indices = np.sort(np.random.choice(n_spikes_currently_in_buffer,n_spikes_currently_in_buffer,replace=False)) | ||
| self.class_log.info( | ||
| f"in encoder {encoder._trode}: choosing {n_spikes_capacity_buffer} from {n_spikes_currently_in_buffer} spikes" | ||
| ) | ||
| encoder._marks = copy.deepcopy(encoder._marks[encoder._chosen_indices]) | ||
| encoder._positions = copy.deepcopy(encoder._positions[encoder._chosen_indices]) | ||
|
|
||
| else: | ||
| encoder._chosen_indices = np.arange(encoder._mark_idx) | ||
|
|
I made a private repo and was updating there. now we want to have a codebase that is actively updated by multiple people, i made a pr so that we can have a one version that can work across set ups