Skip to content

Refactor cmp#176

Merged
cboulay merged 6 commits into
masterfrom
refactor_cmp
Apr 25, 2026
Merged

Refactor cmp#176
cboulay merged 6 commits into
masterfrom
refactor_cmp

Conversation

@cboulay

@cboulay cboulay commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Real-world .cmp files break two assumptions our previous loader made: rows are not in (bank, electrode) order, and one device may carry multiple headstages whose label namespaces (elec1-1elec1-128) collide. This PR rewrites the loader around those facts.

  • bank_offset is gone. loadChannelMap now takes (filepath, start_chan=1, hs_id=0). The parser sorts rows by (bank, electrode) and assigns chan_id = start_chan + sorted_index, so each CMP describes one headstage applied to a contiguous channel range.
  • Labels are namespaced and pushed to the device. When hs_id != 0, every label is prefixed "hs{hs_id}-". The loader sends CHANSETLABEL per channel so the device persists the prefixed labels in chaninfo. hs_id=0 skips the prefix (sensible for single-headstage rigs).
  • Position overlay is keyed by chan_id. Both the CHANREP receive path and the post-load batch re-apply look up by absolute channel number; positions are re-applied on every device-echoed CHANREP so a device refresh can't clobber locally-supplied geometry.
  • Multi-headstage works: call loadChannelMap once per headstage with disjoint start_chan ranges (e.g. 1, 129, …). Subsequent calls merge into the overlay.

API changes (breaking)

Before After
C++ SdkSession::loadChannelMap(path, bank_offset=0) SdkSession::loadChannelMap(path, start_chan=1, hs_id=0)
C cbsdk_session_load_channel_map(s, path, bank_offset) cbsdk_session_load_channel_map(s, path, start_chan, hs_id)
Py Session.load_channel_map(path, bank_offset=0) Session.load_channel_map(path, start_chan=1, hs_id=0)

New: pycbsdk.cmp

  • parse_cmp(path, start_chan=1, hs_id=0) returns dict[chan_id, CmpEntry] mirroring the C++ parser.
  • CLI: python -m pycbsdk.cmp head1.cmp:1:1 head2.cmp:129:2 --device NSP applies one or more FILE:START_CHAN:HS_ID specs to a live device, or --dump prints the parsed entries without connecting.

New test fixture

tests/128ChannelManufacturerMapping.cmp — sanitized 128-channel sample from a real manufacturer CMP (description scrubbed of array IDs and serial). The integration tests now point at this file so they exercise out-of-order rows.

Test plan

  • C++ unit (cmp_parser_tests): 6 tests pass — sort order, start_chan offset, hs_id=0 no-prefix path, hs_id != 0 prefix path, missing-file error.
  • Python unit (tests/test_cmp.py): 12 tests pass — same coverage on the Python parser plus CLI spec parsing and --dump output.
  • pycbsdk integration (TestCMP, 7 tests, against nPlayServer): smoke load, error path, positions match parser, labels round-trip through device after sync(), hs_id change rewrites only the prefix, two-headstage start_chan layouts agree, CHANREP-echo doesn't erase the overlay.
  • CI: GitHub Actions runs the macOS + Linux integration jobs against nPlayServer playing back dnss256 with the new manufacturer fixture as NPLAY_CMP_FILE.

Migration notes

  • Single-headstage code: replace load_channel_map(path) (which used to apply bank_offset=0) with the same call — no change at the call site, but device labels are now untouched (no prefix when hs_id=0).
  • Multi-port / multi-headstage code: replace load_channel_map(path, bank_offset=4*N) with load_channel_map(path, start_chan=1+N*chans_per_hs, hs_id=N+1).

cboulay and others added 6 commits April 25, 2026 00:54
Real-world CMP files have out-of-order rows, and one device may carry
multiple headstages whose label namespaces collide. Replace the
(bank, term)-keyed overlay with one keyed by absolute chan_id:

  - parseCmpFile sorts rows by (bank, electrode) and assigns
    chan_id = start_chan + sorted_index
  - hs_id (default 0 = no prefix) prefixes labels "hs{hs_id}-..."
  - loadChannelMap pushes prefixed labels via CHANSETLABEL so they
    persist on the device; positions stay local but are re-applied
    on the CHANREP receive path so device echoes can't clobber them
  - bank_offset is gone — start_chan supersedes it

Updates the C API, C++ API, unit tests, and SDK/CAPI integration
tests in lockstep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bundled DefaultMapping files have rows already in (bank, elec)
order — useful for parser tests but not realistic. Real .cmp files
from the manufacturer come unsorted. Add a sanitized 128-channel
sample (description scrubbed of array IDs and serial number) and
point the integration tests at it so they exercise the sort and
chan_id assignment under genuine layouts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match cbsdk_session_load_channel_map's new (start_chan, hs_id)
parameters and drop bank_offset. The Python wrapper keeps the same
defaults as the C++ side (start_chan=1, hs_id=0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add pycbsdk.cmp with parse_cmp() (returns chan_id → CmpEntry) and a
CLI that connects to a device and applies one or more
FILE:START_CHAN:HS_ID specs. parse_cmp matches the C++ parser's
behavior: rows are sorted by (bank, electrode), assigned absolute
chan_ids from start_chan, and labels are prefixed "hs{hs_id}-..."
unless hs_id is 0 (no prefix).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TestCMP went from 3 mostly-smoke tests with one assertion to 7 with
real correctness checks: positions match the parser, labels round-
trip through the device (load → sync → read), prefix changes
rewrite labels, two-headstage start_chan layouts agree, and the
CHANREP-echo path doesn't erase the local overlay.

Replaces time.sleep() with session.sync() — the proper barrier for
the async CHANSETLABEL → CHANREP round-trip. Adds a
manufacturer_cmp_path fixture for the 128-ch sample.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cboulay cboulay merged commit 0bf178d into master Apr 25, 2026
16 checks passed
@cboulay cboulay deleted the refactor_cmp branch April 25, 2026 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant