Skip to content

Remove legacy probe construction path for read_imro#410

Open
h-mayorquin wants to merge 13 commits intoSpikeInterface:mainfrom
h-mayorquin:remove_legacy_path_1
Open

Remove legacy probe construction path for read_imro#410
h-mayorquin wants to merge 13 commits intoSpikeInterface:mainfrom
h-mayorquin:remove_legacy_path_1

Conversation

@h-mayorquin
Copy link
Collaborator

@h-mayorquin h-mayorquin commented Mar 18, 2026

_read_imro_string was the last caller of _make_npx_probe_from_description, which builds probes from scratch using electrode positions, pitch, and stagger parameters. This PR rewrites _read_imro_string to use the catalogue-based approach (the same one read_spikeglx and read_openephys already use) and deletes _make_npx_probe_from_description.

The motivation is to have a single probe construction path. Keeping two independent implementations of geometry construction means they can diverge, and the catalogue-based path is the canonical one that produces contact_ids consistent across all readers.

All three readers now follow the same abstract pattern:

  1. Determine probe part number from the input format
  2. build_neuropixels_probe(part_number) to get the full catalogue probe
  3. Parse the format-specific electrode selection (IMRO table, XML CHANNELS/SELECTED_ELECTRODES)
  4. Build canonical contact IDs via _build_canonical_contact_id
  5. full_probe.get_slice(selected_indices) to slice to the active electrodes
  6. Annotate with format-specific metadata (IMRO fields, ADC sampling, settings channel keys)

The differences between readers are only in steps 1 and 3 (where the part number and electrode selection come from) and what extra metadata each format provides (saved channel subsets, serial numbers, Channel Map ordering).

Contributes to #405.

See #351 for related discussion.

@h-mayorquin h-mayorquin marked this pull request as ready for review March 18, 2026 20:34
values = tuple(map(int, field_values_str[1:].split(" ")))
for field, field_value in zip(fields, values):
contact_info[field].append(field_value)
elec_ids = imro_per_channel["electrode"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think imro_np1000 and imro_np1110 don't have electrode as a key (

"imro_np1000_elm_flds": "(channel bank ref_id ap_gain lf_gain ap_hipas_flt)",
)
which is what all bank-related logic below (deleted lines 613-621) refer to. So you need to keep that logic in. Annoyingly, we don't have test data for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bank-y logic should be added to read_spikeglx too, I think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

@h-mayorquin h-mayorquin Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Neuropixels 1.0 I codified the fallback in a new function _resolve_active_contacts_for_np1 that encapsulates the electrode = bank * 384 + channel logic. Both read_spikeglx and read_imro now call _get_active_contact_ids which resolves electrode IDs if missing and builds the canonical contact ID strings. This function dispatches to the appropriate resolver based on the IMRO fields present. The IMRO parsing itself stays in _parse_imro_string, which is now a pure parser that returns a dict of IMRO fields without any electrode resolution (the electrode computation that was previously inline there has been extracted into the resolver functions).

For NP1110 I went and checked how it is done in the C++ implementation (IMROTbl_T1110.cpp in the SpikeGLX repo). I threw an agent at it and had it implement the same logic here, which is now in _resolve_active_contacts_for_np1110. I added enough caveats in the function (a warning, a TODO, a comment explaining why we're mirroring the C++ naming) until we get real test data, but I think it should be OK to move forward.

I also inlined all the logic for building the probe into read_imro directly so it is even more like read_spikeglx, instead of relying on a private method that was doing most of the computation.

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.

3 participants