@@ -471,31 +471,16 @@ def build_neuropixels_probe(probe_part_number: str) -> Probe:
471471 lf_sample_frequency_hz = float (probe_spec_dict ["lf_sample_frequency_hz" ]),
472472 )
473473
474- # ===== 8. Add MUX table annotations =====
475- mux_table_string = probe_features ["z_mux_tables" ][probe_spec_dict ["mux_table_format_type" ]]
476- if mux_table_string is not None :
477- # Parse MUX table string: (num_adcs,num_channels_per_adc)(int int ...)(int int ...)...
478- adc_info = mux_table_string .split (")(" )[0 ]
479- split_mux = mux_table_string .split (")(" )[1 :]
480- num_adcs , num_channels_per_adc = map (int , adc_info [1 :].split ("," ))
481- adc_groups_list = [
482- np .array (each_mux .replace ("(" , "" ).replace (")" , "" ).split (" " )).astype ("int" ) for each_mux in split_mux
483- ]
484- mux_table = np .transpose (np .array (adc_groups_list ))
485-
486- # Map contacts to ADC groups and sample order
487- num_contacts = positions .shape [0 ]
488- adc_groups = np .zeros (num_contacts , dtype = "int64" )
489- adc_sample_order = np .zeros (num_contacts , dtype = "int64" )
490- for adc_index , adc_groups_per_adc in enumerate (mux_table ):
491- adc_groups_per_adc = adc_groups_per_adc [adc_groups_per_adc < num_contacts ]
492- adc_groups [adc_groups_per_adc ] = adc_index
493- adc_sample_order [adc_groups_per_adc ] = np .arange (len (adc_groups_per_adc ))
494-
495- probe .annotate (num_adcs = num_adcs )
496- probe .annotate (num_channels_per_adc = num_channels_per_adc )
497- probe .annotate_contacts (adc_group = adc_groups )
498- probe .annotate_contacts (adc_sample_order = adc_sample_order )
474+ # ===== 8. Store ADC sampling table =====
475+ # The ADC sampling table describes how readout channels map to ADCs, not electrodes.
476+ # Per-contact annotations (adc_group, adc_sample_order) can only be correctly
477+ # assigned when reading a recording with a known channel map (via read_spikeglx),
478+ # because the table indices are readout channel indices, not electrode indices.
479+ # We store the full table string so it's available after slicing.
480+ mux_table_format_type = probe_spec_dict ["mux_table_format_type" ]
481+ adc_sampling_table = probe_features ["z_mux_tables" ].get (mux_table_format_type )
482+ if adc_sampling_table is not None :
483+ probe .annotate (adc_sampling_table = adc_sampling_table )
499484
500485 return probe
501486
@@ -861,6 +846,38 @@ def read_spikeglx(file: str | Path) -> Probe:
861846 annotations [pi_field ] = values
862847 probe .annotate_contacts (** annotations )
863848
849+ # ===== 6b. Add ADC sampling annotations =====
850+ # The ADC sampling table describes which ADC samples each readout channel and in what order.
851+ # At this point, contacts are ordered by readout channel (0-383), so we can directly
852+ # apply the mapping. This must be done here (not in build_neuropixels_probe)
853+ # because the table indices are readout channel indices, not electrode indices.
854+ adc_sampling_table = probe .annotations .get ("adc_sampling_table" )
855+ if adc_sampling_table is not None :
856+ # Parse table string: (num_adcs,num_channels_per_adc)(ch ch ...)(ch ch ...)...
857+ adc_info = adc_sampling_table .split (")(" )[0 ]
858+ split_mux = adc_sampling_table .split (")(" )[1 :]
859+ num_adcs , num_channels_per_adc = map (int , adc_info [1 :].split ("," ))
860+ adc_groups_list = [
861+ np .array (each_mux .replace ("(" , "" ).replace (")" , "" ).split (" " )).astype ("int" )
862+ for each_mux in split_mux
863+ ]
864+ mux_table = np .transpose (np .array (adc_groups_list ))
865+
866+ # Map readout channels to ADC groups and sample order
867+ num_readout_channels = probe .get_contact_count ()
868+ adc_groups = np .zeros (num_readout_channels , dtype = "int64" )
869+ adc_sample_order = np .zeros (num_readout_channels , dtype = "int64" )
870+ for adc_index , channels_per_adc in enumerate (mux_table ):
871+ # Filter out placeholder values (e.g., 128 in mux_np1200 for unused slots)
872+ valid_channels = channels_per_adc [channels_per_adc < num_readout_channels ]
873+ adc_groups [valid_channels ] = adc_index
874+ adc_sample_order [valid_channels ] = np .arange (len (valid_channels ))
875+
876+ probe .annotate (num_adcs = num_adcs )
877+ probe .annotate (num_channels_per_adc = num_channels_per_adc )
878+ probe .annotate_contacts (adc_group = adc_groups )
879+ probe .annotate_contacts (adc_sample_order = adc_sample_order )
880+
864881 # ===== 7. Slice to saved channels (if subset was saved) =====
865882 # This is DIFFERENT from IMRO selection: IMRO selects which electrodes to acquire,
866883 # but SpikeGLX can optionally save only a subset of acquired channels to reduce file size.
0 commit comments