From 47ac78ba31a6e331d82f624c365abbeeb52e2bd4 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Wed, 18 Mar 2026 14:09:48 -0600 Subject: [PATCH 01/12] Remove legacy probe construction path for `read_imro` --- src/probeinterface/neuropixels_tools.py | 60 +++++++++---------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index ca186b9c..dda0a222 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -593,54 +593,38 @@ def _read_imro_string(imro_str: str, imDatPrb_pn: Optional[str] = None) -> Probe """ - probe_type_num_chans, *imro_table_values_list, _ = imro_str.strip().split(")") + # Extract probe_type from the IMRO header "(probe_type,num_chans)" + probe_type = imro_str.strip().split(")")[0].split(",")[0][1:] - # probe_type_num_chans looks like f"({probe_type},{num_chans}" - probe_type = probe_type_num_chans.split(",")[0][1:] + # Parse the IMRO table into per-channel data (same parser used by read_spikeglx) + imro_per_channel = _parse_imro_string(imro_str, imDatPrb_pn) - probe_features = _load_np_probe_features() - pt_metadata, fields, mux_info = get_probe_metadata_from_probe_features(probe_features, imDatPrb_pn) + # Build full catalogue probe and slice to active electrodes + full_probe = build_neuropixels_probe(probe_part_number=imDatPrb_pn) - # fields = probe_description["fields_in_imro_table"] - contact_info = {k: [] for k in fields} - for field_values_str in imro_table_values_list: # Imro table values look like '(value, value, value, ... ' - # Split them by space to get int('value'), int('value'), int('value'), ...) - 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"] + shank_ids = imro_per_channel.get("shank", [None] * len(elec_ids)) + active_contact_ids = [ + _build_canonical_contact_id(elec_id, shank_id) for shank_id, elec_id in zip(shank_ids, elec_ids) + ] - channel_ids = np.array(contact_info["channel"]) - if "electrode" in contact_info: - elec_ids = np.array(contact_info["electrode"]) - else: - if contact_info.get("bank") is not None: - bank_key = "bank" - elif contact_info.get("bank_mask") is not None: - bank_key = "bank_mask" - banks = np.array(contact_info[bank_key]) - elec_ids = banks * 384 + channel_ids - - if pt_metadata["num_shanks"] > 1: - shank_ids = np.array(contact_info["shank"]) - else: - shank_ids = None + contact_id_to_index = {cid: i for i, cid in enumerate(full_probe.contact_ids)} + selected_indices = np.array([contact_id_to_index[cid] for cid in active_contact_ids]) + probe = full_probe.get_slice(selected_indices) - probe = _make_npx_probe_from_description(pt_metadata, imDatPrb_pn, elec_ids, shank_ids, mux_info) + # ADC sampling annotations + adc_sampling_table = probe.annotations.get("adc_sampling_table") + _annotate_probe_with_adc_sampling_info(probe, adc_sampling_table) - # scalar annotations - probe.annotate( - probe_type=probe_type, - ) + # Scalar annotations + probe.annotate(probe_type=probe_type) - # vector annotations + # Vector annotations from IMRO fields vector_properties = ("channel", "bank", "bank_mask", "ref_id", "ap_gain", "lf_gain", "ap_hipas_flt") - vector_properties_available = {} - for k, v in contact_info.items(): - if (k in vector_properties) and (len(v) > 0): - # convert to ProbeInterface naming for backwards compatibility + for k, v in imro_per_channel.items(): + if k in vector_properties and len(v) > 0: vector_properties_available[imro_field_to_pi_field.get(k)] = v - probe.annotate_contacts(**vector_properties_available) return probe From 8ba7849f1627db7144113070ee20128b26d755b5 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Thu, 19 Mar 2026 12:32:02 -0600 Subject: [PATCH 02/12] add support for edge cases --- src/probeinterface/neuropixels_tools.py | 318 ++++++++++++------------ 1 file changed, 158 insertions(+), 160 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index dda0a222..62b4caad 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -212,7 +212,12 @@ def get_probe_contour_vertices(shank_width, tip_length, probe_length) -> list: def read_imro(file_path: Union[str, Path]) -> Probe: """ - Read probe position from the imro file used in input of SpikeGlx and Open-Ephys for neuropixels probes. + Read a Neuropixels probe from an IMRO (Imec ReadOut) table file. + + IMRO files (.imro) are used by SpikeGLX and Open Ephys to configure which electrodes + are recorded and their acquisition settings (gains, references, filters). This function + reads the file, determines the probe part number from the IMRO header, builds the full + catalogue probe, and slices it to the active electrodes specified in the table. Parameters ---------- @@ -221,10 +226,15 @@ def read_imro(file_path: Union[str, Path]) -> Probe: Returns ------- - probe : Probe object + probe : Probe + Probe object with geometry, contact annotations, and device channel mapping. + + See Also + -------- + https://billkarsh.github.io/SpikeGLX/help/imroTables/ """ - # the input is an imro file + # ===== 1. Read file and determine probe part number from IMRO header ===== meta_file = Path(file_path) assert meta_file.suffix == ".imro", "'file' should point to the .imro file" with meta_file.open(mode="r") as f: @@ -247,101 +257,39 @@ def read_imro(file_path: Union[str, Path]) -> Probe: if imDatPrb_type == probe_type: imDatPrb_pn = probe_part_number - return _read_imro_string(imro_str, imDatPrb_pn) - - -def _make_npx_probe_from_description(probe_description, model_name, elec_ids, shank_ids, mux_info=None) -> Probe: - # used by _read_imro_string and for generating the NP library - - # compute position - y_idx, x_idx = np.divmod(elec_ids, probe_description["cols_per_shank"]) - x_pitch = probe_description["electrode_pitch_horz_um"] - y_pitch = probe_description["electrode_pitch_vert_um"] - - raw_stagger = ( - probe_description["even_row_horz_offset_left_edge_to_leftmost_electrode_center_um"] - - probe_description["odd_row_horz_offset_left_edge_to_leftmost_electrode_center_um"] - ) - - stagger = np.mod(y_idx + 1, 2) * raw_stagger - x_pos = (x_idx * x_pitch + stagger).astype("float64") - y_pos = (y_idx * y_pitch).astype("float64") - - # if probe_description["shank_number"] > 1: - if shank_ids is not None: - # shank_ids = np.array(contact_info["shank_id"]) - shank_pitch = probe_description["shank_pitch_um"] - contact_ids = [f"s{shank_id}e{elec_id}" for shank_id, elec_id in zip(shank_ids, elec_ids)] - x_pos += np.array(shank_ids).astype(int) * shank_pitch - else: - # shank_ids = None - contact_ids = [f"e{elec_id}" for elec_id in elec_ids] - - positions = np.stack((x_pos, y_pos), axis=1) - - # construct Probe object - probe = Probe(ndim=2, si_units="um", model_name=model_name, manufacturer="imec") - probe.description = probe_description["description"] - probe.set_contacts( - positions=positions, - shapes="square", - shank_ids=shank_ids, - shape_params={"width": probe_description["electrode_size_horz_direction_um"]}, - ) + # ===== 2. Interpret IMRO table to extract recorded electrodes and acquisition settings ===== + imro_per_channel = _interpret_imro_string(imro_str, imDatPrb_pn) - probe.set_contact_ids(contact_ids) - - # Add planar contour - polygon = np.array( - get_probe_contour_vertices( - probe_description["shank_width_um"], probe_description["tip_length_um"], get_probe_length(model_name) - ) - ) - - contour = [] - shank_pitch = probe_description["shank_pitch_um"] - for shank_id in range(probe_description["num_shanks"]): - shank_shift = np.array([shank_pitch * shank_id, 0]) - contour += list(polygon + shank_shift) - - # final contour_shift - middle_of_bottommost_electrode_to_top_of_shank_tip = 11 - contour_shift = np.array( - [ - -probe_description["odd_row_horz_offset_left_edge_to_leftmost_electrode_center_um"], - -middle_of_bottommost_electrode_to_top_of_shank_tip, - ] - ) - contour = np.array(contour) + contour_shift - probe.set_planar_contour(contour) + # ===== 3. Build full probe with all possible contacts ===== + full_probe = build_neuropixels_probe(probe_part_number=imDatPrb_pn) - # shank tips : minimum of the polygon - shank_tips = [] - for shank_id in range(probe_description["num_shanks"]): - shank_shift = np.array([shank_pitch * shank_id, 0]) - shank_tip = np.array(polygon[2]) + contour_shift + shank_shift - shank_tips.append(shank_tip.tolist()) + # ===== 4. Build contact IDs for active electrodes ===== + elec_ids = imro_per_channel["electrode"] + shank_ids = imro_per_channel.get("shank", [None] * len(elec_ids)) + active_contact_ids = [ + _build_canonical_contact_id(elec_id, shank_id) for shank_id, elec_id in zip(shank_ids, elec_ids) + ] - probe.annotate(shank_tips=shank_tips) + # ===== 5. Slice full probe to active electrodes ===== + contact_id_to_index = {cid: i for i, cid in enumerate(full_probe.contact_ids)} + selected_indices = np.array([contact_id_to_index[cid] for cid in active_contact_ids]) + probe = full_probe.get_slice(selected_indices) - # wire it - probe.set_device_channel_indices(np.arange(positions.shape[0])) + # ===== 6. Annotate probe with recording-specific metadata ===== + adc_sampling_table = probe.annotations.get("adc_sampling_table") + _annotate_probe_with_adc_sampling_info(probe, adc_sampling_table) - # set other key metadata annotations - probe.annotate( - adc_bit_depth=int(probe_description["adc_bit_depth"]), - num_readout_channels=int(probe_description["num_readout_channels"]), - ap_sample_frequency_hz=float(probe_description["ap_sample_frequency_hz"]), - lf_sample_frequency_hz=float(probe_description["lf_sample_frequency_hz"]), - ) + # Scalar annotations + probe_type = imro_str.strip().split(")")[0].split(",")[0][1:] + probe.annotate(probe_type=probe_type) - # annotate with MUX table - if mux_info is not None: - # annotate each contact with its mux channel - num_adcs, num_channels_per_adc, adc_groups_array = make_mux_table_array(mux_info) - probe.annotate(num_adcs=num_adcs) - probe.annotate(num_channels_per_adc=num_channels_per_adc) - _annotate_contacts_from_mux_table(probe, adc_groups_array) + # Vector annotations from IMRO fields + vector_properties = ("channel", "bank", "bank_mask", "ref_id", "ap_gain", "lf_gain", "ap_hipas_flt") + vector_properties_available = {} + for k, v in imro_per_channel.items(): + if k in vector_properties and len(v) > 0: + vector_properties_available[imro_field_to_pi_field.get(k)] = v + probe.annotate_contacts(**vector_properties_available) return probe @@ -571,65 +519,6 @@ def _annotate_probe_with_adc_sampling_info(probe: Probe, adc_sampling_table: str _annotate_contacts_from_mux_table(probe, adc_groups_array) -def _read_imro_string(imro_str: str, imDatPrb_pn: Optional[str] = None) -> Probe: - """ - Parse the IMRO table when presented as a string and create a Probe object. - - Parameters - ---------- - imro_str : str - IMRO table as a string. - imDatPrb_pn : str, optional - Probe number, by default None. - - Returns - ------- - Probe - A Probe object built from the parsed IMRO table data. - - See Also - -------- - https://billkarsh.github.io/SpikeGLX/help/imroTables/ - - """ - - # Extract probe_type from the IMRO header "(probe_type,num_chans)" - probe_type = imro_str.strip().split(")")[0].split(",")[0][1:] - - # Parse the IMRO table into per-channel data (same parser used by read_spikeglx) - imro_per_channel = _parse_imro_string(imro_str, imDatPrb_pn) - - # Build full catalogue probe and slice to active electrodes - full_probe = build_neuropixels_probe(probe_part_number=imDatPrb_pn) - - elec_ids = imro_per_channel["electrode"] - shank_ids = imro_per_channel.get("shank", [None] * len(elec_ids)) - active_contact_ids = [ - _build_canonical_contact_id(elec_id, shank_id) for shank_id, elec_id in zip(shank_ids, elec_ids) - ] - - contact_id_to_index = {cid: i for i, cid in enumerate(full_probe.contact_ids)} - selected_indices = np.array([contact_id_to_index[cid] for cid in active_contact_ids]) - probe = full_probe.get_slice(selected_indices) - - # ADC sampling annotations - adc_sampling_table = probe.annotations.get("adc_sampling_table") - _annotate_probe_with_adc_sampling_info(probe, adc_sampling_table) - - # Scalar annotations - probe.annotate(probe_type=probe_type) - - # Vector annotations from IMRO fields - vector_properties = ("channel", "bank", "bank_mask", "ref_id", "ap_gain", "lf_gain", "ap_hipas_flt") - vector_properties_available = {} - for k, v in imro_per_channel.items(): - if k in vector_properties and len(v) > 0: - vector_properties_available[imro_field_to_pi_field.get(k)] = v - probe.annotate_contacts(**vector_properties_available) - - return probe - - def get_probe_metadata_from_probe_features(probe_features: dict, imDatPrb_pn: str): """ Parses the `probe_features` dict, to cast string to appropriate types @@ -781,7 +670,7 @@ def _build_canonical_contact_id(electrode_id: int, shank_id: int | None = None) return f"e{electrode_id}" -def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: +def _interpret_imro_string(imro_table_string: str, probe_part_number: str) -> dict: """ Parse IMRO (Imec ReadOut) table string into structured per-channel data. @@ -826,18 +715,127 @@ def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: for field, field_value in zip(imro_fields, values): imro_per_channel[field].append(field_value) - # Ensure "electrode" key always exists with physical electrode IDs - # Different probe types encode electrode selection differently + # Resolve electrode IDs for probe types whose IMRO format does not include them. + # NP2.x+ probes have "electrode" directly in the IMRO table. NP1.x probes encode + # electrode selection indirectly and need computation. + if "electrode" not in imro_per_channel: + _resolve_active_contacts_for_np1(imro_per_channel) if "electrode" not in imro_per_channel: - # NP1.0: Bank-based addressing (physical_electrode_id = bank * 384 + channel) - readout_channel_ids = np.array(imro_per_channel["channel"]) - bank_key = "bank" if "bank" in imro_per_channel else "bank_mask" - bank_indices = np.array(imro_per_channel[bank_key]) - imro_per_channel["electrode"] = (bank_indices * 384 + readout_channel_ids).tolist() + _resolve_active_contacts_for_np1110(imro_per_channel, imro_table_string) return imro_per_channel +def _resolve_active_contacts_for_np1(imro_per_channel: dict) -> None: + """ + Compute electrode IDs for NP 1.0-like probes that use simple bank addressing. + + These probes (IMRO types 0, 1020, 1030, 1100, 1120-1123, 1200, 1300) have + "channel" and "bank" fields in their IMRO table but no "electrode" field. + The electrode ID is computed as: electrode = bank * 384 + channel. + + Modifies imro_per_channel in place, adding the "electrode" key. + + Parameters + ---------- + imro_per_channel : dict + Parsed IMRO data from _interpret_imro_string. Modified in place. + """ + if "channel" not in imro_per_channel: + return + + readout_channel_ids = np.array(imro_per_channel["channel"]) + bank_key = "bank" if "bank" in imro_per_channel else "bank_mask" + bank_indices = np.array(imro_per_channel[bank_key]) + imro_per_channel["electrode"] = (bank_indices * 384 + readout_channel_ids).tolist() + + +def _resolve_active_contacts_for_np1110(imro_per_channel: dict, imro_table_string: str) -> None: + """ + Compute electrode IDs for NP1110 (UHD2 active) probes that use group-based addressing. + + NP1110 has 6144 electrodes in an 8x768 grid, 384 readout channels, 24 groups, and 16 banks. + The IMRO table has 24 per-group entries (group, bankA, bankB) and a header with col_mode. + Each group covers 16 channels. For each channel, the group index is deterministic, the + effective bank is selected from (bankA, bankB) based on col_mode, and the electrode ID + is computed from channel + bank using column/row lookup tables. + + Modifies imro_per_channel in place, adding the "electrode" key with 384 electrode IDs. + + Parameters + ---------- + imro_per_channel : dict + Parsed IMRO data with keys "group", "bankA", "bankB" (24 entries each). + imro_table_string : str + Raw IMRO table string, needed to extract col_mode from the header. + + References + ---------- + https://github.com/billkarsh/SpikeGLX/blob/51b96c70204c025748d69c9a588e07406728f9eb/Src-imro/IMROTbl_T1110.cpp + """ + if "group" not in imro_per_channel: + return + + # Extract col_mode from IMRO header: (type,col_mode,ref_id,ap_gain,lf_gain,ap_hipas_flt) + header_str = imro_table_string.strip().split(")")[0][1:] # remove leading "(" + header_fields = header_str.split(",") + col_mode = int(header_fields[1]) # 0=INNER, 1=OUTER, 2=ALL + + groups_bankA = imro_per_channel["bankA"] + groups_bankB = imro_per_channel["bankB"] + + col_tbl = [0, 3, 1, 2, 1, 2, 0, 3] + + def _grp_idx(ch): + return 2 * ((ch % 384) // 32) + ((ch % 384) & 1) + + def _col(ch, bank): + grp_index = _grp_idx(ch) + grp_col = col_tbl[4 * (bank & 1) + (grp_index % 4)] + crossed = (bank // 4) & 1 + ingrp_col = ((((ch % 64) % 32) // 2) & 1) ^ crossed + if ch & 1: + return 2 * grp_col + (1 - ingrp_col) + else: + return 2 * grp_col + ingrp_col + + def _row(ch, bank): + grp_index = _grp_idx(ch) + grp_row = grp_index // 4 + ingrp_row = ((ch % 64) % 32) // 4 + if ch & 1: + b0_row = 8 * grp_row + (7 - ingrp_row) + else: + b0_row = 8 * grp_row + ingrp_row + return 48 * bank + b0_row + + def _bank_for_channel(ch, bankA, bankB): + if col_mode == 2: # ALL + return bankA + # INNER (0) or OUTER (1): choose bankA or bankB based on column position + c = _col(ch, bankA) + if c < 4: + use_bankA = (c % 2 == 0) if col_mode == 1 else (c % 2 == 1) + else: + use_bankA = (c % 2 == 1) if col_mode == 1 else (c % 2 == 0) + return bankA if use_bankA else bankB + + electrode_ids = [] + for ch in range(384): + grp = _grp_idx(ch) + bankA = groups_bankA[grp] + bankB = groups_bankB[grp] + bank = _bank_for_channel(ch, bankA, bankB) + row = _row(ch, bank) + col = _col(ch, bank) + electrode_ids.append(8 * row + col) + + imro_per_channel["electrode"] = electrode_ids + # Also add the "channel" key (0-383) since the IMRO entries are per-group, not per-channel + imro_per_channel["channel"] = list(range(384)) + + + def read_spikeglx(file: str | Path) -> Probe: """ Read probe geometry and configuration from a SpikeGLX metadata file. @@ -888,7 +886,7 @@ def read_spikeglx(file: str | Path) -> Probe: # Specifies which electrodes were selected for recording (e.g., 384 of 960) plus their # acquisition settings (gains, references, filters). See: https://billkarsh.github.io/SpikeGLX/help/imroTables/ imro_table_string = meta["imroTbl"] - imro_per_channel = _parse_imro_string(imro_table_string, imDatPrb_pn) + imro_per_channel = _interpret_imro_string(imro_table_string, imDatPrb_pn) # ===== 4. Build contact IDs for active electrodes ===== # Convert physical electrode IDs to probeinterface canonical contact ID strings From 9e3fa8ae7ab215f505621d80403e8f4feb8ce508 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:32:16 +0000 Subject: [PATCH 03/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/probeinterface/neuropixels_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index 62b4caad..db7b5566 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -835,7 +835,6 @@ def _bank_for_channel(ch, bankA, bankB): imro_per_channel["channel"] = list(range(384)) - def read_spikeglx(file: str | Path) -> Probe: """ Read probe geometry and configuration from a SpikeGLX metadata file. From 355754f49ac4051052f0a4f2761fca7ca17c8591 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Thu, 19 Mar 2026 12:41:08 -0600 Subject: [PATCH 04/12] done, comments and warning --- src/probeinterface/neuropixels_tools.py | 56 +++++++++++++++---------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index 62b4caad..289eae34 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -715,7 +715,7 @@ def _interpret_imro_string(imro_table_string: str, probe_part_number: str) -> di for field, field_value in zip(imro_fields, values): imro_per_channel[field].append(field_value) - # Resolve electrode IDs for probe types whose IMRO format does not include them. + # Resolve activate electrodes (i.e. `electrodes` entry) for probe types whose IMRO format does not include them. # NP2.x+ probes have "electrode" directly in the IMRO table. NP1.x probes encode # electrode selection indirectly and need computation. if "electrode" not in imro_per_channel: @@ -776,6 +776,17 @@ def _resolve_active_contacts_for_np1110(imro_per_channel: dict, imro_table_strin if "group" not in imro_per_channel: return + # TODO: Remove this warning once we have test data for NP1110 recordings. + warnings.warn( + "NP1110 (Neuropixels 1.0 UHD2 active) support is experimental. " + "The active electrode selection logic is translated directly from SpikeGLX " + "(https://github.com/billkarsh/SpikeGLX, Src-imro/IMROTbl_T1110.cpp) but has not " + "been validated against real NP1110 recordings. Please double-check the electrode " + "selection and report any issues at https://github.com/SpikeInterface/probeinterface/issues", + UserWarning, + stacklevel=3, # Points to read_imro / read_spikeglx (caller of _interpret_imro_string) + ) + # Extract col_mode from IMRO header: (type,col_mode,ref_id,ap_gain,lf_gain,ap_hipas_flt) header_str = imro_table_string.strip().split(")")[0][1:] # remove leading "(" header_fields = header_str.split(",") @@ -784,14 +795,17 @@ def _resolve_active_contacts_for_np1110(imro_per_channel: dict, imro_table_strin groups_bankA = imro_per_channel["bankA"] groups_bankB = imro_per_channel["bankB"] + # With pain in my heart, I am following here the C++ convention of terse naming from the + # original SpikeGLX implementation (IMROTbl_T1110.cpp). The purpose is to make it easy to + # spot differences when comparing against the original code, until we have real NP1110 test + # data to validate against and feel comfortable (if ever) renaming to our own conventions. col_tbl = [0, 3, 1, 2, 1, 2, 0, 3] - def _grp_idx(ch): + def grpIdx(ch): return 2 * ((ch % 384) // 32) + ((ch % 384) & 1) - def _col(ch, bank): - grp_index = _grp_idx(ch) - grp_col = col_tbl[4 * (bank & 1) + (grp_index % 4)] + def col(ch, bank): + grp_col = col_tbl[4 * (bank & 1) + (grpIdx(ch) % 4)] crossed = (bank // 4) & 1 ingrp_col = ((((ch % 64) % 32) // 2) & 1) ^ crossed if ch & 1: @@ -799,9 +813,8 @@ def _col(ch, bank): else: return 2 * grp_col + ingrp_col - def _row(ch, bank): - grp_index = _grp_idx(ch) - grp_row = grp_index // 4 + def row(ch, bank): + grp_row = grpIdx(ch) // 4 ingrp_row = ((ch % 64) % 32) // 4 if ch & 1: b0_row = 8 * grp_row + (7 - ingrp_row) @@ -809,26 +822,27 @@ def _row(ch, bank): b0_row = 8 * grp_row + ingrp_row return 48 * bank + b0_row - def _bank_for_channel(ch, bankA, bankB): + def bank(ch, bankA, bankB): if col_mode == 2: # ALL return bankA # INNER (0) or OUTER (1): choose bankA or bankB based on column position - c = _col(ch, bankA) - if c < 4: - use_bankA = (c % 2 == 0) if col_mode == 1 else (c % 2 == 1) + c = col(ch, bankA) + if c <= 3: + if col_mode == 1: # OUTER + return bankA if not (c & 1) else bankB + else: # INNER + return bankA if (c & 1) else bankB else: - use_bankA = (c % 2 == 1) if col_mode == 1 else (c % 2 == 0) - return bankA if use_bankA else bankB + if col_mode == 1: # OUTER + return bankA if (c & 1) else bankB + else: # INNER + return bankA if not (c & 1) else bankB electrode_ids = [] for ch in range(384): - grp = _grp_idx(ch) - bankA = groups_bankA[grp] - bankB = groups_bankB[grp] - bank = _bank_for_channel(ch, bankA, bankB) - row = _row(ch, bank) - col = _col(ch, bank) - electrode_ids.append(8 * row + col) + grp = grpIdx(ch) + b = bank(ch, groups_bankA[grp], groups_bankB[grp]) + electrode_ids.append(8 * row(ch, b) + col(ch, b)) imro_per_channel["electrode"] = electrode_ids # Also add the "channel" key (0-383) since the IMRO entries are per-group, not per-channel From adc785690024344f93e79e8cbaa57b5d4eee410b Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Thu, 19 Mar 2026 12:59:40 -0600 Subject: [PATCH 05/12] naming --- src/probeinterface/neuropixels_tools.py | 81 ++++++++++++++++--------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index ed91c0c4..76d518a6 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -257,25 +257,21 @@ def read_imro(file_path: Union[str, Path]) -> Probe: if imDatPrb_type == probe_type: imDatPrb_pn = probe_part_number - # ===== 2. Interpret IMRO table to extract recorded electrodes and acquisition settings ===== - imro_per_channel = _interpret_imro_string(imro_str, imDatPrb_pn) + # ===== 2. Interpret IMRO table ===== + imro_per_channel = _parse_imro_string(imro_str, imDatPrb_pn) # ===== 3. Build full probe with all possible contacts ===== full_probe = build_neuropixels_probe(probe_part_number=imDatPrb_pn) # ===== 4. Build contact IDs for active electrodes ===== - elec_ids = imro_per_channel["electrode"] - shank_ids = imro_per_channel.get("shank", [None] * len(elec_ids)) - active_contact_ids = [ - _build_canonical_contact_id(elec_id, shank_id) for shank_id, elec_id in zip(shank_ids, elec_ids) - ] + active_contact_ids = _get_active_contact_ids(imro_per_channel, imro_str) # ===== 5. Slice full probe to active electrodes ===== contact_id_to_index = {cid: i for i, cid in enumerate(full_probe.contact_ids)} selected_indices = np.array([contact_id_to_index[cid] for cid in active_contact_ids]) probe = full_probe.get_slice(selected_indices) - # ===== 6. Annotate probe with recording-specific metadata ===== + # ===== 7. Annotate probe with recording-specific metadata ===== adc_sampling_table = probe.annotations.get("adc_sampling_table") _annotate_probe_with_adc_sampling_info(probe, adc_sampling_table) @@ -670,7 +666,7 @@ def _build_canonical_contact_id(electrode_id: int, shank_id: int | None = None) return f"e{electrode_id}" -def _interpret_imro_string(imro_table_string: str, probe_part_number: str) -> dict: +def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: """ Parse IMRO (Imec ReadOut) table string into structured per-channel data. @@ -692,12 +688,13 @@ def _interpret_imro_string(imro_table_string: str, probe_part_number: str) -> di Returns ------- imro_per_channel : dict - Dictionary where each key maps to a list of values (one per channel). - Keys are IMRO fields like "channel", "bank", "electrode", "ap_gain", etc. - The "electrode" key always contains physical electrode IDs (0-959 for NP1.0, etc.). - For NP2.0+: electrode IDs come directly from IMRO data. - For NP1.0: electrode IDs are computed as bank * 384 + channel. - Example: {"channel": [0,1,2,...], "bank": [1,0,0,...], "electrode": [384,1,2,...], "ap_gain": [500,500,...]} + Dictionary where each key maps to a list of values (one per entry). + Keys correspond to the IMRO field names from the catalogue. + NP2.x+ probes will have an "electrode" key directly. NP1.x probes will not + (electrode IDs must be resolved separately via _get_active_contact_ids). + Example for NP1.0: {"channel": [0,1,...], "bank": [0,0,...], "ref_id": [0,0,...], ...} + Example for NP2.0: {"channel": [0,1,...], "bank_mask": [1,1,...], "electrode": [0,1,...], ...} + Example for NP1110: {"group": [0,1,...], "bankA": [0,0,...], "bankB": [0,0,...]} """ # Get IMRO field format from catalogue probe_features = _load_np_probe_features() @@ -715,15 +712,44 @@ def _interpret_imro_string(imro_table_string: str, probe_part_number: str) -> di for field, field_value in zip(imro_fields, values): imro_per_channel[field].append(field_value) - # Resolve activate electrodes (i.e. `electrodes` entry) for probe types whose IMRO format does not include them. - # NP2.x+ probes have "electrode" directly in the IMRO table. NP1.x probes encode - # electrode selection indirectly and need computation. + return imro_per_channel + + +def _get_active_contact_ids(imro_per_channel: dict, imro_table_string: str) -> list[str]: + """ + Get canonical contact ID strings for the active electrodes in a parsed IMRO table. + + If the IMRO format includes electrode IDs directly (NP2.x+), uses them as-is. + If not (NP1.x), resolves them first via the appropriate addressing scheme + (simple bank for NP1.0-like probes, UHD group-based for NP1110). + + Parameters + ---------- + imro_per_channel : dict + Parsed IMRO data from _parse_imro_string. Modified in place if electrode + IDs need to be resolved. + imro_table_string : str + Raw IMRO table string, needed by NP1110 to extract col_mode from the header. + + Returns + ------- + list of str + Canonical contact ID strings (e.g., ["e0", "e384", ...] or ["s0e123", ...]). + """ if "electrode" not in imro_per_channel: _resolve_active_contacts_for_np1(imro_per_channel) if "electrode" not in imro_per_channel: _resolve_active_contacts_for_np1110(imro_per_channel, imro_table_string) + assert "electrode" in imro_per_channel, ( + f"Could not resolve electrode IDs from IMRO fields: {list(imro_per_channel.keys())}" + ) - return imro_per_channel + elec_ids = imro_per_channel["electrode"] + shank_ids = imro_per_channel.get("shank", [None] * len(elec_ids)) + return [ + _build_canonical_contact_id(elec_id, shank_id) + for shank_id, elec_id in zip(shank_ids, elec_ids) + ] def _resolve_active_contacts_for_np1(imro_per_channel: dict) -> None: @@ -739,7 +765,7 @@ def _resolve_active_contacts_for_np1(imro_per_channel: dict) -> None: Parameters ---------- imro_per_channel : dict - Parsed IMRO data from _interpret_imro_string. Modified in place. + Parsed IMRO data from _parse_imro_string. Modified in place. """ if "channel" not in imro_per_channel: return @@ -784,7 +810,7 @@ def _resolve_active_contacts_for_np1110(imro_per_channel: dict, imro_table_strin "been validated against real NP1110 recordings. Please double-check the electrode " "selection and report any issues at https://github.com/SpikeInterface/probeinterface/issues", UserWarning, - stacklevel=3, # Points to read_imro / read_spikeglx (caller of _interpret_imro_string) + stacklevel=3, # Points to read_imro / read_spikeglx (caller of _ensure_active_contacts_available) ) # Extract col_mode from IMRO header: (type,col_mode,ref_id,ap_gain,lf_gain,ap_hipas_flt) @@ -899,15 +925,10 @@ def read_spikeglx(file: str | Path) -> Probe: # Specifies which electrodes were selected for recording (e.g., 384 of 960) plus their # acquisition settings (gains, references, filters). See: https://billkarsh.github.io/SpikeGLX/help/imroTables/ imro_table_string = meta["imroTbl"] - imro_per_channel = _interpret_imro_string(imro_table_string, imDatPrb_pn) + imro_per_channel = _parse_imro_string(imro_table_string, imDatPrb_pn) # ===== 4. Build contact IDs for active electrodes ===== - # Convert physical electrode IDs to probeinterface canonical contact ID strings - imro_electrode = imro_per_channel["electrode"] - imro_shank = imro_per_channel.get("shank", [None] * len(imro_electrode)) - active_contact_ids = [ - _build_canonical_contact_id(elec_id, shank_id) for shank_id, elec_id in zip(imro_shank, imro_electrode) - ] + active_contact_ids = _get_active_contact_ids(imro_per_channel, imro_table_string) # ===== 5. Slice full probe to active electrodes ===== # Find indices of active contacts in the full probe, preserving IMRO order @@ -944,7 +965,7 @@ def read_spikeglx(file: str | Path) -> Probe: if saved_chans.size != probe.get_contact_count(): probe = probe.get_slice(saved_chans) - # ===== 6. Add recording-specific annotations ===== + # ===== 8. Add recording-specific annotations ===== # These annotations identify the physical probe instance and recording setup imDatPrb_serial_number = meta.get("imDatPrb_sn") or meta.get("imProbeSN") # Phase3A uses imProbeSN imDatPrb_port = meta.get("imDatPrb_port", None) @@ -954,7 +975,7 @@ def read_spikeglx(file: str | Path) -> Probe: probe.annotate(port=imDatPrb_port) probe.annotate(slot=imDatPrb_slot) - # ===== 7. Set device channel indices (wiring) ===== + # ===== 9. Set device channel indices (wiring) ===== probe.set_device_channel_indices(np.arange(probe.get_contact_count())) return probe From aae913c3848862f81d45b95ce2d79a311066e69d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:59:53 +0000 Subject: [PATCH 06/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/probeinterface/neuropixels_tools.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index 76d518a6..87db61d7 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -740,16 +740,13 @@ def _get_active_contact_ids(imro_per_channel: dict, imro_table_string: str) -> l _resolve_active_contacts_for_np1(imro_per_channel) if "electrode" not in imro_per_channel: _resolve_active_contacts_for_np1110(imro_per_channel, imro_table_string) - assert "electrode" in imro_per_channel, ( - f"Could not resolve electrode IDs from IMRO fields: {list(imro_per_channel.keys())}" - ) + assert ( + "electrode" in imro_per_channel + ), f"Could not resolve electrode IDs from IMRO fields: {list(imro_per_channel.keys())}" elec_ids = imro_per_channel["electrode"] shank_ids = imro_per_channel.get("shank", [None] * len(elec_ids)) - return [ - _build_canonical_contact_id(elec_id, shank_id) - for shank_id, elec_id in zip(shank_ids, elec_ids) - ] + return [_build_canonical_contact_id(elec_id, shank_id) for shank_id, elec_id in zip(shank_ids, elec_ids)] def _resolve_active_contacts_for_np1(imro_per_channel: dict) -> None: From 589aaaa008d76575c044232f1c905355e953c7ea Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Thu, 19 Mar 2026 13:13:12 -0600 Subject: [PATCH 07/12] add header to imro parsing --- src/probeinterface/neuropixels_tools.py | 51 ++++++++++++++----------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index 76d518a6..e71bb3d4 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -264,7 +264,7 @@ def read_imro(file_path: Union[str, Path]) -> Probe: full_probe = build_neuropixels_probe(probe_part_number=imDatPrb_pn) # ===== 4. Build contact IDs for active electrodes ===== - active_contact_ids = _get_active_contact_ids(imro_per_channel, imro_str) + active_contact_ids = _get_active_contact_ids(imro_per_channel) # ===== 5. Slice full probe to active electrodes ===== contact_id_to_index = {cid: i for i, cid in enumerate(full_probe.contact_ids)} @@ -688,13 +688,17 @@ def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: Returns ------- imro_per_channel : dict - Dictionary where each key maps to a list of values (one per entry). - Keys correspond to the IMRO field names from the catalogue. + Dictionary with a "header" key containing parsed header fields, and one key per + IMRO entry field mapping to a list of values. The number of entries varies by probe + type (384 per channel for most probes, 24 per group for NP1110). NP2.x+ probes will have an "electrode" key directly. NP1.x probes will not (electrode IDs must be resolved separately via _get_active_contact_ids). - Example for NP1.0: {"channel": [0,1,...], "bank": [0,0,...], "ref_id": [0,0,...], ...} - Example for NP2.0: {"channel": [0,1,...], "bank_mask": [1,1,...], "electrode": [0,1,...], ...} - Example for NP1110: {"group": [0,1,...], "bankA": [0,0,...], "bankB": [0,0,...]} + Example for NP1.0: {"header": {"type": 0, "num_channels": 384}, + "channel": [0,1,...], "bank": [0,0,...], "ref_id": [0,0,...], ...} + Example for NP2.0: {"header": {"type": 21, "num_channels": 384}, + "channel": [0,1,...], "bank_mask": [1,1,...], "electrode": [0,1,...], ...} + Example for NP1110: {"header": {"type": 1110, "col_mode": 2, "ref_id": 0, ...}, + "group": [0,1,...], "bankA": [0,0,...], "bankB": [0,0,...]} # 24 entries, not 384 """ # Get IMRO field format from catalogue probe_features = _load_np_probe_features() @@ -703,10 +707,17 @@ def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: imro_fields_string = probe_features["z_imro_formats"][imro_format + "_elm_flds"] imro_fields = tuple(imro_fields_string.replace("(", "").replace(")", "").split(" ")) - # Parse IMRO table values into per-channel data - # Skip the header "(probe_type,num_chans)" and trailing empty string - _, *imro_table_values_list, _ = imro_table_string.strip().split(")") - imro_per_channel = {k: [] for k in imro_fields} + # Parse IMRO header and per-entry values + header_str, *imro_table_values_list, _ = imro_table_string.strip().split(")") + + # Parse header fields using the catalogue schema + imro_header_fields_string = probe_features["z_imro_formats"][imro_format + "_hdr_flds"] + imro_header_fields = tuple(imro_header_fields_string.replace("(", "").replace(")", "").split(",")) + header_values = tuple(map(int, header_str[1:].split(","))) + # Initialize with parsed header and empty lists for per-entry fields (filled below) + imro_per_channel = {"header": dict(zip(imro_header_fields, header_values))} + for field in imro_fields: + imro_per_channel[field] = [] for field_values_str in imro_table_values_list: values = tuple(map(int, field_values_str[1:].split(" "))) for field, field_value in zip(imro_fields, values): @@ -715,7 +726,7 @@ def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: return imro_per_channel -def _get_active_contact_ids(imro_per_channel: dict, imro_table_string: str) -> list[str]: +def _get_active_contact_ids(imro_per_channel: dict) -> list[str]: """ Get canonical contact ID strings for the active electrodes in a parsed IMRO table. @@ -728,8 +739,6 @@ def _get_active_contact_ids(imro_per_channel: dict, imro_table_string: str) -> l imro_per_channel : dict Parsed IMRO data from _parse_imro_string. Modified in place if electrode IDs need to be resolved. - imro_table_string : str - Raw IMRO table string, needed by NP1110 to extract col_mode from the header. Returns ------- @@ -739,7 +748,7 @@ def _get_active_contact_ids(imro_per_channel: dict, imro_table_string: str) -> l if "electrode" not in imro_per_channel: _resolve_active_contacts_for_np1(imro_per_channel) if "electrode" not in imro_per_channel: - _resolve_active_contacts_for_np1110(imro_per_channel, imro_table_string) + _resolve_active_contacts_for_np1110(imro_per_channel) assert "electrode" in imro_per_channel, ( f"Could not resolve electrode IDs from IMRO fields: {list(imro_per_channel.keys())}" ) @@ -776,7 +785,7 @@ def _resolve_active_contacts_for_np1(imro_per_channel: dict) -> None: imro_per_channel["electrode"] = (bank_indices * 384 + readout_channel_ids).tolist() -def _resolve_active_contacts_for_np1110(imro_per_channel: dict, imro_table_string: str) -> None: +def _resolve_active_contacts_for_np1110(imro_per_channel: dict) -> None: """ Compute electrode IDs for NP1110 (UHD2 active) probes that use group-based addressing. @@ -791,9 +800,8 @@ def _resolve_active_contacts_for_np1110(imro_per_channel: dict, imro_table_strin Parameters ---------- imro_per_channel : dict - Parsed IMRO data with keys "group", "bankA", "bankB" (24 entries each). - imro_table_string : str - Raw IMRO table string, needed to extract col_mode from the header. + Parsed IMRO data with keys "group", "bankA", "bankB" (24 entries each) + and "header" dict containing "col_mode". References ---------- @@ -813,10 +821,7 @@ def _resolve_active_contacts_for_np1110(imro_per_channel: dict, imro_table_strin stacklevel=3, # Points to read_imro / read_spikeglx (caller of _ensure_active_contacts_available) ) - # Extract col_mode from IMRO header: (type,col_mode,ref_id,ap_gain,lf_gain,ap_hipas_flt) - header_str = imro_table_string.strip().split(")")[0][1:] # remove leading "(" - header_fields = header_str.split(",") - col_mode = int(header_fields[1]) # 0=INNER, 1=OUTER, 2=ALL + col_mode = imro_per_channel["header"]["col_mode"] # 0=INNER, 1=OUTER, 2=ALL groups_bankA = imro_per_channel["bankA"] groups_bankB = imro_per_channel["bankB"] @@ -928,7 +933,7 @@ def read_spikeglx(file: str | Path) -> Probe: imro_per_channel = _parse_imro_string(imro_table_string, imDatPrb_pn) # ===== 4. Build contact IDs for active electrodes ===== - active_contact_ids = _get_active_contact_ids(imro_per_channel, imro_table_string) + active_contact_ids = _get_active_contact_ids(imro_per_channel) # ===== 5. Slice full probe to active electrodes ===== # Find indices of active contacts in the full probe, preserving IMRO order From cfeaa1f6a327e957f1b5ad487d9999a7a7153e8f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:15:01 +0000 Subject: [PATCH 08/12] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/probeinterface/neuropixels_tools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index 33e26dea..ef2926c3 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -749,9 +749,9 @@ def _get_active_contact_ids(imro_per_channel: dict) -> list[str]: _resolve_active_contacts_for_np1(imro_per_channel) if "electrode" not in imro_per_channel: _resolve_active_contacts_for_np1110(imro_per_channel) - assert "electrode" in imro_per_channel, ( - f"Could not resolve electrode IDs from IMRO fields: {list(imro_per_channel.keys())}" - ) + assert ( + "electrode" in imro_per_channel + ), f"Could not resolve electrode IDs from IMRO fields: {list(imro_per_channel.keys())}" elec_ids = imro_per_channel["electrode"] shank_ids = imro_per_channel.get("shank", [None] * len(elec_ids)) From 7201ffce89f4ade6fd198a1a605ec4c9bc3ed5d2 Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Thu, 19 Mar 2026 13:21:27 -0600 Subject: [PATCH 09/12] unify slicing --- src/probeinterface/neuropixels_tools.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index 33e26dea..abf31418 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -263,15 +263,13 @@ def read_imro(file_path: Union[str, Path]) -> Probe: # ===== 3. Build full probe with all possible contacts ===== full_probe = build_neuropixels_probe(probe_part_number=imDatPrb_pn) - # ===== 4. Build contact IDs for active electrodes ===== + # ===== 4. Slice full probe to active electrodes ===== active_contact_ids = _get_active_contact_ids(imro_per_channel) - - # ===== 5. Slice full probe to active electrodes ===== contact_id_to_index = {cid: i for i, cid in enumerate(full_probe.contact_ids)} selected_indices = np.array([contact_id_to_index[cid] for cid in active_contact_ids]) probe = full_probe.get_slice(selected_indices) - # ===== 7. Annotate probe with recording-specific metadata ===== + # ===== 5. Annotate probe with recording-specific metadata ===== adc_sampling_table = probe.annotations.get("adc_sampling_table") _annotate_probe_with_adc_sampling_info(probe, adc_sampling_table) @@ -929,17 +927,13 @@ def read_spikeglx(file: str | Path) -> Probe: imro_table_string = meta["imroTbl"] imro_per_channel = _parse_imro_string(imro_table_string, imDatPrb_pn) - # ===== 4. Build contact IDs for active electrodes ===== + # ===== 4. Slice full probe to active electrodes ===== active_contact_ids = _get_active_contact_ids(imro_per_channel) - - # ===== 5. Slice full probe to active electrodes ===== - # Find indices of active contacts in the full probe, preserving IMRO order contact_id_to_index = {contact_id: idx for idx, contact_id in enumerate(full_probe.contact_ids)} selected_contact_indices = np.array([contact_id_to_index[contact_id] for contact_id in active_contact_ids]) - probe = full_probe.get_slice(selected_contact_indices) - # ===== 6. Store IMRO properties (acquisition settings) as annotations ===== + # ===== 5. Store IMRO properties (acquisition settings) as annotations ===== # Filter IMRO data to only the properties we want to add as annotations imro_properties_to_add = ("channel", "bank", "bank_mask", "ref_id", "ap_gain", "lf_gain", "ap_hipas_flt") imro_filtered = {k: v for k, v in imro_per_channel.items() if k in imro_properties_to_add and len(v) > 0} @@ -950,7 +944,7 @@ def read_spikeglx(file: str | Path) -> Probe: annotations[pi_field] = values probe.annotate_contacts(**annotations) - # ===== 6b. Add ADC sampling annotations ===== + # ===== 5b. Add ADC sampling annotations ===== # The ADC sampling table describes which ADC samples each readout channel and in what order. # At this point, contacts are ordered by readout channel (0-383), so we can directly # apply the mapping. This must be done here (not in build_neuropixels_probe) @@ -958,7 +952,7 @@ def read_spikeglx(file: str | Path) -> Probe: adc_sampling_table = probe.annotations.get("adc_sampling_table") _annotate_probe_with_adc_sampling_info(probe, adc_sampling_table) - # ===== 7. Slice to saved channels (if subset was saved) ===== + # ===== 6. Slice to saved channels (if subset was saved) ===== # This is DIFFERENT from IMRO selection: IMRO selects which electrodes to acquire, # but SpikeGLX can optionally save only a subset of acquired channels to reduce file size. # For example: IMRO selects 384 electrodes, but only 300 are saved to disk. @@ -967,7 +961,7 @@ def read_spikeglx(file: str | Path) -> Probe: if saved_chans.size != probe.get_contact_count(): probe = probe.get_slice(saved_chans) - # ===== 8. Add recording-specific annotations ===== + # ===== 7. Add recording-specific annotations ===== # These annotations identify the physical probe instance and recording setup imDatPrb_serial_number = meta.get("imDatPrb_sn") or meta.get("imProbeSN") # Phase3A uses imProbeSN imDatPrb_port = meta.get("imDatPrb_port", None) @@ -977,7 +971,7 @@ def read_spikeglx(file: str | Path) -> Probe: probe.annotate(port=imDatPrb_port) probe.annotate(slot=imDatPrb_slot) - # ===== 9. Set device channel indices (wiring) ===== + # ===== 8. Set device channel indices (wiring) ===== probe.set_device_channel_indices(np.arange(probe.get_contact_count())) return probe From 041afc4b6ad0e806f155a2de09748703de25a940 Mon Sep 17 00:00:00 2001 From: Alessio Buccino Date: Fri, 20 Mar 2026 12:01:26 +0100 Subject: [PATCH 10/12] reorganize and remove unused functions, add test files and tests for NP1110 --- src/probeinterface/neuropixels_tools.py | 430 +++++++----------- .../spikeglx/{npUltra.meta => NP-Ultra.meta} | 0 .../NP1110_2x192_bank4_g0_t0.imec0.ap.meta | 77 ++++ .../spikeglx/NP1110_bank0_g0_t0.imec0.ap.meta | 77 ++++ .../NP1110_botrow80_g0_t0.imec0.ap.meta | 77 ++++ .../NP1110_vstripe_g0_t0.imec0.ap.meta | 77 ++++ tests/test_io/test_spikeglx.py | 68 ++- 7 files changed, 539 insertions(+), 267 deletions(-) rename tests/data/spikeglx/{npUltra.meta => NP-Ultra.meta} (100%) create mode 100644 tests/data/spikeglx/NP1110_2x192_bank4_g0_t0.imec0.ap.meta create mode 100644 tests/data/spikeglx/NP1110_bank0_g0_t0.imec0.ap.meta create mode 100644 tests/data/spikeglx/NP1110_botrow80_g0_t0.imec0.ap.meta create mode 100644 tests/data/spikeglx/NP1110_vstripe_g0_t0.imec0.ap.meta diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index ade15f17..f79267b4 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -22,15 +22,9 @@ _np_probe_features = None -def _load_np_probe_features(): - # this avoid loading the json several times - global _np_probe_features - if _np_probe_features is None: - probe_features_filepath = Path(__file__).absolute().parent / Path("resources/neuropixels_probe_features.json") - with open(probe_features_filepath, "r") as f: - _np_probe_features = json.load(f) - return _np_probe_features - +############### +# Utils zone # +############### # Map imDatPrb_pn (probe number) to imDatPrb_type (probe type) when the latter is missing # ONLY needed for `read_imro` function @@ -90,7 +84,17 @@ def _load_np_probe_features(): } -def get_probe_length(probe_part_number: str) -> int: +def _load_np_probe_features(): + # this avoid loading the json several times + global _np_probe_features + if _np_probe_features is None: + probe_features_filepath = Path(__file__).absolute().parent / Path("resources/neuropixels_probe_features.json") + with open(probe_features_filepath, "r") as f: + _np_probe_features = json.load(f) + return _np_probe_features + + +def _get_probe_length(probe_part_number: str) -> int: """ Returns the length of a given probe from ProbeTable specifications. @@ -114,43 +118,7 @@ def get_probe_length(probe_part_number: str) -> int: return 10_000 -def make_mux_table_array(mux_information) -> np.array: - """ - Function to parse the mux_table from ProbeTable. - - Parameters - ---------- - mux_information : str - The information from `z_mux_tables` in the ProbeTable `probe_feature.json` file - - Returns - ------- - num_adcs: int - Number of ADCs used in the probe's readout system. - num_channels_per_adc: int - Number of readout channels assigned to each ADC. - adc_groups_array : np.array - Array of which channels are in each adc group, shaped (number of `adc`s, number of channels in each `adc`). - """ - - # mux_information looks like (num_adcs num_channels_per_adc)(int int int ...)(int int int ...)...(int int int ...) - # First split on ')(' to get a list of the information in the brackets, and remove the leading data - adc_info = mux_information.split(")(")[0] - split_mux = mux_information.split(")(")[1:] - - # The first element is the number of ADCs and the number of channels per ADC - num_adcs, num_channels_per_adc = map(int, adc_info[1:].split(",")) - - # Then remove the brackets, and split using " " to get each integer as a list - adc_groups = [ - np.array(each_mux.replace("(", "").replace(")", "").split(" ")).astype("int") for each_mux in split_mux - ] - adc_groups_array = np.transpose(np.array(adc_groups)) - - return num_adcs, num_channels_per_adc, adc_groups_array - - -def get_probe_contour_vertices(shank_width, tip_length, probe_length) -> list: +def _get_probe_contour_vertices(shank_width, tip_length, probe_length) -> list: """ Function to get the vertices of the probe contour from probe properties. The probe contour can be constructed from five points. @@ -210,84 +178,6 @@ def get_probe_contour_vertices(shank_width, tip_length, probe_length) -> list: return polygon_vertices -def read_imro(file_path: Union[str, Path]) -> Probe: - """ - Read a Neuropixels probe from an IMRO (Imec ReadOut) table file. - - IMRO files (.imro) are used by SpikeGLX and Open Ephys to configure which electrodes - are recorded and their acquisition settings (gains, references, filters). This function - reads the file, determines the probe part number from the IMRO header, builds the full - catalogue probe, and slices it to the active electrodes specified in the table. - - Parameters - ---------- - file_path : Path or str - The .imro file path - - Returns - ------- - probe : Probe - Probe object with geometry, contact annotations, and device channel mapping. - - See Also - -------- - https://billkarsh.github.io/SpikeGLX/help/imroTables/ - - """ - # ===== 1. Read file and determine probe part number from IMRO header ===== - meta_file = Path(file_path) - assert meta_file.suffix == ".imro", "'file' should point to the .imro file" - with meta_file.open(mode="r") as f: - imro_str = str(f.read()) - - imro_table_header_str, *imro_table_values_list, _ = imro_str.strip().split(")") - imro_table_header = tuple(map(int, imro_table_header_str[1:].split(","))) - - if len(imro_table_header) == 3: - # In older versions of neuropixel arrays (phase 3A), imro tables were structured differently. - # We use probe_type "0", which maps to probe_part_number NP1010 as a proxy for Phase3a. - imDatPrb_type = "0" - elif len(imro_table_header) == 2: - imDatPrb_type, _ = imro_table_header - else: - raise ValueError(f"read_imro error, the header has a strange length: {imro_table_header}") - imDatPrb_type = str(imDatPrb_type) - - for probe_part_number, probe_type in probe_part_number_to_probe_type.items(): - if imDatPrb_type == probe_type: - imDatPrb_pn = probe_part_number - - # ===== 2. Interpret IMRO table ===== - imro_per_channel = _parse_imro_string(imro_str, imDatPrb_pn) - - # ===== 3. Build full probe with all possible contacts ===== - full_probe = build_neuropixels_probe(probe_part_number=imDatPrb_pn) - - # ===== 4. Slice full probe to active electrodes ===== - active_contact_ids = _get_active_contact_ids(imro_per_channel) - contact_id_to_index = {cid: i for i, cid in enumerate(full_probe.contact_ids)} - selected_indices = np.array([contact_id_to_index[cid] for cid in active_contact_ids]) - probe = full_probe.get_slice(selected_indices) - - # ===== 5. Annotate probe with recording-specific metadata ===== - adc_sampling_table = probe.annotations.get("adc_sampling_table") - _annotate_probe_with_adc_sampling_info(probe, adc_sampling_table) - - # Scalar annotations - probe_type = imro_str.strip().split(")")[0].split(",")[0][1:] - probe.annotate(probe_type=probe_type) - - # Vector annotations from IMRO fields - vector_properties = ("channel", "bank", "bank_mask", "ref_id", "ap_gain", "lf_gain", "ap_hipas_flt") - vector_properties_available = {} - for k, v in imro_per_channel.items(): - if k in vector_properties and len(v) > 0: - vector_properties_available[imro_field_to_pi_field.get(k)] = v - probe.annotate_contacts(**vector_properties_available) - - return probe - - def build_neuropixels_probe(probe_part_number: str) -> Probe: """ Build a Neuropixels probe with all possible contacts from the probe part number. @@ -374,7 +264,7 @@ def build_neuropixels_probe(probe_part_number: str) -> Probe: # ===== 6. Build probe contour and shank tips ===== shank_width = float(probe_spec_dict["shank_width_um"]) tip_length = float(probe_spec_dict["tip_length_um"]) - polygon = np.array(get_probe_contour_vertices(shank_width, tip_length, get_probe_length(probe_part_number))) + polygon = np.array(_get_probe_contour_vertices(shank_width, tip_length, _get_probe_length(probe_part_number))) # Build contour for all shanks contour = [] @@ -393,7 +283,7 @@ def build_neuropixels_probe(probe_part_number: str) -> Probe: contour = np.array(contour) + contour_shift probe.set_planar_contour(contour) - # Calculate shank tips (polygon[2] is the tip vertex from get_probe_contour_vertices) + # Calculate shank tips (polygon[2] is the tip vertex from _get_probe_contour_vertices) tip_vertex = polygon[2] shank_tips = [] for shank_id in range(num_shanks): @@ -513,122 +403,9 @@ def _annotate_probe_with_adc_sampling_info(probe: Probe, adc_sampling_table: str _annotate_contacts_from_mux_table(probe, adc_groups_array) -def get_probe_metadata_from_probe_features(probe_features: dict, imDatPrb_pn: str): - """ - Parses the `probe_features` dict, to cast string to appropriate types - and parses the imro_table_fields string. Returns the metadata needed - to construct a probe with part number `imDatPrb_pn`. - - Parameters - ---------- - probe_features : dict - Dictionary obtained when reading in the `neuropixels_probe_features.json` file. - imDatPrb_pn : str - Probe part number. - - Returns - ------- - probe_metadata, imro_field, mux_information - Dictionary of probe metadata. - Tuple of fields included in the `imro_table_fields`. - Mux table information, if available, as a string. - """ - - probe_metadata = probe_features["neuropixels_probes"].get(imDatPrb_pn) - for key in probe_metadata.keys(): - if key in ["num_shanks", "cols_per_shank", "rows_per_shank", "adc_bit_depth", "num_readout_channels"]: - probe_metadata[key] = int(probe_metadata[key]) - elif key in [ - "electrode_pitch_horz_um", - "electrode_pitch_vert_um", - "electrode_size_horz_direction_um", - "shank_pitch_um", - "shank_width_um", - "tip_length_um", - "even_row_horz_offset_left_edge_to_leftmost_electrode_center_um", - "odd_row_horz_offset_left_edge_to_leftmost_electrode_center_um", - ]: - probe_metadata[key] = float(probe_metadata[key]) - - # Read the imro table formats to find out which fields the imro tables contain - imro_table_format_type = probe_metadata["imro_table_format_type"] - imro_table_fields = probe_features["z_imro_formats"][imro_table_format_type + "_elm_flds"] - - # parse the imro_table_fields, which look like (value value value ...) - list_of_imro_fields = imro_table_fields.replace("(", "").replace(")", "").split(" ") - - imro_fields_list = [] - for imro_field in list_of_imro_fields: - imro_fields_list.append(imro_field) - - imro_fields = tuple(imro_fields_list) - - # Read MUX table information - mux_information = None - - if "z_mux_tables" in probe_features: - mux_table_format_type = probe_metadata.get("mux_table_format_type", None) - mux_information = probe_features["z_mux_tables"].get(mux_table_format_type, None) - - return probe_metadata, imro_fields, mux_information - - -def write_imro(file: str | Path, probe: Probe): - """ - save imro file (`.imrc`, imec readout) in a file. - https://github.com/open-ephys-plugins/neuropixels-pxi/blob/master/Source/Formats/IMRO.h - - Parameters - ---------- - file : Path or str - The file path - probe : Probe object - - """ - probe_type = probe.annotations["probe_type"] - data = probe.to_dataframe(complete=True).sort_values("device_channel_indices") - annotations = probe.contact_annotations - ret = [f"({probe_type},{len(data)})"] - - if probe_type == "0": - # Phase3a probe does not have `ap_hp_filters` annotation - if annotations.get("ap_hp_filters") is not None: - - for ch in range(len(data)): - ret.append( - f"({ch} 0 {annotations['references'][ch]} {annotations['ap_gains'][ch]} " - f"{annotations['lf_gains'][ch]} {annotations['ap_hp_filters'][ch]})" - ) - else: - - for ch in range(len(data)): - ret.append( - f"({ch} 0 {annotations['references'][ch]} {annotations['ap_gains'][ch]} " - f"{annotations['lf_gains'][ch]})" - ) - - elif probe_type in ("21", "2003", "2004"): - for ch in range(len(data)): - ret.append( - f"({data['device_channel_indices'][ch]} {annotations['banks'][ch]} " - f"{annotations['references'][ch]} {data['contact_ids'][ch][1:]})" - ) - - elif probe_type in ("24", "2013", "2014"): - for ch in range(len(data)): - ret.append( - f"({data['device_channel_indices'][ch]} {data['shank_ids'][ch]} {annotations['banks'][ch]} " - f"{annotations['references'][ch]} {data['contact_ids'][ch][3:]})" - ) - else: - raise ValueError(f"unknown imro type : {probe_type}") - with open(file, "w") as f: - f.write("".join(ret)) - - -## -# SpikeGLX zone for neuropixel -## +######################### +# SpikeGLX / IMRO zone # +######################### def _build_canonical_contact_id(electrode_id: int, shank_id: int | None = None) -> str: @@ -690,7 +467,7 @@ def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: IMRO entry field mapping to a list of values. The number of entries varies by probe type (384 per channel for most probes, 24 per group for NP1110). NP2.x+ probes will have an "electrode" key directly. NP1.x probes will not - (electrode IDs must be resolved separately via _get_active_contact_ids). + (electrode IDs must be resolved separately via _get_imro_active_contact_ids). Example for NP1.0: {"header": {"type": 0, "num_channels": 384}, "channel": [0,1,...], "bank": [0,0,...], "ref_id": [0,0,...], ...} Example for NP2.0: {"header": {"type": 21, "num_channels": 384}, @@ -724,7 +501,60 @@ def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: return imro_per_channel -def _get_active_contact_ids(imro_per_channel: dict) -> list[str]: +def write_imro(file: str | Path, probe: Probe): + """ + save imro file (`.imrc`, imec readout) in a file. + https://github.com/open-ephys-plugins/neuropixels-pxi/blob/master/Source/Formats/IMRO.h + + Parameters + ---------- + file : Path or str + The file path + probe : Probe object + + """ + probe_type = probe.annotations["probe_type"] + data = probe.to_dataframe(complete=True).sort_values("device_channel_indices") + annotations = probe.contact_annotations + ret = [f"({probe_type},{len(data)})"] + + if probe_type == "0": + # Phase3a probe does not have `ap_hp_filters` annotation + if annotations.get("ap_hp_filters") is not None: + + for ch in range(len(data)): + ret.append( + f"({ch} 0 {annotations['references'][ch]} {annotations['ap_gains'][ch]} " + f"{annotations['lf_gains'][ch]} {annotations['ap_hp_filters'][ch]})" + ) + else: + + for ch in range(len(data)): + ret.append( + f"({ch} 0 {annotations['references'][ch]} {annotations['ap_gains'][ch]} " + f"{annotations['lf_gains'][ch]})" + ) + + elif probe_type in ("21", "2003", "2004"): + for ch in range(len(data)): + ret.append( + f"({data['device_channel_indices'][ch]} {annotations['banks'][ch]} " + f"{annotations['references'][ch]} {data['contact_ids'][ch][1:]})" + ) + + elif probe_type in ("24", "2013", "2014"): + for ch in range(len(data)): + ret.append( + f"({data['device_channel_indices'][ch]} {data['shank_ids'][ch]} {annotations['banks'][ch]} " + f"{annotations['references'][ch]} {data['contact_ids'][ch][3:]})" + ) + else: + raise ValueError(f"unknown imro type : {probe_type}") + with open(file, "w") as f: + f.write("".join(ret)) + + +def _get_imro_active_contact_ids(imro_per_channel: dict) -> list[str]: """ Get canonical contact ID strings for the active electrodes in a parsed IMRO table. @@ -875,6 +705,84 @@ def bank(ch, bankA, bankB): imro_per_channel["channel"] = list(range(384)) +def read_imro(file_path: Union[str, Path]) -> Probe: + """ + Read a Neuropixels probe from an IMRO (Imec ReadOut) table file. + + IMRO files (.imro) are used by SpikeGLX and Open Ephys to configure which electrodes + are recorded and their acquisition settings (gains, references, filters). This function + reads the file, determines the probe part number from the IMRO header, builds the full + catalogue probe, and slices it to the active electrodes specified in the table. + + Parameters + ---------- + file_path : Path or str + The .imro file path + + Returns + ------- + probe : Probe + Probe object with geometry, contact annotations, and device channel mapping. + + See Also + -------- + https://billkarsh.github.io/SpikeGLX/help/imroTables/ + + """ + # ===== 1. Read file and determine probe part number from IMRO header ===== + meta_file = Path(file_path) + assert meta_file.suffix == ".imro", "'file' should point to the .imro file" + with meta_file.open(mode="r") as f: + imro_str = str(f.read()) + + imro_table_header_str, *imro_table_values_list, _ = imro_str.strip().split(")") + imro_table_header = tuple(map(int, imro_table_header_str[1:].split(","))) + + if len(imro_table_header) == 3: + # In older versions of neuropixel arrays (phase 3A), imro tables were structured differently. + # We use probe_type "0", which maps to probe_part_number NP1010 as a proxy for Phase3a. + imDatPrb_type = "0" + elif len(imro_table_header) == 2: + imDatPrb_type, _ = imro_table_header + else: + raise ValueError(f"read_imro error, the header has a strange length: {imro_table_header}") + imDatPrb_type = str(imDatPrb_type) + + for probe_part_number, probe_type in probe_part_number_to_probe_type.items(): + if imDatPrb_type == probe_type: + imDatPrb_pn = probe_part_number + + # ===== 2. Interpret IMRO table ===== + imro_per_channel = _parse_imro_string(imro_str, imDatPrb_pn) + + # ===== 3. Build full probe with all possible contacts ===== + full_probe = build_neuropixels_probe(probe_part_number=imDatPrb_pn) + + # ===== 4. Slice full probe to active electrodes ===== + active_contact_ids = _get_imro_active_contact_ids(imro_per_channel) + contact_id_to_index = {cid: i for i, cid in enumerate(full_probe.contact_ids)} + selected_indices = np.array([contact_id_to_index[cid] for cid in active_contact_ids]) + probe = full_probe.get_slice(selected_indices) + + # ===== 5. Annotate probe with recording-specific metadata ===== + adc_sampling_table = probe.annotations.get("adc_sampling_table") + _annotate_probe_with_adc_sampling_info(probe, adc_sampling_table) + + # Scalar annotations + probe_type = imro_str.strip().split(")")[0].split(",")[0][1:] + probe.annotate(probe_type=probe_type) + + # Vector annotations from IMRO fields + vector_properties = ("channel", "bank", "bank_mask", "ref_id", "ap_gain", "lf_gain", "ap_hipas_flt") + vector_properties_available = {} + for k, v in imro_per_channel.items(): + if k in vector_properties and len(v) > 0: + vector_properties_available[imro_field_to_pi_field.get(k)] = v + probe.annotate_contacts(**vector_properties_available) + + return probe + + def read_spikeglx(file: str | Path) -> Probe: """ Read probe geometry and configuration from a SpikeGLX metadata file. @@ -928,7 +836,7 @@ def read_spikeglx(file: str | Path) -> Probe: imro_per_channel = _parse_imro_string(imro_table_string, imDatPrb_pn) # ===== 4. Slice full probe to active electrodes ===== - active_contact_ids = _get_active_contact_ids(imro_per_channel) + active_contact_ids = _get_imro_active_contact_ids(imro_per_channel) contact_id_to_index = {contact_id: idx for idx, contact_id in enumerate(full_probe.contact_ids)} selected_contact_indices = np.array([contact_id_to_index[contact_id] for contact_id in active_contact_ids]) probe = full_probe.get_slice(selected_contact_indices) @@ -1035,10 +943,6 @@ def parse_spikeglx_snsGeomMap(meta): return num_shank, shank_width, shank_pitch, shank_ids, x_pos, y_pos, activated -# def spikeglx_snsGeomMap_to_probe(meta): -# parse_spikeglx_snsGeomMap(meta) - - def get_saved_channel_indices_from_spikeglx_meta(meta_file: str | Path) -> np.array: """ Utils function to get the saved channels. @@ -1070,9 +974,9 @@ def get_saved_channel_indices_from_spikeglx_meta(meta_file: str | Path) -> np.ar return chans -## -# OpenEphys zone for neuropixel -## +################### +# OpenEphys zone # +################### def _parse_openephys_settings( @@ -1104,7 +1008,7 @@ def _parse_openephys_settings( - selected_electrode_indices: list of int (from SELECTED_ELECTRODES), or None - contact_ids: list of str (reverse-engineered from CHANNELS), or None - settings_channel_keys: np.array of str, or None - - elec_ids, shank_ids, pt_metadata, mux_info: for legacy fallback + - elec_ids, shank_ids: for legacy fallback """ ET = import_safely("xml.etree.ElementTree") tree = ET.parse(str(settings_file)) @@ -1269,7 +1173,7 @@ def _parse_openephys_settings( selected_electrodes = np_probe.find("SELECTED_ELECTRODES") channels = np_probe.find("CHANNELS") - pt_metadata, _, mux_info = get_probe_metadata_from_probe_features(probe_features, probe_part_number) + pt_metadata = probe_features["neuropixels_probes"].get(probe_part_number) # Assign probe name if "custom_probe_name" in np_probe.attrib and np_probe.attrib["custom_probe_name"] != probe_serial_number: @@ -1285,7 +1189,6 @@ def _parse_openephys_settings( "port": port, "dock": dock, "pt_metadata": pt_metadata, - "mux_info": mux_info, "selected_electrode_indices": None, "contact_ids": None, "settings_channel_keys": None, @@ -1330,7 +1233,7 @@ def _parse_openephys_settings( ypos = np.array([float(electrode_ypos.attrib[ch]) for ch in channel_names]) positions = np.array([xpos, ypos]).T - shank_pitch = pt_metadata["shank_pitch_um"] + shank_pitch = float(pt_metadata["shank_pitch_um"]) if fix_x_position_for_oe_5 and oe_version < parse("0.6.0") and shank_ids is not None: positions[:, 1] = positions[:, 1] - shank_pitch * shank_ids @@ -1341,14 +1244,13 @@ def _parse_openephys_settings( offset -= np.min(shank_ids) * shank_pitch positions[:, 0] -= offset - y_pitch = pt_metadata["electrode_pitch_vert_um"] - x_pitch = pt_metadata["electrode_pitch_horz_um"] - number_of_columns = pt_metadata["cols_per_shank"] - probe_stagger = ( + y_pitch = float(pt_metadata["electrode_pitch_vert_um"]) + x_pitch = float(pt_metadata["electrode_pitch_horz_um"]) + number_of_columns = int(pt_metadata["cols_per_shank"]) + probe_stagger = float( pt_metadata["even_row_horz_offset_left_edge_to_leftmost_electrode_center_um"] - - pt_metadata["odd_row_horz_offset_left_edge_to_leftmost_electrode_center_um"] - ) - num_shanks = pt_metadata["num_shanks"] + ) - float(pt_metadata["odd_row_horz_offset_left_edge_to_leftmost_electrode_center_um"]) + num_shanks = int(pt_metadata["num_shanks"]) description = pt_metadata.get("description") elec_ids = [] diff --git a/tests/data/spikeglx/npUltra.meta b/tests/data/spikeglx/NP-Ultra.meta similarity index 100% rename from tests/data/spikeglx/npUltra.meta rename to tests/data/spikeglx/NP-Ultra.meta diff --git a/tests/data/spikeglx/NP1110_2x192_bank4_g0_t0.imec0.ap.meta b/tests/data/spikeglx/NP1110_2x192_bank4_g0_t0.imec0.ap.meta new file mode 100644 index 00000000..8f6e5f12 --- /dev/null +++ b/tests/data/spikeglx/NP1110_2x192_bank4_g0_t0.imec0.ap.meta @@ -0,0 +1,77 @@ +acqApLfSy=384,384,1 +appVersion=20251218 +fileCreateTime=2026-03-19T22:20:16 +fileName=C:/SGL_DATA/031826_wireless/np1110_2x192_bank4_g0/np1110_2x192_bank4_g0_imec0/np1110_2x192_bank4_g0_t0.imec0.ap.bin +fileSHA1=05F16F057289B9DEE1149BC12F438E483045D4E4 +fileSizeBytes=172529280 +fileTimeSecs=7.4688 +firstSample=305280 +gateMode=Immediate +imAiRangeMax=0.6 +imAiRangeMin=-0.6 +imAnyChanFullBand=false +imCalibrated=false +imChan0Ref=ext +imChan0apGain=500 +imColsPerShank=8 +imDatApi=4.1.3 +imDatBs_fw=3.0.226 +imDatBsc_fw=4.0.233 +imDatBsc_hw=1.9 +imDatBsc_pn=NP2_QBSC_00 +imDatBsc_sn=175 +imDatBsc_tech=std +imDatFx_hw=4.13 +imDatFx_pn=NP2_FLEX_0 +imDatFx_sn=0 +imDatHs_fw=0.0.0 +imDatHs_hw=4.9 +imDatHs_pn=NP2_HS_30 +imDatHs_sn=46 +imDatPrb_dock=1 +imDatPrb_pn=NP1110 +imDatPrb_port=4 +imDatPrb_slot=5 +imDatPrb_sn=21144116901 +imDatPrb_sr_mask=1 +imDatPrb_sr_nok=1 +imDatPrb_tech=std +imDatPrb_type=1110 +imErrFlags0_IS_CT_SR_LK_PP_SY_MS=0 0 0 0 0 0 0 +imIsSvyRun=false +imLEDEnable=false +imLowLatency=false +imMaxInt=512 +imRowsPerShank=768 +imSRAtDetect=true +imSampRate=30000 +imStdby= +imSvyMaxBnk=-1 +imSvyNShanks=1 +imSvySecPerBnk=60 +imSvySettleSec=2 +imTipLength=203.5 +imTrgRising=true +imTrgSource=0 +imX0EvenRow=15.5 +imX0OddRow=15.5 +imXPitch=6 +imZPitch=6 +imroFile=C:/Users/labadmin/Desktop/SGLX_top/Release_v20251218-api4/IMRO_Tables/MATLAB_Scripts/NPUHD2_192x2_bank4_ref0.imro +nDataDirs=1 +nSavedChans=385 +snsApLfSy=384,0,1 +snsSaveChanSubset=0:383,768 +syncImInputSlot=5 +syncSourceIdx=3 +syncSourcePeriod=2 +trigMode=Immediate +typeImEnabled=1 +typeNiEnabled=0 +typeObEnabled=0 +typeThis=imec +userNotes= +~imroTbl=(1110,2,0,500,250,1)(0 5 5)(1 7 7)(2 4 4)(3 6 6)(4 5 5)(5 7 7)(6 4 4)(7 6 6)(8 5 5)(9 7 7)(10 4 4)(11 6 6)(12 5 5)(13 7 7)(14 4 4)(15 6 6)(16 5 5)(17 7 7)(18 4 4)(19 6 6)(20 5 5)(21 7 7)(22 4 4)(23 6 6) +~muxTbl=(32,12)(0 1 24 25 48 49 72 73 96 97 120 121 144 145 168 169 192 193 216 217 240 241 264 265 288 289 312 313 336 337 360 361)(2 3 26 27 50 51 74 75 98 99 122 123 146 147 170 171 194 195 218 219 242 243 266 267 290 291 314 315 338 339 362 363)(4 5 28 29 52 53 76 77 100 101 124 125 148 149 172 173 196 197 220 221 244 245 268 269 292 293 316 317 340 341 364 365)(6 7 30 31 54 55 78 79 102 103 126 127 150 151 174 175 198 199 222 223 246 247 270 271 294 295 318 319 342 343 366 367)(8 9 32 33 56 57 80 81 104 105 128 129 152 153 176 177 200 201 224 225 248 249 272 273 296 297 320 321 344 345 368 369)(10 11 34 35 58 59 82 83 106 107 130 131 154 155 178 179 202 203 226 227 250 251 274 275 298 299 322 323 346 347 370 371)(12 13 36 37 60 61 84 85 108 109 132 133 156 157 180 181 204 205 228 229 252 253 276 277 300 301 324 325 348 349 372 373)(14 15 38 39 62 63 86 87 110 111 134 135 158 159 182 183 206 207 230 231 254 255 278 279 302 303 326 327 350 351 374 375)(16 17 40 41 64 65 88 89 112 113 136 137 160 161 184 185 208 209 232 233 256 257 280 281 304 305 328 329 352 353 376 377)(18 19 42 43 66 67 90 91 114 115 138 139 162 163 186 187 210 211 234 235 258 259 282 283 306 307 330 331 354 355 378 379)(20 21 44 45 68 69 92 93 116 117 140 141 164 165 188 189 212 213 236 237 260 261 284 285 308 309 332 333 356 357 380 381)(22 23 46 47 70 71 94 95 118 119 142 143 166 167 190 191 214 215 238 239 262 263 286 287 310 311 334 335 358 359 382 383) +~snsChanMap=(384,384,1)(AP0;0:97)(AP1;1:302)(AP2;2:96)(AP3;3:303)(AP4;4:99)(AP5;5:300)(AP6;6:98)(AP7;7:301)(AP8;8:101)(AP9;9:298)(AP10;10:100)(AP11;11:299)(AP12;12:103)(AP13;13:296)(AP14;14:102)(AP15;15:297)(AP16;16:105)(AP17;17:294)(AP18;18:104)(AP19;19:295)(AP20;20:107)(AP21;21:292)(AP22;22:106)(AP23;23:293)(AP24;24:109)(AP25;25:290)(AP26;26:108)(AP27;27:291)(AP28;28:111)(AP29;29:288)(AP30;30:110)(AP31;31:289)(AP32;32:1)(AP33;33:206)(AP34;34:0)(AP35;35:207)(AP36;36:3)(AP37;37:204)(AP38;38:2)(AP39;39:205)(AP40;40:5)(AP41;41:202)(AP42;42:4)(AP43;43:203)(AP44;44:7)(AP45;45:200)(AP46;46:6)(AP47;47:201)(AP48;48:9)(AP49;49:198)(AP50;50:8)(AP51;51:199)(AP52;52:11)(AP53;53:196)(AP54;54:10)(AP55;55:197)(AP56;56:13)(AP57;57:194)(AP58;58:12)(AP59;59:195)(AP60;60:15)(AP61;61:192)(AP62;62:14)(AP63;63:193)(AP64;64:113)(AP65;65:318)(AP66;66:112)(AP67;67:319)(AP68;68:115)(AP69;69:316)(AP70;70:114)(AP71;71:317)(AP72;72:117)(AP73;73:314)(AP74;74:116)(AP75;75:315)(AP76;76:119)(AP77;77:312)(AP78;78:118)(AP79;79:313)(AP80;80:121)(AP81;81:310)(AP82;82:120)(AP83;83:311)(AP84;84:123)(AP85;85:308)(AP86;86:122)(AP87;87:309)(AP88;88:125)(AP89;89:306)(AP90;90:124)(AP91;91:307)(AP92;92:127)(AP93;93:304)(AP94;94:126)(AP95;95:305)(AP96;96:17)(AP97;97:222)(AP98;98:16)(AP99;99:223)(AP100;100:19)(AP101;101:220)(AP102;102:18)(AP103;103:221)(AP104;104:21)(AP105;105:218)(AP106;106:20)(AP107;107:219)(AP108;108:23)(AP109;109:216)(AP110;110:22)(AP111;111:217)(AP112;112:25)(AP113;113:214)(AP114;114:24)(AP115;115:215)(AP116;116:27)(AP117;117:212)(AP118;118:26)(AP119;119:213)(AP120;120:29)(AP121;121:210)(AP122;122:28)(AP123;123:211)(AP124;124:31)(AP125;125:208)(AP126;126:30)(AP127;127:209)(AP128;128:129)(AP129;129:334)(AP130;130:128)(AP131;131:335)(AP132;132:131)(AP133;133:332)(AP134;134:130)(AP135;135:333)(AP136;136:133)(AP137;137:330)(AP138;138:132)(AP139;139:331)(AP140;140:135)(AP141;141:328)(AP142;142:134)(AP143;143:329)(AP144;144:137)(AP145;145:326)(AP146;146:136)(AP147;147:327)(AP148;148:139)(AP149;149:324)(AP150;150:138)(AP151;151:325)(AP152;152:141)(AP153;153:322)(AP154;154:140)(AP155;155:323)(AP156;156:143)(AP157;157:320)(AP158;158:142)(AP159;159:321)(AP160;160:33)(AP161;161:238)(AP162;162:32)(AP163;163:239)(AP164;164:35)(AP165;165:236)(AP166;166:34)(AP167;167:237)(AP168;168:37)(AP169;169:234)(AP170;170:36)(AP171;171:235)(AP172;172:39)(AP173;173:232)(AP174;174:38)(AP175;175:233)(AP176;176:41)(AP177;177:230)(AP178;178:40)(AP179;179:231)(AP180;180:43)(AP181;181:228)(AP182;182:42)(AP183;183:229)(AP184;184:45)(AP185;185:226)(AP186;186:44)(AP187;187:227)(AP188;188:47)(AP189;189:224)(AP190;190:46)(AP191;191:225)(AP192;192:145)(AP193;193:350)(AP194;194:144)(AP195;195:351)(AP196;196:147)(AP197;197:348)(AP198;198:146)(AP199;199:349)(AP200;200:149)(AP201;201:346)(AP202;202:148)(AP203;203:347)(AP204;204:151)(AP205;205:344)(AP206;206:150)(AP207;207:345)(AP208;208:153)(AP209;209:342)(AP210;210:152)(AP211;211:343)(AP212;212:155)(AP213;213:340)(AP214;214:154)(AP215;215:341)(AP216;216:157)(AP217;217:338)(AP218;218:156)(AP219;219:339)(AP220;220:159)(AP221;221:336)(AP222;222:158)(AP223;223:337)(AP224;224:49)(AP225;225:254)(AP226;226:48)(AP227;227:255)(AP228;228:51)(AP229;229:252)(AP230;230:50)(AP231;231:253)(AP232;232:53)(AP233;233:250)(AP234;234:52)(AP235;235:251)(AP236;236:55)(AP237;237:248)(AP238;238:54)(AP239;239:249)(AP240;240:57)(AP241;241:246)(AP242;242:56)(AP243;243:247)(AP244;244:59)(AP245;245:244)(AP246;246:58)(AP247;247:245)(AP248;248:61)(AP249;249:242)(AP250;250:60)(AP251;251:243)(AP252;252:63)(AP253;253:240)(AP254;254:62)(AP255;255:241)(AP256;256:161)(AP257;257:366)(AP258;258:160)(AP259;259:367)(AP260;260:163)(AP261;261:364)(AP262;262:162)(AP263;263:365)(AP264;264:165)(AP265;265:362)(AP266;266:164)(AP267;267:363)(AP268;268:167)(AP269;269:360)(AP270;270:166)(AP271;271:361)(AP272;272:169)(AP273;273:358)(AP274;274:168)(AP275;275:359)(AP276;276:171)(AP277;277:356)(AP278;278:170)(AP279;279:357)(AP280;280:173)(AP281;281:354)(AP282;282:172)(AP283;283:355)(AP284;284:175)(AP285;285:352)(AP286;286:174)(AP287;287:353)(AP288;288:65)(AP289;289:270)(AP290;290:64)(AP291;291:271)(AP292;292:67)(AP293;293:268)(AP294;294:66)(AP295;295:269)(AP296;296:69)(AP297;297:266)(AP298;298:68)(AP299;299:267)(AP300;300:71)(AP301;301:264)(AP302;302:70)(AP303;303:265)(AP304;304:73)(AP305;305:262)(AP306;306:72)(AP307;307:263)(AP308;308:75)(AP309;309:260)(AP310;310:74)(AP311;311:261)(AP312;312:77)(AP313;313:258)(AP314;314:76)(AP315;315:259)(AP316;316:79)(AP317;317:256)(AP318;318:78)(AP319;319:257)(AP320;320:177)(AP321;321:382)(AP322;322:176)(AP323;323:383)(AP324;324:179)(AP325;325:380)(AP326;326:178)(AP327;327:381)(AP328;328:181)(AP329;329:378)(AP330;330:180)(AP331;331:379)(AP332;332:183)(AP333;333:376)(AP334;334:182)(AP335;335:377)(AP336;336:185)(AP337;337:374)(AP338;338:184)(AP339;339:375)(AP340;340:187)(AP341;341:372)(AP342;342:186)(AP343;343:373)(AP344;344:189)(AP345;345:370)(AP346;346:188)(AP347;347:371)(AP348;348:191)(AP349;349:368)(AP350;350:190)(AP351;351:369)(AP352;352:81)(AP353;353:286)(AP354;354:80)(AP355;355:287)(AP356;356:83)(AP357;357:284)(AP358;358:82)(AP359;359:285)(AP360;360:85)(AP361;361:282)(AP362;362:84)(AP363;363:283)(AP364;364:87)(AP365;365:280)(AP366;366:86)(AP367;367:281)(AP368;368:89)(AP369;369:278)(AP370;370:88)(AP371;371:279)(AP372;372:91)(AP373;373:276)(AP374;374:90)(AP375;375:277)(AP376;376:93)(AP377;377:274)(AP378;378:92)(AP379;379:275)(AP380;380:95)(AP381;381:272)(AP382;382:94)(AP383;383:273)(SY0;768:768) +~snsGeomMap=(NP1110,1,0,73)(0:33.5:1440:1)(0:39.5:2058:1)(0:27.5:1440:1)(0:45.5:2058:1)(0:33.5:1446:1)(0:39.5:2052:1)(0:27.5:1446:1)(0:45.5:2052:1)(0:33.5:1452:1)(0:39.5:2046:1)(0:27.5:1452:1)(0:45.5:2046:1)(0:33.5:1458:1)(0:39.5:2040:1)(0:27.5:1458:1)(0:45.5:2040:1)(0:33.5:1464:1)(0:39.5:2034:1)(0:27.5:1464:1)(0:45.5:2034:1)(0:33.5:1470:1)(0:39.5:2028:1)(0:27.5:1470:1)(0:45.5:2028:1)(0:33.5:1476:1)(0:39.5:2022:1)(0:27.5:1476:1)(0:45.5:2022:1)(0:33.5:1482:1)(0:39.5:2016:1)(0:27.5:1482:1)(0:45.5:2016:1)(0:33.5:1152:1)(0:39.5:1770:1)(0:27.5:1152:1)(0:45.5:1770:1)(0:33.5:1158:1)(0:39.5:1764:1)(0:27.5:1158:1)(0:45.5:1764:1)(0:33.5:1164:1)(0:39.5:1758:1)(0:27.5:1164:1)(0:45.5:1758:1)(0:33.5:1170:1)(0:39.5:1752:1)(0:27.5:1170:1)(0:45.5:1752:1)(0:33.5:1176:1)(0:39.5:1746:1)(0:27.5:1176:1)(0:45.5:1746:1)(0:33.5:1182:1)(0:39.5:1740:1)(0:27.5:1182:1)(0:45.5:1740:1)(0:33.5:1188:1)(0:39.5:1734:1)(0:27.5:1188:1)(0:45.5:1734:1)(0:33.5:1194:1)(0:39.5:1728:1)(0:27.5:1194:1)(0:45.5:1728:1)(0:33.5:1488:1)(0:39.5:2106:1)(0:27.5:1488:1)(0:45.5:2106:1)(0:33.5:1494:1)(0:39.5:2100:1)(0:27.5:1494:1)(0:45.5:2100:1)(0:33.5:1500:1)(0:39.5:2094:1)(0:27.5:1500:1)(0:45.5:2094:1)(0:33.5:1506:1)(0:39.5:2088:1)(0:27.5:1506:1)(0:45.5:2088:1)(0:33.5:1512:1)(0:39.5:2082:1)(0:27.5:1512:1)(0:45.5:2082:1)(0:33.5:1518:1)(0:39.5:2076:1)(0:27.5:1518:1)(0:45.5:2076:1)(0:33.5:1524:1)(0:39.5:2070:1)(0:27.5:1524:1)(0:45.5:2070:1)(0:33.5:1530:1)(0:39.5:2064:1)(0:27.5:1530:1)(0:45.5:2064:1)(0:33.5:1200:1)(0:39.5:1818:1)(0:27.5:1200:1)(0:45.5:1818:1)(0:33.5:1206:1)(0:39.5:1812:1)(0:27.5:1206:1)(0:45.5:1812:1)(0:33.5:1212:1)(0:39.5:1806:1)(0:27.5:1212:1)(0:45.5:1806:1)(0:33.5:1218:1)(0:39.5:1800:1)(0:27.5:1218:1)(0:45.5:1800:1)(0:33.5:1224:1)(0:39.5:1794:1)(0:27.5:1224:1)(0:45.5:1794:1)(0:33.5:1230:1)(0:39.5:1788:1)(0:27.5:1230:1)(0:45.5:1788:1)(0:33.5:1236:1)(0:39.5:1782:1)(0:27.5:1236:1)(0:45.5:1782:1)(0:33.5:1242:1)(0:39.5:1776:1)(0:27.5:1242:1)(0:45.5:1776:1)(0:33.5:1536:1)(0:39.5:2154:1)(0:27.5:1536:1)(0:45.5:2154:1)(0:33.5:1542:1)(0:39.5:2148:1)(0:27.5:1542:1)(0:45.5:2148:1)(0:33.5:1548:1)(0:39.5:2142:1)(0:27.5:1548:1)(0:45.5:2142:1)(0:33.5:1554:1)(0:39.5:2136:1)(0:27.5:1554:1)(0:45.5:2136:1)(0:33.5:1560:1)(0:39.5:2130:1)(0:27.5:1560:1)(0:45.5:2130:1)(0:33.5:1566:1)(0:39.5:2124:1)(0:27.5:1566:1)(0:45.5:2124:1)(0:33.5:1572:1)(0:39.5:2118:1)(0:27.5:1572:1)(0:45.5:2118:1)(0:33.5:1578:1)(0:39.5:2112:1)(0:27.5:1578:1)(0:45.5:2112:1)(0:33.5:1248:1)(0:39.5:1866:1)(0:27.5:1248:1)(0:45.5:1866:1)(0:33.5:1254:1)(0:39.5:1860:1)(0:27.5:1254:1)(0:45.5:1860:1)(0:33.5:1260:1)(0:39.5:1854:1)(0:27.5:1260:1)(0:45.5:1854:1)(0:33.5:1266:1)(0:39.5:1848:1)(0:27.5:1266:1)(0:45.5:1848:1)(0:33.5:1272:1)(0:39.5:1842:1)(0:27.5:1272:1)(0:45.5:1842:1)(0:33.5:1278:1)(0:39.5:1836:1)(0:27.5:1278:1)(0:45.5:1836:1)(0:33.5:1284:1)(0:39.5:1830:1)(0:27.5:1284:1)(0:45.5:1830:1)(0:33.5:1290:1)(0:39.5:1824:1)(0:27.5:1290:1)(0:45.5:1824:1)(0:33.5:1584:1)(0:39.5:2202:1)(0:27.5:1584:1)(0:45.5:2202:1)(0:33.5:1590:1)(0:39.5:2196:1)(0:27.5:1590:1)(0:45.5:2196:1)(0:33.5:1596:1)(0:39.5:2190:1)(0:27.5:1596:1)(0:45.5:2190:1)(0:33.5:1602:1)(0:39.5:2184:1)(0:27.5:1602:1)(0:45.5:2184:1)(0:33.5:1608:1)(0:39.5:2178:1)(0:27.5:1608:1)(0:45.5:2178:1)(0:33.5:1614:1)(0:39.5:2172:1)(0:27.5:1614:1)(0:45.5:2172:1)(0:33.5:1620:1)(0:39.5:2166:1)(0:27.5:1620:1)(0:45.5:2166:1)(0:33.5:1626:1)(0:39.5:2160:1)(0:27.5:1626:1)(0:45.5:2160:1)(0:33.5:1296:1)(0:39.5:1914:1)(0:27.5:1296:1)(0:45.5:1914:1)(0:33.5:1302:1)(0:39.5:1908:1)(0:27.5:1302:1)(0:45.5:1908:1)(0:33.5:1308:1)(0:39.5:1902:1)(0:27.5:1308:1)(0:45.5:1902:1)(0:33.5:1314:1)(0:39.5:1896:1)(0:27.5:1314:1)(0:45.5:1896:1)(0:33.5:1320:1)(0:39.5:1890:1)(0:27.5:1320:1)(0:45.5:1890:1)(0:33.5:1326:1)(0:39.5:1884:1)(0:27.5:1326:1)(0:45.5:1884:1)(0:33.5:1332:1)(0:39.5:1878:1)(0:27.5:1332:1)(0:45.5:1878:1)(0:33.5:1338:1)(0:39.5:1872:1)(0:27.5:1338:1)(0:45.5:1872:1)(0:33.5:1632:1)(0:39.5:2250:1)(0:27.5:1632:1)(0:45.5:2250:1)(0:33.5:1638:1)(0:39.5:2244:1)(0:27.5:1638:1)(0:45.5:2244:1)(0:33.5:1644:1)(0:39.5:2238:1)(0:27.5:1644:1)(0:45.5:2238:1)(0:33.5:1650:1)(0:39.5:2232:1)(0:27.5:1650:1)(0:45.5:2232:1)(0:33.5:1656:1)(0:39.5:2226:1)(0:27.5:1656:1)(0:45.5:2226:1)(0:33.5:1662:1)(0:39.5:2220:1)(0:27.5:1662:1)(0:45.5:2220:1)(0:33.5:1668:1)(0:39.5:2214:1)(0:27.5:1668:1)(0:45.5:2214:1)(0:33.5:1674:1)(0:39.5:2208:1)(0:27.5:1674:1)(0:45.5:2208:1)(0:33.5:1344:1)(0:39.5:1962:1)(0:27.5:1344:1)(0:45.5:1962:1)(0:33.5:1350:1)(0:39.5:1956:1)(0:27.5:1350:1)(0:45.5:1956:1)(0:33.5:1356:1)(0:39.5:1950:1)(0:27.5:1356:1)(0:45.5:1950:1)(0:33.5:1362:1)(0:39.5:1944:1)(0:27.5:1362:1)(0:45.5:1944:1)(0:33.5:1368:1)(0:39.5:1938:1)(0:27.5:1368:1)(0:45.5:1938:1)(0:33.5:1374:1)(0:39.5:1932:1)(0:27.5:1374:1)(0:45.5:1932:1)(0:33.5:1380:1)(0:39.5:1926:1)(0:27.5:1380:1)(0:45.5:1926:1)(0:33.5:1386:1)(0:39.5:1920:1)(0:27.5:1386:1)(0:45.5:1920:1)(0:33.5:1680:1)(0:39.5:2298:1)(0:27.5:1680:1)(0:45.5:2298:1)(0:33.5:1686:1)(0:39.5:2292:1)(0:27.5:1686:1)(0:45.5:2292:1)(0:33.5:1692:1)(0:39.5:2286:1)(0:27.5:1692:1)(0:45.5:2286:1)(0:33.5:1698:1)(0:39.5:2280:1)(0:27.5:1698:1)(0:45.5:2280:1)(0:33.5:1704:1)(0:39.5:2274:1)(0:27.5:1704:1)(0:45.5:2274:1)(0:33.5:1710:1)(0:39.5:2268:1)(0:27.5:1710:1)(0:45.5:2268:1)(0:33.5:1716:1)(0:39.5:2262:1)(0:27.5:1716:1)(0:45.5:2262:1)(0:33.5:1722:1)(0:39.5:2256:1)(0:27.5:1722:1)(0:45.5:2256:1)(0:33.5:1392:1)(0:39.5:2010:1)(0:27.5:1392:1)(0:45.5:2010:1)(0:33.5:1398:1)(0:39.5:2004:1)(0:27.5:1398:1)(0:45.5:2004:1)(0:33.5:1404:1)(0:39.5:1998:1)(0:27.5:1404:1)(0:45.5:1998:1)(0:33.5:1410:1)(0:39.5:1992:1)(0:27.5:1410:1)(0:45.5:1992:1)(0:33.5:1416:1)(0:39.5:1986:1)(0:27.5:1416:1)(0:45.5:1986:1)(0:33.5:1422:1)(0:39.5:1980:1)(0:27.5:1422:1)(0:45.5:1980:1)(0:33.5:1428:1)(0:39.5:1974:1)(0:27.5:1428:1)(0:45.5:1974:1)(0:33.5:1434:1)(0:39.5:1968:1)(0:27.5:1434:1)(0:45.5:1968:1) diff --git a/tests/data/spikeglx/NP1110_bank0_g0_t0.imec0.ap.meta b/tests/data/spikeglx/NP1110_bank0_g0_t0.imec0.ap.meta new file mode 100644 index 00000000..b41a6f8e --- /dev/null +++ b/tests/data/spikeglx/NP1110_bank0_g0_t0.imec0.ap.meta @@ -0,0 +1,77 @@ +acqApLfSy=384,384,1 +appVersion=20251218 +fileCreateTime=2026-03-19T22:16:27 +fileName=C:/SGL_DATA/031826_wireless/np1110_bank0_g0/np1110_bank0_g0_imec0/np1110_bank0_g0_t0.imec0.ap.bin +fileSHA1=39A36A17F58B3587D882B6B2C34CFB3492F3DE31 +fileSizeBytes=378673680 +fileTimeSecs=16.3928 +firstSample=1462140 +gateMode=Immediate +imAiRangeMax=0.6 +imAiRangeMin=-0.6 +imAnyChanFullBand=false +imCalibrated=false +imChan0Ref=ext +imChan0apGain=500 +imColsPerShank=8 +imDatApi=4.1.3 +imDatBs_fw=3.0.226 +imDatBsc_fw=4.0.233 +imDatBsc_hw=1.9 +imDatBsc_pn=NP2_QBSC_00 +imDatBsc_sn=175 +imDatBsc_tech=std +imDatFx_hw=4.13 +imDatFx_pn=NP2_FLEX_0 +imDatFx_sn=0 +imDatHs_fw=0.0.0 +imDatHs_hw=4.9 +imDatHs_pn=NP2_HS_30 +imDatHs_sn=46 +imDatPrb_dock=1 +imDatPrb_pn=NP1110 +imDatPrb_port=4 +imDatPrb_slot=5 +imDatPrb_sn=21144116901 +imDatPrb_sr_mask=1 +imDatPrb_sr_nok=1 +imDatPrb_tech=std +imDatPrb_type=1110 +imErrFlags0_IS_CT_SR_LK_PP_SY_MS=0 0 0 0 0 0 0 +imIsSvyRun=false +imLEDEnable=false +imLowLatency=false +imMaxInt=512 +imRowsPerShank=768 +imSRAtDetect=true +imSampRate=30000 +imStdby= +imSvyMaxBnk=-1 +imSvyNShanks=1 +imSvySecPerBnk=60 +imSvySettleSec=2 +imTipLength=203.5 +imTrgRising=true +imTrgSource=0 +imX0EvenRow=15.5 +imX0OddRow=15.5 +imXPitch=6 +imZPitch=6 +imroFile= +nDataDirs=1 +nSavedChans=385 +snsApLfSy=384,0,1 +snsSaveChanSubset=0:383,768 +syncImInputSlot=5 +syncSourceIdx=3 +syncSourcePeriod=2 +trigMode=Immediate +typeImEnabled=1 +typeNiEnabled=0 +typeObEnabled=0 +typeThis=imec +userNotes= +~imroTbl=(1110,2,0,500,250,1)(0 0 0)(1 0 0)(2 0 0)(3 0 0)(4 0 0)(5 0 0)(6 0 0)(7 0 0)(8 0 0)(9 0 0)(10 0 0)(11 0 0)(12 0 0)(13 0 0)(14 0 0)(15 0 0)(16 0 0)(17 0 0)(18 0 0)(19 0 0)(20 0 0)(21 0 0)(22 0 0)(23 0 0) +~muxTbl=(32,12)(0 1 24 25 48 49 72 73 96 97 120 121 144 145 168 169 192 193 216 217 240 241 264 265 288 289 312 313 336 337 360 361)(2 3 26 27 50 51 74 75 98 99 122 123 146 147 170 171 194 195 218 219 242 243 266 267 290 291 314 315 338 339 362 363)(4 5 28 29 52 53 76 77 100 101 124 125 148 149 172 173 196 197 220 221 244 245 268 269 292 293 316 317 340 341 364 365)(6 7 30 31 54 55 78 79 102 103 126 127 150 151 174 175 198 199 222 223 246 247 270 271 294 295 318 319 342 343 366 367)(8 9 32 33 56 57 80 81 104 105 128 129 152 153 176 177 200 201 224 225 248 249 272 273 296 297 320 321 344 345 368 369)(10 11 34 35 58 59 82 83 106 107 130 131 154 155 178 179 202 203 226 227 250 251 274 275 298 299 322 323 346 347 370 371)(12 13 36 37 60 61 84 85 108 109 132 133 156 157 180 181 204 205 228 229 252 253 276 277 300 301 324 325 348 349 372 373)(14 15 38 39 62 63 86 87 110 111 134 135 158 159 182 183 206 207 230 231 254 255 278 279 302 303 326 327 350 351 374 375)(16 17 40 41 64 65 88 89 112 113 136 137 160 161 184 185 208 209 232 233 256 257 280 281 304 305 328 329 352 353 376 377)(18 19 42 43 66 67 90 91 114 115 138 139 162 163 186 187 210 211 234 235 258 259 282 283 306 307 330 331 354 355 378 379)(20 21 44 45 68 69 92 93 116 117 140 141 164 165 188 189 212 213 236 237 260 261 284 285 308 309 332 333 356 357 380 381)(22 23 46 47 70 71 94 95 118 119 142 143 166 167 190 191 214 215 238 239 262 263 286 287 310 311 334 335 358 359 382 383) +~snsChanMap=(384,384,1)(AP0;0:0)(AP1;1:63)(AP2;2:1)(AP3;3:62)(AP4;4:8)(AP5;5:55)(AP6;6:9)(AP7;7:54)(AP8;8:16)(AP9;9:47)(AP10;10:17)(AP11;11:46)(AP12;12:24)(AP13;13:39)(AP14;14:25)(AP15;15:38)(AP16;16:32)(AP17;17:31)(AP18;18:33)(AP19;19:30)(AP20;20:40)(AP21;21:23)(AP22;22:41)(AP23;23:22)(AP24;24:48)(AP25;25:15)(AP26;26:49)(AP27;27:14)(AP28;28:56)(AP29;29:7)(AP30;30:57)(AP31;31:6)(AP32;32:2)(AP33;33:61)(AP34;34:3)(AP35;35:60)(AP36;36:10)(AP37;37:53)(AP38;38:11)(AP39;39:52)(AP40;40:18)(AP41;41:45)(AP42;42:19)(AP43;43:44)(AP44;44:26)(AP45;45:37)(AP46;46:27)(AP47;47:36)(AP48;48:34)(AP49;49:29)(AP50;50:35)(AP51;51:28)(AP52;52:42)(AP53;53:21)(AP54;54:43)(AP55;55:20)(AP56;56:50)(AP57;57:13)(AP58;58:51)(AP59;59:12)(AP60;60:58)(AP61;61:5)(AP62;62:59)(AP63;63:4)(AP64;64:64)(AP65;65:127)(AP66;66:65)(AP67;67:126)(AP68;68:72)(AP69;69:119)(AP70;70:73)(AP71;71:118)(AP72;72:80)(AP73;73:111)(AP74;74:81)(AP75;75:110)(AP76;76:88)(AP77;77:103)(AP78;78:89)(AP79;79:102)(AP80;80:96)(AP81;81:95)(AP82;82:97)(AP83;83:94)(AP84;84:104)(AP85;85:87)(AP86;86:105)(AP87;87:86)(AP88;88:112)(AP89;89:79)(AP90;90:113)(AP91;91:78)(AP92;92:120)(AP93;93:71)(AP94;94:121)(AP95;95:70)(AP96;96:66)(AP97;97:125)(AP98;98:67)(AP99;99:124)(AP100;100:74)(AP101;101:117)(AP102;102:75)(AP103;103:116)(AP104;104:82)(AP105;105:109)(AP106;106:83)(AP107;107:108)(AP108;108:90)(AP109;109:101)(AP110;110:91)(AP111;111:100)(AP112;112:98)(AP113;113:93)(AP114;114:99)(AP115;115:92)(AP116;116:106)(AP117;117:85)(AP118;118:107)(AP119;119:84)(AP120;120:114)(AP121;121:77)(AP122;122:115)(AP123;123:76)(AP124;124:122)(AP125;125:69)(AP126;126:123)(AP127;127:68)(AP128;128:128)(AP129;129:191)(AP130;130:129)(AP131;131:190)(AP132;132:136)(AP133;133:183)(AP134;134:137)(AP135;135:182)(AP136;136:144)(AP137;137:175)(AP138;138:145)(AP139;139:174)(AP140;140:152)(AP141;141:167)(AP142;142:153)(AP143;143:166)(AP144;144:160)(AP145;145:159)(AP146;146:161)(AP147;147:158)(AP148;148:168)(AP149;149:151)(AP150;150:169)(AP151;151:150)(AP152;152:176)(AP153;153:143)(AP154;154:177)(AP155;155:142)(AP156;156:184)(AP157;157:135)(AP158;158:185)(AP159;159:134)(AP160;160:130)(AP161;161:189)(AP162;162:131)(AP163;163:188)(AP164;164:138)(AP165;165:181)(AP166;166:139)(AP167;167:180)(AP168;168:146)(AP169;169:173)(AP170;170:147)(AP171;171:172)(AP172;172:154)(AP173;173:165)(AP174;174:155)(AP175;175:164)(AP176;176:162)(AP177;177:157)(AP178;178:163)(AP179;179:156)(AP180;180:170)(AP181;181:149)(AP182;182:171)(AP183;183:148)(AP184;184:178)(AP185;185:141)(AP186;186:179)(AP187;187:140)(AP188;188:186)(AP189;189:133)(AP190;190:187)(AP191;191:132)(AP192;192:192)(AP193;193:255)(AP194;194:193)(AP195;195:254)(AP196;196:200)(AP197;197:247)(AP198;198:201)(AP199;199:246)(AP200;200:208)(AP201;201:239)(AP202;202:209)(AP203;203:238)(AP204;204:216)(AP205;205:231)(AP206;206:217)(AP207;207:230)(AP208;208:224)(AP209;209:223)(AP210;210:225)(AP211;211:222)(AP212;212:232)(AP213;213:215)(AP214;214:233)(AP215;215:214)(AP216;216:240)(AP217;217:207)(AP218;218:241)(AP219;219:206)(AP220;220:248)(AP221;221:199)(AP222;222:249)(AP223;223:198)(AP224;224:194)(AP225;225:253)(AP226;226:195)(AP227;227:252)(AP228;228:202)(AP229;229:245)(AP230;230:203)(AP231;231:244)(AP232;232:210)(AP233;233:237)(AP234;234:211)(AP235;235:236)(AP236;236:218)(AP237;237:229)(AP238;238:219)(AP239;239:228)(AP240;240:226)(AP241;241:221)(AP242;242:227)(AP243;243:220)(AP244;244:234)(AP245;245:213)(AP246;246:235)(AP247;247:212)(AP248;248:242)(AP249;249:205)(AP250;250:243)(AP251;251:204)(AP252;252:250)(AP253;253:197)(AP254;254:251)(AP255;255:196)(AP256;256:256)(AP257;257:319)(AP258;258:257)(AP259;259:318)(AP260;260:264)(AP261;261:311)(AP262;262:265)(AP263;263:310)(AP264;264:272)(AP265;265:303)(AP266;266:273)(AP267;267:302)(AP268;268:280)(AP269;269:295)(AP270;270:281)(AP271;271:294)(AP272;272:288)(AP273;273:287)(AP274;274:289)(AP275;275:286)(AP276;276:296)(AP277;277:279)(AP278;278:297)(AP279;279:278)(AP280;280:304)(AP281;281:271)(AP282;282:305)(AP283;283:270)(AP284;284:312)(AP285;285:263)(AP286;286:313)(AP287;287:262)(AP288;288:258)(AP289;289:317)(AP290;290:259)(AP291;291:316)(AP292;292:266)(AP293;293:309)(AP294;294:267)(AP295;295:308)(AP296;296:274)(AP297;297:301)(AP298;298:275)(AP299;299:300)(AP300;300:282)(AP301;301:293)(AP302;302:283)(AP303;303:292)(AP304;304:290)(AP305;305:285)(AP306;306:291)(AP307;307:284)(AP308;308:298)(AP309;309:277)(AP310;310:299)(AP311;311:276)(AP312;312:306)(AP313;313:269)(AP314;314:307)(AP315;315:268)(AP316;316:314)(AP317;317:261)(AP318;318:315)(AP319;319:260)(AP320;320:320)(AP321;321:383)(AP322;322:321)(AP323;323:382)(AP324;324:328)(AP325;325:375)(AP326;326:329)(AP327;327:374)(AP328;328:336)(AP329;329:367)(AP330;330:337)(AP331;331:366)(AP332;332:344)(AP333;333:359)(AP334;334:345)(AP335;335:358)(AP336;336:352)(AP337;337:351)(AP338;338:353)(AP339;339:350)(AP340;340:360)(AP341;341:343)(AP342;342:361)(AP343;343:342)(AP344;344:368)(AP345;345:335)(AP346;346:369)(AP347;347:334)(AP348;348:376)(AP349;349:327)(AP350;350:377)(AP351;351:326)(AP352;352:322)(AP353;353:381)(AP354;354:323)(AP355;355:380)(AP356;356:330)(AP357;357:373)(AP358;358:331)(AP359;359:372)(AP360;360:338)(AP361;361:365)(AP362;362:339)(AP363;363:364)(AP364;364:346)(AP365;365:357)(AP366;366:347)(AP367;367:356)(AP368;368:354)(AP369;369:349)(AP370;370:355)(AP371;371:348)(AP372;372:362)(AP373;373:341)(AP374;374:363)(AP375;375:340)(AP376;376:370)(AP377;377:333)(AP378;378:371)(AP379;379:332)(AP380;380:378)(AP381;381:325)(AP382;382:379)(AP383;383:324)(SY0;768:768) +~snsGeomMap=(NP1110,1,0,73)(0:15.5:0:1)(0:57.5:42:1)(0:21.5:0:1)(0:51.5:42:1)(0:15.5:6:1)(0:57.5:36:1)(0:21.5:6:1)(0:51.5:36:1)(0:15.5:12:1)(0:57.5:30:1)(0:21.5:12:1)(0:51.5:30:1)(0:15.5:18:1)(0:57.5:24:1)(0:21.5:18:1)(0:51.5:24:1)(0:15.5:24:1)(0:57.5:18:1)(0:21.5:24:1)(0:51.5:18:1)(0:15.5:30:1)(0:57.5:12:1)(0:21.5:30:1)(0:51.5:12:1)(0:15.5:36:1)(0:57.5:6:1)(0:21.5:36:1)(0:51.5:6:1)(0:15.5:42:1)(0:57.5:0:1)(0:21.5:42:1)(0:51.5:0:1)(0:27.5:0:1)(0:45.5:42:1)(0:33.5:0:1)(0:39.5:42:1)(0:27.5:6:1)(0:45.5:36:1)(0:33.5:6:1)(0:39.5:36:1)(0:27.5:12:1)(0:45.5:30:1)(0:33.5:12:1)(0:39.5:30:1)(0:27.5:18:1)(0:45.5:24:1)(0:33.5:18:1)(0:39.5:24:1)(0:27.5:24:1)(0:45.5:18:1)(0:33.5:24:1)(0:39.5:18:1)(0:27.5:30:1)(0:45.5:12:1)(0:33.5:30:1)(0:39.5:12:1)(0:27.5:36:1)(0:45.5:6:1)(0:33.5:36:1)(0:39.5:6:1)(0:27.5:42:1)(0:45.5:0:1)(0:33.5:42:1)(0:39.5:0:1)(0:15.5:48:1)(0:57.5:90:1)(0:21.5:48:1)(0:51.5:90:1)(0:15.5:54:1)(0:57.5:84:1)(0:21.5:54:1)(0:51.5:84:1)(0:15.5:60:1)(0:57.5:78:1)(0:21.5:60:1)(0:51.5:78:1)(0:15.5:66:1)(0:57.5:72:1)(0:21.5:66:1)(0:51.5:72:1)(0:15.5:72:1)(0:57.5:66:1)(0:21.5:72:1)(0:51.5:66:1)(0:15.5:78:1)(0:57.5:60:1)(0:21.5:78:1)(0:51.5:60:1)(0:15.5:84:1)(0:57.5:54:1)(0:21.5:84:1)(0:51.5:54:1)(0:15.5:90:1)(0:57.5:48:1)(0:21.5:90:1)(0:51.5:48:1)(0:27.5:48:1)(0:45.5:90:1)(0:33.5:48:1)(0:39.5:90:1)(0:27.5:54:1)(0:45.5:84:1)(0:33.5:54:1)(0:39.5:84:1)(0:27.5:60:1)(0:45.5:78:1)(0:33.5:60:1)(0:39.5:78:1)(0:27.5:66:1)(0:45.5:72:1)(0:33.5:66:1)(0:39.5:72:1)(0:27.5:72:1)(0:45.5:66:1)(0:33.5:72:1)(0:39.5:66:1)(0:27.5:78:1)(0:45.5:60:1)(0:33.5:78:1)(0:39.5:60:1)(0:27.5:84:1)(0:45.5:54:1)(0:33.5:84:1)(0:39.5:54:1)(0:27.5:90:1)(0:45.5:48:1)(0:33.5:90:1)(0:39.5:48:1)(0:15.5:96:1)(0:57.5:138:1)(0:21.5:96:1)(0:51.5:138:1)(0:15.5:102:1)(0:57.5:132:1)(0:21.5:102:1)(0:51.5:132:1)(0:15.5:108:1)(0:57.5:126:1)(0:21.5:108:1)(0:51.5:126:1)(0:15.5:114:1)(0:57.5:120:1)(0:21.5:114:1)(0:51.5:120:1)(0:15.5:120:1)(0:57.5:114:1)(0:21.5:120:1)(0:51.5:114:1)(0:15.5:126:1)(0:57.5:108:1)(0:21.5:126:1)(0:51.5:108:1)(0:15.5:132:1)(0:57.5:102:1)(0:21.5:132:1)(0:51.5:102:1)(0:15.5:138:1)(0:57.5:96:1)(0:21.5:138:1)(0:51.5:96:1)(0:27.5:96:1)(0:45.5:138:1)(0:33.5:96:1)(0:39.5:138:1)(0:27.5:102:1)(0:45.5:132:1)(0:33.5:102:1)(0:39.5:132:1)(0:27.5:108:1)(0:45.5:126:1)(0:33.5:108:1)(0:39.5:126:1)(0:27.5:114:1)(0:45.5:120:1)(0:33.5:114:1)(0:39.5:120:1)(0:27.5:120:1)(0:45.5:114:1)(0:33.5:120:1)(0:39.5:114:1)(0:27.5:126:1)(0:45.5:108:1)(0:33.5:126:1)(0:39.5:108:1)(0:27.5:132:1)(0:45.5:102:1)(0:33.5:132:1)(0:39.5:102:1)(0:27.5:138:1)(0:45.5:96:1)(0:33.5:138:1)(0:39.5:96:1)(0:15.5:144:1)(0:57.5:186:1)(0:21.5:144:1)(0:51.5:186:1)(0:15.5:150:1)(0:57.5:180:1)(0:21.5:150:1)(0:51.5:180:1)(0:15.5:156:1)(0:57.5:174:1)(0:21.5:156:1)(0:51.5:174:1)(0:15.5:162:1)(0:57.5:168:1)(0:21.5:162:1)(0:51.5:168:1)(0:15.5:168:1)(0:57.5:162:1)(0:21.5:168:1)(0:51.5:162:1)(0:15.5:174:1)(0:57.5:156:1)(0:21.5:174:1)(0:51.5:156:1)(0:15.5:180:1)(0:57.5:150:1)(0:21.5:180:1)(0:51.5:150:1)(0:15.5:186:1)(0:57.5:144:1)(0:21.5:186:1)(0:51.5:144:1)(0:27.5:144:1)(0:45.5:186:1)(0:33.5:144:1)(0:39.5:186:1)(0:27.5:150:1)(0:45.5:180:1)(0:33.5:150:1)(0:39.5:180:1)(0:27.5:156:1)(0:45.5:174:1)(0:33.5:156:1)(0:39.5:174:1)(0:27.5:162:1)(0:45.5:168:1)(0:33.5:162:1)(0:39.5:168:1)(0:27.5:168:1)(0:45.5:162:1)(0:33.5:168:1)(0:39.5:162:1)(0:27.5:174:1)(0:45.5:156:1)(0:33.5:174:1)(0:39.5:156:1)(0:27.5:180:1)(0:45.5:150:1)(0:33.5:180:1)(0:39.5:150:1)(0:27.5:186:1)(0:45.5:144:1)(0:33.5:186:1)(0:39.5:144:1)(0:15.5:192:1)(0:57.5:234:1)(0:21.5:192:1)(0:51.5:234:1)(0:15.5:198:1)(0:57.5:228:1)(0:21.5:198:1)(0:51.5:228:1)(0:15.5:204:1)(0:57.5:222:1)(0:21.5:204:1)(0:51.5:222:1)(0:15.5:210:1)(0:57.5:216:1)(0:21.5:210:1)(0:51.5:216:1)(0:15.5:216:1)(0:57.5:210:1)(0:21.5:216:1)(0:51.5:210:1)(0:15.5:222:1)(0:57.5:204:1)(0:21.5:222:1)(0:51.5:204:1)(0:15.5:228:1)(0:57.5:198:1)(0:21.5:228:1)(0:51.5:198:1)(0:15.5:234:1)(0:57.5:192:1)(0:21.5:234:1)(0:51.5:192:1)(0:27.5:192:1)(0:45.5:234:1)(0:33.5:192:1)(0:39.5:234:1)(0:27.5:198:1)(0:45.5:228:1)(0:33.5:198:1)(0:39.5:228:1)(0:27.5:204:1)(0:45.5:222:1)(0:33.5:204:1)(0:39.5:222:1)(0:27.5:210:1)(0:45.5:216:1)(0:33.5:210:1)(0:39.5:216:1)(0:27.5:216:1)(0:45.5:210:1)(0:33.5:216:1)(0:39.5:210:1)(0:27.5:222:1)(0:45.5:204:1)(0:33.5:222:1)(0:39.5:204:1)(0:27.5:228:1)(0:45.5:198:1)(0:33.5:228:1)(0:39.5:198:1)(0:27.5:234:1)(0:45.5:192:1)(0:33.5:234:1)(0:39.5:192:1)(0:15.5:240:1)(0:57.5:282:1)(0:21.5:240:1)(0:51.5:282:1)(0:15.5:246:1)(0:57.5:276:1)(0:21.5:246:1)(0:51.5:276:1)(0:15.5:252:1)(0:57.5:270:1)(0:21.5:252:1)(0:51.5:270:1)(0:15.5:258:1)(0:57.5:264:1)(0:21.5:258:1)(0:51.5:264:1)(0:15.5:264:1)(0:57.5:258:1)(0:21.5:264:1)(0:51.5:258:1)(0:15.5:270:1)(0:57.5:252:1)(0:21.5:270:1)(0:51.5:252:1)(0:15.5:276:1)(0:57.5:246:1)(0:21.5:276:1)(0:51.5:246:1)(0:15.5:282:1)(0:57.5:240:1)(0:21.5:282:1)(0:51.5:240:1)(0:27.5:240:1)(0:45.5:282:1)(0:33.5:240:1)(0:39.5:282:1)(0:27.5:246:1)(0:45.5:276:1)(0:33.5:246:1)(0:39.5:276:1)(0:27.5:252:1)(0:45.5:270:1)(0:33.5:252:1)(0:39.5:270:1)(0:27.5:258:1)(0:45.5:264:1)(0:33.5:258:1)(0:39.5:264:1)(0:27.5:264:1)(0:45.5:258:1)(0:33.5:264:1)(0:39.5:258:1)(0:27.5:270:1)(0:45.5:252:1)(0:33.5:270:1)(0:39.5:252:1)(0:27.5:276:1)(0:45.5:246:1)(0:33.5:276:1)(0:39.5:246:1)(0:27.5:282:1)(0:45.5:240:1)(0:33.5:282:1)(0:39.5:240:1) diff --git a/tests/data/spikeglx/NP1110_botrow80_g0_t0.imec0.ap.meta b/tests/data/spikeglx/NP1110_botrow80_g0_t0.imec0.ap.meta new file mode 100644 index 00000000..aa116892 --- /dev/null +++ b/tests/data/spikeglx/NP1110_botrow80_g0_t0.imec0.ap.meta @@ -0,0 +1,77 @@ +acqApLfSy=384,384,1 +appVersion=20251218 +fileCreateTime=2026-03-19T22:18:10 +fileName=C:/SGL_DATA/031826_wireless/np1110_botrow80_g0/np1110_botrow80_g0_imec0/np1110_botrow80_g0_t0.imec0.ap.bin +fileSHA1=053121298F0BDCA3964578E362ED96A3BAC17A32 +fileSizeBytes=151333490 +fileTimeSecs=6.551233333333333 +firstSample=4551371 +gateMode=Immediate +imAiRangeMax=0.6 +imAiRangeMin=-0.6 +imAnyChanFullBand=false +imCalibrated=false +imChan0Ref=ext +imChan0apGain=500 +imColsPerShank=8 +imDatApi=4.1.3 +imDatBs_fw=3.0.226 +imDatBsc_fw=4.0.233 +imDatBsc_hw=1.9 +imDatBsc_pn=NP2_QBSC_00 +imDatBsc_sn=175 +imDatBsc_tech=std +imDatFx_hw=4.13 +imDatFx_pn=NP2_FLEX_0 +imDatFx_sn=0 +imDatHs_fw=0.0.0 +imDatHs_hw=4.9 +imDatHs_pn=NP2_HS_30 +imDatHs_sn=46 +imDatPrb_dock=1 +imDatPrb_pn=NP1110 +imDatPrb_port=4 +imDatPrb_slot=5 +imDatPrb_sn=21144116901 +imDatPrb_sr_mask=1 +imDatPrb_sr_nok=1 +imDatPrb_tech=std +imDatPrb_type=1110 +imErrFlags0_IS_CT_SR_LK_PP_SY_MS=0 0 0 0 0 0 0 +imIsSvyRun=false +imLEDEnable=false +imLowLatency=false +imMaxInt=512 +imRowsPerShank=768 +imSRAtDetect=true +imSampRate=30000 +imStdby= +imSvyMaxBnk=-1 +imSvyNShanks=1 +imSvySecPerBnk=60 +imSvySettleSec=2 +imTipLength=203.5 +imTrgRising=true +imTrgSource=0 +imX0EvenRow=15.5 +imX0OddRow=15.5 +imXPitch=6 +imZPitch=6 +imroFile=C:/Users/labadmin/Desktop/SGLX_top/Release_v20251218-api4/IMRO_Tables/MATLAB_Scripts/NPUHD2_botRow10_ref0.imro +nDataDirs=1 +nSavedChans=385 +snsApLfSy=384,0,1 +snsSaveChanSubset=0:383,768 +syncImInputSlot=5 +syncSourceIdx=3 +syncSourcePeriod=2 +trigMode=Immediate +typeImEnabled=1 +typeNiEnabled=0 +typeObEnabled=0 +typeThis=imec +userNotes= +~imroTbl=(1110,2,0,500,250,1)(0 2 2)(1 2 2)(2 2 2)(3 2 2)(4 2 2)(5 2 2)(6 2 2)(7 2 2)(8 2 2)(9 2 2)(10 2 2)(11 2 2)(12 2 2)(13 2 2)(14 2 2)(15 2 2)(16 1 1)(17 1 1)(18 1 1)(19 1 1)(20 1 1)(21 1 1)(22 1 1)(23 1 1) +~muxTbl=(32,12)(0 1 24 25 48 49 72 73 96 97 120 121 144 145 168 169 192 193 216 217 240 241 264 265 288 289 312 313 336 337 360 361)(2 3 26 27 50 51 74 75 98 99 122 123 146 147 170 171 194 195 218 219 242 243 266 267 290 291 314 315 338 339 362 363)(4 5 28 29 52 53 76 77 100 101 124 125 148 149 172 173 196 197 220 221 244 245 268 269 292 293 316 317 340 341 364 365)(6 7 30 31 54 55 78 79 102 103 126 127 150 151 174 175 198 199 222 223 246 247 270 271 294 295 318 319 342 343 366 367)(8 9 32 33 56 57 80 81 104 105 128 129 152 153 176 177 200 201 224 225 248 249 272 273 296 297 320 321 344 345 368 369)(10 11 34 35 58 59 82 83 106 107 130 131 154 155 178 179 202 203 226 227 250 251 274 275 298 299 322 323 346 347 370 371)(12 13 36 37 60 61 84 85 108 109 132 133 156 157 180 181 204 205 228 229 252 253 276 277 300 301 324 325 348 349 372 373)(14 15 38 39 62 63 86 87 110 111 134 135 158 159 182 183 206 207 230 231 254 255 278 279 302 303 326 327 350 351 374 375)(16 17 40 41 64 65 88 89 112 113 136 137 160 161 184 185 208 209 232 233 256 257 280 281 304 305 328 329 352 353 376 377)(18 19 42 43 66 67 90 91 114 115 138 139 162 163 186 187 210 211 234 235 258 259 282 283 306 307 330 331 354 355 378 379)(20 21 44 45 68 69 92 93 116 117 140 141 164 165 188 189 212 213 236 237 260 261 284 285 308 309 332 333 356 357 380 381)(22 23 46 47 70 71 94 95 118 119 142 143 166 167 190 191 214 215 238 239 262 263 286 287 310 311 334 335 358 359 382 383) +~snsChanMap=(384,384,1)(AP0;0:128)(AP1;1:191)(AP2;2:129)(AP3;3:190)(AP4;4:136)(AP5;5:183)(AP6;6:137)(AP7;7:182)(AP8;8:144)(AP9;9:175)(AP10;10:145)(AP11;11:174)(AP12;12:152)(AP13;13:167)(AP14;14:153)(AP15;15:166)(AP16;16:160)(AP17;17:159)(AP18;18:161)(AP19;19:158)(AP20;20:168)(AP21;21:151)(AP22;22:169)(AP23;23:150)(AP24;24:176)(AP25;25:143)(AP26;26:177)(AP27;27:142)(AP28;28:184)(AP29;29:135)(AP30;30:185)(AP31;31:134)(AP32;32:130)(AP33;33:189)(AP34;34:131)(AP35;35:188)(AP36;36:138)(AP37;37:181)(AP38;38:139)(AP39;39:180)(AP40;40:146)(AP41;41:173)(AP42;42:147)(AP43;43:172)(AP44;44:154)(AP45;45:165)(AP46;46:155)(AP47;47:164)(AP48;48:162)(AP49;49:157)(AP50;50:163)(AP51;51:156)(AP52;52:170)(AP53;53:149)(AP54;54:171)(AP55;55:148)(AP56;56:178)(AP57;57:141)(AP58;58:179)(AP59;59:140)(AP60;60:186)(AP61;61:133)(AP62;62:187)(AP63;63:132)(AP64;64:192)(AP65;65:255)(AP66;66:193)(AP67;67:254)(AP68;68:200)(AP69;69:247)(AP70;70:201)(AP71;71:246)(AP72;72:208)(AP73;73:239)(AP74;74:209)(AP75;75:238)(AP76;76:216)(AP77;77:231)(AP78;78:217)(AP79;79:230)(AP80;80:224)(AP81;81:223)(AP82;82:225)(AP83;83:222)(AP84;84:232)(AP85;85:215)(AP86;86:233)(AP87;87:214)(AP88;88:240)(AP89;89:207)(AP90;90:241)(AP91;91:206)(AP92;92:248)(AP93;93:199)(AP94;94:249)(AP95;95:198)(AP96;96:194)(AP97;97:253)(AP98;98:195)(AP99;99:252)(AP100;100:202)(AP101;101:245)(AP102;102:203)(AP103;103:244)(AP104;104:210)(AP105;105:237)(AP106;106:211)(AP107;107:236)(AP108;108:218)(AP109;109:229)(AP110;110:219)(AP111;111:228)(AP112;112:226)(AP113;113:221)(AP114;114:227)(AP115;115:220)(AP116;116:234)(AP117;117:213)(AP118;118:235)(AP119;119:212)(AP120;120:242)(AP121;121:205)(AP122;122:243)(AP123;123:204)(AP124;124:250)(AP125;125:197)(AP126;126:251)(AP127;127:196)(AP128;128:256)(AP129;129:319)(AP130;130:257)(AP131;131:318)(AP132;132:264)(AP133;133:311)(AP134;134:265)(AP135;135:310)(AP136;136:272)(AP137;137:303)(AP138;138:273)(AP139;139:302)(AP140;140:280)(AP141;141:295)(AP142;142:281)(AP143;143:294)(AP144;144:288)(AP145;145:287)(AP146;146:289)(AP147;147:286)(AP148;148:296)(AP149;149:279)(AP150;150:297)(AP151;151:278)(AP152;152:304)(AP153;153:271)(AP154;154:305)(AP155;155:270)(AP156;156:312)(AP157;157:263)(AP158;158:313)(AP159;159:262)(AP160;160:258)(AP161;161:317)(AP162;162:259)(AP163;163:316)(AP164;164:266)(AP165;165:309)(AP166;166:267)(AP167;167:308)(AP168;168:274)(AP169;169:301)(AP170;170:275)(AP171;171:300)(AP172;172:282)(AP173;173:293)(AP174;174:283)(AP175;175:292)(AP176;176:290)(AP177;177:285)(AP178;178:291)(AP179;179:284)(AP180;180:298)(AP181;181:277)(AP182;182:299)(AP183;183:276)(AP184;184:306)(AP185;185:269)(AP186;186:307)(AP187;187:268)(AP188;188:314)(AP189;189:261)(AP190;190:315)(AP191;191:260)(AP192;192:320)(AP193;193:383)(AP194;194:321)(AP195;195:382)(AP196;196:328)(AP197;197:375)(AP198;198:329)(AP199;199:374)(AP200;200:336)(AP201;201:367)(AP202;202:337)(AP203;203:366)(AP204;204:344)(AP205;205:359)(AP206;206:345)(AP207;207:358)(AP208;208:352)(AP209;209:351)(AP210;210:353)(AP211;211:350)(AP212;212:360)(AP213;213:343)(AP214;214:361)(AP215;215:342)(AP216;216:368)(AP217;217:335)(AP218;218:369)(AP219;219:334)(AP220;220:376)(AP221;221:327)(AP222;222:377)(AP223;223:326)(AP224;224:322)(AP225;225:381)(AP226;226:323)(AP227;227:380)(AP228;228:330)(AP229;229:373)(AP230;230:331)(AP231;231:372)(AP232;232:338)(AP233;233:365)(AP234;234:339)(AP235;235:364)(AP236;236:346)(AP237;237:357)(AP238;238:347)(AP239;239:356)(AP240;240:354)(AP241;241:349)(AP242;242:355)(AP243;243:348)(AP244;244:362)(AP245;245:341)(AP246;246:363)(AP247;247:340)(AP248;248:370)(AP249;249:333)(AP250;250:371)(AP251;251:332)(AP252;252:378)(AP253;253:325)(AP254;254:379)(AP255;255:324)(AP256;256:2)(AP257;257:61)(AP258;258:3)(AP259;259:60)(AP260;260:10)(AP261;261:53)(AP262;262:11)(AP263;263:52)(AP264;264:18)(AP265;265:45)(AP266;266:19)(AP267;267:44)(AP268;268:26)(AP269;269:37)(AP270;270:27)(AP271;271:36)(AP272;272:34)(AP273;273:29)(AP274;274:35)(AP275;275:28)(AP276;276:42)(AP277;277:21)(AP278;278:43)(AP279;279:20)(AP280;280:50)(AP281;281:13)(AP282;282:51)(AP283;283:12)(AP284;284:58)(AP285;285:5)(AP286;286:59)(AP287;287:4)(AP288;288:0)(AP289;289:63)(AP290;290:1)(AP291;291:62)(AP292;292:8)(AP293;293:55)(AP294;294:9)(AP295;295:54)(AP296;296:16)(AP297;297:47)(AP298;298:17)(AP299;299:46)(AP300;300:24)(AP301;301:39)(AP302;302:25)(AP303;303:38)(AP304;304:32)(AP305;305:31)(AP306;306:33)(AP307;307:30)(AP308;308:40)(AP309;309:23)(AP310;310:41)(AP311;311:22)(AP312;312:48)(AP313;313:15)(AP314;314:49)(AP315;315:14)(AP316;316:56)(AP317;317:7)(AP318;318:57)(AP319;319:6)(AP320;320:66)(AP321;321:125)(AP322;322:67)(AP323;323:124)(AP324;324:74)(AP325;325:117)(AP326;326:75)(AP327;327:116)(AP328;328:82)(AP329;329:109)(AP330;330:83)(AP331;331:108)(AP332;332:90)(AP333;333:101)(AP334;334:91)(AP335;335:100)(AP336;336:98)(AP337;337:93)(AP338;338:99)(AP339;339:92)(AP340;340:106)(AP341;341:85)(AP342;342:107)(AP343;343:84)(AP344;344:114)(AP345;345:77)(AP346;346:115)(AP347;347:76)(AP348;348:122)(AP349;349:69)(AP350;350:123)(AP351;351:68)(AP352;352:64)(AP353;353:127)(AP354;354:65)(AP355;355:126)(AP356;356:72)(AP357;357:119)(AP358;358:73)(AP359;359:118)(AP360;360:80)(AP361;361:111)(AP362;362:81)(AP363;363:110)(AP364;364:88)(AP365;365:103)(AP366;366:89)(AP367;367:102)(AP368;368:96)(AP369;369:95)(AP370;370:97)(AP371;371:94)(AP372;372:104)(AP373;373:87)(AP374;374:105)(AP375;375:86)(AP376;376:112)(AP377;377:79)(AP378;378:113)(AP379;379:78)(AP380;380:120)(AP381;381:71)(AP382;382:121)(AP383;383:70)(SY0;768:768) +~snsGeomMap=(NP1110,1,0,73)(0:15.5:576:1)(0:57.5:618:1)(0:21.5:576:1)(0:51.5:618:1)(0:15.5:582:1)(0:57.5:612:1)(0:21.5:582:1)(0:51.5:612:1)(0:15.5:588:1)(0:57.5:606:1)(0:21.5:588:1)(0:51.5:606:1)(0:15.5:594:1)(0:57.5:600:1)(0:21.5:594:1)(0:51.5:600:1)(0:15.5:600:1)(0:57.5:594:1)(0:21.5:600:1)(0:51.5:594:1)(0:15.5:606:1)(0:57.5:588:1)(0:21.5:606:1)(0:51.5:588:1)(0:15.5:612:1)(0:57.5:582:1)(0:21.5:612:1)(0:51.5:582:1)(0:15.5:618:1)(0:57.5:576:1)(0:21.5:618:1)(0:51.5:576:1)(0:27.5:576:1)(0:45.5:618:1)(0:33.5:576:1)(0:39.5:618:1)(0:27.5:582:1)(0:45.5:612:1)(0:33.5:582:1)(0:39.5:612:1)(0:27.5:588:1)(0:45.5:606:1)(0:33.5:588:1)(0:39.5:606:1)(0:27.5:594:1)(0:45.5:600:1)(0:33.5:594:1)(0:39.5:600:1)(0:27.5:600:1)(0:45.5:594:1)(0:33.5:600:1)(0:39.5:594:1)(0:27.5:606:1)(0:45.5:588:1)(0:33.5:606:1)(0:39.5:588:1)(0:27.5:612:1)(0:45.5:582:1)(0:33.5:612:1)(0:39.5:582:1)(0:27.5:618:1)(0:45.5:576:1)(0:33.5:618:1)(0:39.5:576:1)(0:15.5:624:1)(0:57.5:666:1)(0:21.5:624:1)(0:51.5:666:1)(0:15.5:630:1)(0:57.5:660:1)(0:21.5:630:1)(0:51.5:660:1)(0:15.5:636:1)(0:57.5:654:1)(0:21.5:636:1)(0:51.5:654:1)(0:15.5:642:1)(0:57.5:648:1)(0:21.5:642:1)(0:51.5:648:1)(0:15.5:648:1)(0:57.5:642:1)(0:21.5:648:1)(0:51.5:642:1)(0:15.5:654:1)(0:57.5:636:1)(0:21.5:654:1)(0:51.5:636:1)(0:15.5:660:1)(0:57.5:630:1)(0:21.5:660:1)(0:51.5:630:1)(0:15.5:666:1)(0:57.5:624:1)(0:21.5:666:1)(0:51.5:624:1)(0:27.5:624:1)(0:45.5:666:1)(0:33.5:624:1)(0:39.5:666:1)(0:27.5:630:1)(0:45.5:660:1)(0:33.5:630:1)(0:39.5:660:1)(0:27.5:636:1)(0:45.5:654:1)(0:33.5:636:1)(0:39.5:654:1)(0:27.5:642:1)(0:45.5:648:1)(0:33.5:642:1)(0:39.5:648:1)(0:27.5:648:1)(0:45.5:642:1)(0:33.5:648:1)(0:39.5:642:1)(0:27.5:654:1)(0:45.5:636:1)(0:33.5:654:1)(0:39.5:636:1)(0:27.5:660:1)(0:45.5:630:1)(0:33.5:660:1)(0:39.5:630:1)(0:27.5:666:1)(0:45.5:624:1)(0:33.5:666:1)(0:39.5:624:1)(0:15.5:672:1)(0:57.5:714:1)(0:21.5:672:1)(0:51.5:714:1)(0:15.5:678:1)(0:57.5:708:1)(0:21.5:678:1)(0:51.5:708:1)(0:15.5:684:1)(0:57.5:702:1)(0:21.5:684:1)(0:51.5:702:1)(0:15.5:690:1)(0:57.5:696:1)(0:21.5:690:1)(0:51.5:696:1)(0:15.5:696:1)(0:57.5:690:1)(0:21.5:696:1)(0:51.5:690:1)(0:15.5:702:1)(0:57.5:684:1)(0:21.5:702:1)(0:51.5:684:1)(0:15.5:708:1)(0:57.5:678:1)(0:21.5:708:1)(0:51.5:678:1)(0:15.5:714:1)(0:57.5:672:1)(0:21.5:714:1)(0:51.5:672:1)(0:27.5:672:1)(0:45.5:714:1)(0:33.5:672:1)(0:39.5:714:1)(0:27.5:678:1)(0:45.5:708:1)(0:33.5:678:1)(0:39.5:708:1)(0:27.5:684:1)(0:45.5:702:1)(0:33.5:684:1)(0:39.5:702:1)(0:27.5:690:1)(0:45.5:696:1)(0:33.5:690:1)(0:39.5:696:1)(0:27.5:696:1)(0:45.5:690:1)(0:33.5:696:1)(0:39.5:690:1)(0:27.5:702:1)(0:45.5:684:1)(0:33.5:702:1)(0:39.5:684:1)(0:27.5:708:1)(0:45.5:678:1)(0:33.5:708:1)(0:39.5:678:1)(0:27.5:714:1)(0:45.5:672:1)(0:33.5:714:1)(0:39.5:672:1)(0:15.5:720:1)(0:57.5:762:1)(0:21.5:720:1)(0:51.5:762:1)(0:15.5:726:1)(0:57.5:756:1)(0:21.5:726:1)(0:51.5:756:1)(0:15.5:732:1)(0:57.5:750:1)(0:21.5:732:1)(0:51.5:750:1)(0:15.5:738:1)(0:57.5:744:1)(0:21.5:738:1)(0:51.5:744:1)(0:15.5:744:1)(0:57.5:738:1)(0:21.5:744:1)(0:51.5:738:1)(0:15.5:750:1)(0:57.5:732:1)(0:21.5:750:1)(0:51.5:732:1)(0:15.5:756:1)(0:57.5:726:1)(0:21.5:756:1)(0:51.5:726:1)(0:15.5:762:1)(0:57.5:720:1)(0:21.5:762:1)(0:51.5:720:1)(0:27.5:720:1)(0:45.5:762:1)(0:33.5:720:1)(0:39.5:762:1)(0:27.5:726:1)(0:45.5:756:1)(0:33.5:726:1)(0:39.5:756:1)(0:27.5:732:1)(0:45.5:750:1)(0:33.5:732:1)(0:39.5:750:1)(0:27.5:738:1)(0:45.5:744:1)(0:33.5:738:1)(0:39.5:744:1)(0:27.5:744:1)(0:45.5:738:1)(0:33.5:744:1)(0:39.5:738:1)(0:27.5:750:1)(0:45.5:732:1)(0:33.5:750:1)(0:39.5:732:1)(0:27.5:756:1)(0:45.5:726:1)(0:33.5:756:1)(0:39.5:726:1)(0:27.5:762:1)(0:45.5:720:1)(0:33.5:762:1)(0:39.5:720:1)(0:27.5:480:1)(0:45.5:522:1)(0:33.5:480:1)(0:39.5:522:1)(0:27.5:486:1)(0:45.5:516:1)(0:33.5:486:1)(0:39.5:516:1)(0:27.5:492:1)(0:45.5:510:1)(0:33.5:492:1)(0:39.5:510:1)(0:27.5:498:1)(0:45.5:504:1)(0:33.5:498:1)(0:39.5:504:1)(0:27.5:504:1)(0:45.5:498:1)(0:33.5:504:1)(0:39.5:498:1)(0:27.5:510:1)(0:45.5:492:1)(0:33.5:510:1)(0:39.5:492:1)(0:27.5:516:1)(0:45.5:486:1)(0:33.5:516:1)(0:39.5:486:1)(0:27.5:522:1)(0:45.5:480:1)(0:33.5:522:1)(0:39.5:480:1)(0:15.5:480:1)(0:57.5:522:1)(0:21.5:480:1)(0:51.5:522:1)(0:15.5:486:1)(0:57.5:516:1)(0:21.5:486:1)(0:51.5:516:1)(0:15.5:492:1)(0:57.5:510:1)(0:21.5:492:1)(0:51.5:510:1)(0:15.5:498:1)(0:57.5:504:1)(0:21.5:498:1)(0:51.5:504:1)(0:15.5:504:1)(0:57.5:498:1)(0:21.5:504:1)(0:51.5:498:1)(0:15.5:510:1)(0:57.5:492:1)(0:21.5:510:1)(0:51.5:492:1)(0:15.5:516:1)(0:57.5:486:1)(0:21.5:516:1)(0:51.5:486:1)(0:15.5:522:1)(0:57.5:480:1)(0:21.5:522:1)(0:51.5:480:1)(0:27.5:528:1)(0:45.5:570:1)(0:33.5:528:1)(0:39.5:570:1)(0:27.5:534:1)(0:45.5:564:1)(0:33.5:534:1)(0:39.5:564:1)(0:27.5:540:1)(0:45.5:558:1)(0:33.5:540:1)(0:39.5:558:1)(0:27.5:546:1)(0:45.5:552:1)(0:33.5:546:1)(0:39.5:552:1)(0:27.5:552:1)(0:45.5:546:1)(0:33.5:552:1)(0:39.5:546:1)(0:27.5:558:1)(0:45.5:540:1)(0:33.5:558:1)(0:39.5:540:1)(0:27.5:564:1)(0:45.5:534:1)(0:33.5:564:1)(0:39.5:534:1)(0:27.5:570:1)(0:45.5:528:1)(0:33.5:570:1)(0:39.5:528:1)(0:15.5:528:1)(0:57.5:570:1)(0:21.5:528:1)(0:51.5:570:1)(0:15.5:534:1)(0:57.5:564:1)(0:21.5:534:1)(0:51.5:564:1)(0:15.5:540:1)(0:57.5:558:1)(0:21.5:540:1)(0:51.5:558:1)(0:15.5:546:1)(0:57.5:552:1)(0:21.5:546:1)(0:51.5:552:1)(0:15.5:552:1)(0:57.5:546:1)(0:21.5:552:1)(0:51.5:546:1)(0:15.5:558:1)(0:57.5:540:1)(0:21.5:558:1)(0:51.5:540:1)(0:15.5:564:1)(0:57.5:534:1)(0:21.5:564:1)(0:51.5:534:1)(0:15.5:570:1)(0:57.5:528:1)(0:21.5:570:1)(0:51.5:528:1) diff --git a/tests/data/spikeglx/NP1110_vstripe_g0_t0.imec0.ap.meta b/tests/data/spikeglx/NP1110_vstripe_g0_t0.imec0.ap.meta new file mode 100644 index 00000000..ab48d68d --- /dev/null +++ b/tests/data/spikeglx/NP1110_vstripe_g0_t0.imec0.ap.meta @@ -0,0 +1,77 @@ +acqApLfSy=384,384,1 +appVersion=20251218 +fileCreateTime=2026-03-19T22:15:54 +fileName=C:/SGL_DATA/031826_wireless/np1110_vstripe_g0/np1110_vstripe_g0_imec0/np1110_vstripe_g0_t0.imec0.ap.bin +fileSHA1=4CF43B58F11D8C53BC30735647B304F71719D44C +fileSizeBytes=226269120 +fileTimeSecs=9.7952 +firstSample=467544 +gateMode=Immediate +imAiRangeMax=0.6 +imAiRangeMin=-0.6 +imAnyChanFullBand=false +imCalibrated=false +imChan0Ref=ext +imChan0apGain=500 +imColsPerShank=8 +imDatApi=4.1.3 +imDatBs_fw=3.0.226 +imDatBsc_fw=4.0.233 +imDatBsc_hw=1.9 +imDatBsc_pn=NP2_QBSC_00 +imDatBsc_sn=175 +imDatBsc_tech=std +imDatFx_hw=4.13 +imDatFx_pn=NP2_FLEX_0 +imDatFx_sn=0 +imDatHs_fw=0.0.0 +imDatHs_hw=4.9 +imDatHs_pn=NP2_HS_30 +imDatHs_sn=46 +imDatPrb_dock=1 +imDatPrb_pn=NP1110 +imDatPrb_port=4 +imDatPrb_slot=5 +imDatPrb_sn=21144116901 +imDatPrb_sr_mask=1 +imDatPrb_sr_nok=1 +imDatPrb_tech=std +imDatPrb_type=1110 +imErrFlags0_IS_CT_SR_LK_PP_SY_MS=0 0 0 0 0 0 0 +imIsSvyRun=false +imLEDEnable=false +imLowLatency=false +imMaxInt=512 +imRowsPerShank=768 +imSRAtDetect=true +imSampRate=30000 +imStdby= +imSvyMaxBnk=-1 +imSvyNShanks=1 +imSvySecPerBnk=60 +imSvySettleSec=2 +imTipLength=203.5 +imTrgRising=true +imTrgSource=0 +imX0EvenRow=15.5 +imX0OddRow=15.5 +imXPitch=6 +imZPitch=6 +imroFile=C:/Users/labadmin/Desktop/SGLX_top/Release_v20251218-api4/IMRO_Tables/MATLAB_Scripts/NPUHD2_inner_vstripe_bank8_ref0.imro +nDataDirs=1 +nSavedChans=385 +snsApLfSy=384,0,1 +snsSaveChanSubset=0:383,768 +syncImInputSlot=5 +syncSourceIdx=3 +syncSourcePeriod=2 +trigMode=Immediate +typeImEnabled=1 +typeNiEnabled=0 +typeObEnabled=0 +typeThis=imec +userNotes= +~imroTbl=(1110,0,0,500,250,1)(0 9 13)(1 11 15)(2 8 12)(3 10 14)(4 9 13)(5 11 15)(6 8 12)(7 10 14)(8 9 13)(9 11 15)(10 8 12)(11 10 14)(12 9 13)(13 11 15)(14 8 12)(15 10 14)(16 9 13)(17 11 15)(18 8 12)(19 10 14)(20 9 13)(21 11 15)(22 8 12)(23 10 14) +~muxTbl=(32,12)(0 1 24 25 48 49 72 73 96 97 120 121 144 145 168 169 192 193 216 217 240 241 264 265 288 289 312 313 336 337 360 361)(2 3 26 27 50 51 74 75 98 99 122 123 146 147 170 171 194 195 218 219 242 243 266 267 290 291 314 315 338 339 362 363)(4 5 28 29 52 53 76 77 100 101 124 125 148 149 172 173 196 197 220 221 244 245 268 269 292 293 316 317 340 341 364 365)(6 7 30 31 54 55 78 79 102 103 126 127 150 151 174 175 198 199 222 223 246 247 270 271 294 295 318 319 342 343 366 367)(8 9 32 33 56 57 80 81 104 105 128 129 152 153 176 177 200 201 224 225 248 249 272 273 296 297 320 321 344 345 368 369)(10 11 34 35 58 59 82 83 106 107 130 131 154 155 178 179 202 203 226 227 250 251 274 275 298 299 322 323 346 347 370 371)(12 13 36 37 60 61 84 85 108 109 132 133 156 157 180 181 204 205 228 229 252 253 276 277 300 301 324 325 348 349 372 373)(14 15 38 39 62 63 86 87 110 111 134 135 158 159 182 183 206 207 230 231 254 255 278 279 302 303 326 327 350 351 374 375)(16 17 40 41 64 65 88 89 112 113 136 137 160 161 184 185 208 209 232 233 256 257 280 281 304 305 328 329 352 353 376 377)(18 19 42 43 66 67 90 91 114 115 138 139 162 163 186 187 210 211 234 235 258 259 282 283 306 307 330 331 354 355 378 379)(20 21 44 45 68 69 92 93 116 117 140 141 164 165 188 189 212 213 236 237 260 261 284 285 308 309 332 333 356 357 380 381)(22 23 46 47 70 71 94 95 118 119 142 143 166 167 190 191 214 215 238 239 262 263 286 287 310 311 334 335 358 359 382 383) +~snsChanMap=(384,384,1)(AP0;0:240)(AP1;1:343)(AP2;2:48)(AP3;3:151)(AP4;4:241)(AP5;5:342)(AP6;6:49)(AP7;7:150)(AP8;8:242)(AP9;9:341)(AP10;10:50)(AP11;11:149)(AP12;12:243)(AP13;13:340)(AP14;14:51)(AP15;15:148)(AP16;16:244)(AP17;17:339)(AP18;18:52)(AP19;19:147)(AP20;20:245)(AP21;21:338)(AP22;22:53)(AP23;23:146)(AP24;24:246)(AP25;25:337)(AP26;26:54)(AP27;27:145)(AP28;28:247)(AP29;29:336)(AP30;30:55)(AP31;31:144)(AP32;32:192)(AP33;33:295)(AP34;34:0)(AP35;35:103)(AP36;36:193)(AP37;37:294)(AP38;38:1)(AP39;39:102)(AP40;40:194)(AP41;41:293)(AP42;42:2)(AP43;43:101)(AP44;44:195)(AP45;45:292)(AP46;46:3)(AP47;47:100)(AP48;48:196)(AP49;49:291)(AP50;50:4)(AP51;51:99)(AP52;52:197)(AP53;53:290)(AP54;54:5)(AP55;55:98)(AP56;56:198)(AP57;57:289)(AP58;58:6)(AP59;59:97)(AP60;60:199)(AP61;61:288)(AP62;62:7)(AP63;63:96)(AP64;64:248)(AP65;65:351)(AP66;66:56)(AP67;67:159)(AP68;68:249)(AP69;69:350)(AP70;70:57)(AP71;71:158)(AP72;72:250)(AP73;73:349)(AP74;74:58)(AP75;75:157)(AP76;76:251)(AP77;77:348)(AP78;78:59)(AP79;79:156)(AP80;80:252)(AP81;81:347)(AP82;82:60)(AP83;83:155)(AP84;84:253)(AP85;85:346)(AP86;86:61)(AP87;87:154)(AP88;88:254)(AP89;89:345)(AP90;90:62)(AP91;91:153)(AP92;92:255)(AP93;93:344)(AP94;94:63)(AP95;95:152)(AP96;96:200)(AP97;97:303)(AP98;98:8)(AP99;99:111)(AP100;100:201)(AP101;101:302)(AP102;102:9)(AP103;103:110)(AP104;104:202)(AP105;105:301)(AP106;106:10)(AP107;107:109)(AP108;108:203)(AP109;109:300)(AP110;110:11)(AP111;111:108)(AP112;112:204)(AP113;113:299)(AP114;114:12)(AP115;115:107)(AP116;116:205)(AP117;117:298)(AP118;118:13)(AP119;119:106)(AP120;120:206)(AP121;121:297)(AP122;122:14)(AP123;123:105)(AP124;124:207)(AP125;125:296)(AP126;126:15)(AP127;127:104)(AP128;128:256)(AP129;129:359)(AP130;130:64)(AP131;131:167)(AP132;132:257)(AP133;133:358)(AP134;134:65)(AP135;135:166)(AP136;136:258)(AP137;137:357)(AP138;138:66)(AP139;139:165)(AP140;140:259)(AP141;141:356)(AP142;142:67)(AP143;143:164)(AP144;144:260)(AP145;145:355)(AP146;146:68)(AP147;147:163)(AP148;148:261)(AP149;149:354)(AP150;150:69)(AP151;151:162)(AP152;152:262)(AP153;153:353)(AP154;154:70)(AP155;155:161)(AP156;156:263)(AP157;157:352)(AP158;158:71)(AP159;159:160)(AP160;160:208)(AP161;161:311)(AP162;162:16)(AP163;163:119)(AP164;164:209)(AP165;165:310)(AP166;166:17)(AP167;167:118)(AP168;168:210)(AP169;169:309)(AP170;170:18)(AP171;171:117)(AP172;172:211)(AP173;173:308)(AP174;174:19)(AP175;175:116)(AP176;176:212)(AP177;177:307)(AP178;178:20)(AP179;179:115)(AP180;180:213)(AP181;181:306)(AP182;182:21)(AP183;183:114)(AP184;184:214)(AP185;185:305)(AP186;186:22)(AP187;187:113)(AP188;188:215)(AP189;189:304)(AP190;190:23)(AP191;191:112)(AP192;192:264)(AP193;193:367)(AP194;194:72)(AP195;195:175)(AP196;196:265)(AP197;197:366)(AP198;198:73)(AP199;199:174)(AP200;200:266)(AP201;201:365)(AP202;202:74)(AP203;203:173)(AP204;204:267)(AP205;205:364)(AP206;206:75)(AP207;207:172)(AP208;208:268)(AP209;209:363)(AP210;210:76)(AP211;211:171)(AP212;212:269)(AP213;213:362)(AP214;214:77)(AP215;215:170)(AP216;216:270)(AP217;217:361)(AP218;218:78)(AP219;219:169)(AP220;220:271)(AP221;221:360)(AP222;222:79)(AP223;223:168)(AP224;224:216)(AP225;225:319)(AP226;226:24)(AP227;227:127)(AP228;228:217)(AP229;229:318)(AP230;230:25)(AP231;231:126)(AP232;232:218)(AP233;233:317)(AP234;234:26)(AP235;235:125)(AP236;236:219)(AP237;237:316)(AP238;238:27)(AP239;239:124)(AP240;240:220)(AP241;241:315)(AP242;242:28)(AP243;243:123)(AP244;244:221)(AP245;245:314)(AP246;246:29)(AP247;247:122)(AP248;248:222)(AP249;249:313)(AP250;250:30)(AP251;251:121)(AP252;252:223)(AP253;253:312)(AP254;254:31)(AP255;255:120)(AP256;256:272)(AP257;257:375)(AP258;258:80)(AP259;259:183)(AP260;260:273)(AP261;261:374)(AP262;262:81)(AP263;263:182)(AP264;264:274)(AP265;265:373)(AP266;266:82)(AP267;267:181)(AP268;268:275)(AP269;269:372)(AP270;270:83)(AP271;271:180)(AP272;272:276)(AP273;273:371)(AP274;274:84)(AP275;275:179)(AP276;276:277)(AP277;277:370)(AP278;278:85)(AP279;279:178)(AP280;280:278)(AP281;281:369)(AP282;282:86)(AP283;283:177)(AP284;284:279)(AP285;285:368)(AP286;286:87)(AP287;287:176)(AP288;288:224)(AP289;289:327)(AP290;290:32)(AP291;291:135)(AP292;292:225)(AP293;293:326)(AP294;294:33)(AP295;295:134)(AP296;296:226)(AP297;297:325)(AP298;298:34)(AP299;299:133)(AP300;300:227)(AP301;301:324)(AP302;302:35)(AP303;303:132)(AP304;304:228)(AP305;305:323)(AP306;306:36)(AP307;307:131)(AP308;308:229)(AP309;309:322)(AP310;310:37)(AP311;311:130)(AP312;312:230)(AP313;313:321)(AP314;314:38)(AP315;315:129)(AP316;316:231)(AP317;317:320)(AP318;318:39)(AP319;319:128)(AP320;320:280)(AP321;321:383)(AP322;322:88)(AP323;323:191)(AP324;324:281)(AP325;325:382)(AP326;326:89)(AP327;327:190)(AP328;328:282)(AP329;329:381)(AP330;330:90)(AP331;331:189)(AP332;332:283)(AP333;333:380)(AP334;334:91)(AP335;335:188)(AP336;336:284)(AP337;337:379)(AP338;338:92)(AP339;339:187)(AP340;340:285)(AP341;341:378)(AP342;342:93)(AP343;343:186)(AP344;344:286)(AP345;345:377)(AP346;346:94)(AP347;347:185)(AP348;348:287)(AP349;349:376)(AP350;350:95)(AP351;351:184)(AP352;352:232)(AP353;353:335)(AP354;354:40)(AP355;355:143)(AP356;356:233)(AP357;357:334)(AP358;358:41)(AP359;359:142)(AP360;360:234)(AP361;361:333)(AP362;362:42)(AP363;363:141)(AP364;364:235)(AP365;365:332)(AP366;366:43)(AP367;367:140)(AP368;368:236)(AP369;369:331)(AP370;370:44)(AP371;371:139)(AP372;372:237)(AP373;373:330)(AP374;374:45)(AP375;375:138)(AP376;376:238)(AP377;377:329)(AP378;378:46)(AP379;379:137)(AP380;380:239)(AP381;381:328)(AP382;382:47)(AP383;383:136)(SY0;768:768) +~snsGeomMap=(NP1110,1,0,73)(0:33.5:3744:1)(0:39.5:4362:1)(0:33.5:2592:1)(0:39.5:3210:1)(0:33.5:3750:1)(0:39.5:4356:1)(0:33.5:2598:1)(0:39.5:3204:1)(0:33.5:3756:1)(0:39.5:4350:1)(0:33.5:2604:1)(0:39.5:3198:1)(0:33.5:3762:1)(0:39.5:4344:1)(0:33.5:2610:1)(0:39.5:3192:1)(0:33.5:3768:1)(0:39.5:4338:1)(0:33.5:2616:1)(0:39.5:3186:1)(0:33.5:3774:1)(0:39.5:4332:1)(0:33.5:2622:1)(0:39.5:3180:1)(0:33.5:3780:1)(0:39.5:4326:1)(0:33.5:2628:1)(0:39.5:3174:1)(0:33.5:3786:1)(0:39.5:4320:1)(0:33.5:2634:1)(0:39.5:3168:1)(0:33.5:3456:1)(0:39.5:4074:1)(0:33.5:2304:1)(0:39.5:2922:1)(0:33.5:3462:1)(0:39.5:4068:1)(0:33.5:2310:1)(0:39.5:2916:1)(0:33.5:3468:1)(0:39.5:4062:1)(0:33.5:2316:1)(0:39.5:2910:1)(0:33.5:3474:1)(0:39.5:4056:1)(0:33.5:2322:1)(0:39.5:2904:1)(0:33.5:3480:1)(0:39.5:4050:1)(0:33.5:2328:1)(0:39.5:2898:1)(0:33.5:3486:1)(0:39.5:4044:1)(0:33.5:2334:1)(0:39.5:2892:1)(0:33.5:3492:1)(0:39.5:4038:1)(0:33.5:2340:1)(0:39.5:2886:1)(0:33.5:3498:1)(0:39.5:4032:1)(0:33.5:2346:1)(0:39.5:2880:1)(0:33.5:3792:1)(0:39.5:4410:1)(0:33.5:2640:1)(0:39.5:3258:1)(0:33.5:3798:1)(0:39.5:4404:1)(0:33.5:2646:1)(0:39.5:3252:1)(0:33.5:3804:1)(0:39.5:4398:1)(0:33.5:2652:1)(0:39.5:3246:1)(0:33.5:3810:1)(0:39.5:4392:1)(0:33.5:2658:1)(0:39.5:3240:1)(0:33.5:3816:1)(0:39.5:4386:1)(0:33.5:2664:1)(0:39.5:3234:1)(0:33.5:3822:1)(0:39.5:4380:1)(0:33.5:2670:1)(0:39.5:3228:1)(0:33.5:3828:1)(0:39.5:4374:1)(0:33.5:2676:1)(0:39.5:3222:1)(0:33.5:3834:1)(0:39.5:4368:1)(0:33.5:2682:1)(0:39.5:3216:1)(0:33.5:3504:1)(0:39.5:4122:1)(0:33.5:2352:1)(0:39.5:2970:1)(0:33.5:3510:1)(0:39.5:4116:1)(0:33.5:2358:1)(0:39.5:2964:1)(0:33.5:3516:1)(0:39.5:4110:1)(0:33.5:2364:1)(0:39.5:2958:1)(0:33.5:3522:1)(0:39.5:4104:1)(0:33.5:2370:1)(0:39.5:2952:1)(0:33.5:3528:1)(0:39.5:4098:1)(0:33.5:2376:1)(0:39.5:2946:1)(0:33.5:3534:1)(0:39.5:4092:1)(0:33.5:2382:1)(0:39.5:2940:1)(0:33.5:3540:1)(0:39.5:4086:1)(0:33.5:2388:1)(0:39.5:2934:1)(0:33.5:3546:1)(0:39.5:4080:1)(0:33.5:2394:1)(0:39.5:2928:1)(0:33.5:3840:1)(0:39.5:4458:1)(0:33.5:2688:1)(0:39.5:3306:1)(0:33.5:3846:1)(0:39.5:4452:1)(0:33.5:2694:1)(0:39.5:3300:1)(0:33.5:3852:1)(0:39.5:4446:1)(0:33.5:2700:1)(0:39.5:3294:1)(0:33.5:3858:1)(0:39.5:4440:1)(0:33.5:2706:1)(0:39.5:3288:1)(0:33.5:3864:1)(0:39.5:4434:1)(0:33.5:2712:1)(0:39.5:3282:1)(0:33.5:3870:1)(0:39.5:4428:1)(0:33.5:2718:1)(0:39.5:3276:1)(0:33.5:3876:1)(0:39.5:4422:1)(0:33.5:2724:1)(0:39.5:3270:1)(0:33.5:3882:1)(0:39.5:4416:1)(0:33.5:2730:1)(0:39.5:3264:1)(0:33.5:3552:1)(0:39.5:4170:1)(0:33.5:2400:1)(0:39.5:3018:1)(0:33.5:3558:1)(0:39.5:4164:1)(0:33.5:2406:1)(0:39.5:3012:1)(0:33.5:3564:1)(0:39.5:4158:1)(0:33.5:2412:1)(0:39.5:3006:1)(0:33.5:3570:1)(0:39.5:4152:1)(0:33.5:2418:1)(0:39.5:3000:1)(0:33.5:3576:1)(0:39.5:4146:1)(0:33.5:2424:1)(0:39.5:2994:1)(0:33.5:3582:1)(0:39.5:4140:1)(0:33.5:2430:1)(0:39.5:2988:1)(0:33.5:3588:1)(0:39.5:4134:1)(0:33.5:2436:1)(0:39.5:2982:1)(0:33.5:3594:1)(0:39.5:4128:1)(0:33.5:2442:1)(0:39.5:2976:1)(0:33.5:3888:1)(0:39.5:4506:1)(0:33.5:2736:1)(0:39.5:3354:1)(0:33.5:3894:1)(0:39.5:4500:1)(0:33.5:2742:1)(0:39.5:3348:1)(0:33.5:3900:1)(0:39.5:4494:1)(0:33.5:2748:1)(0:39.5:3342:1)(0:33.5:3906:1)(0:39.5:4488:1)(0:33.5:2754:1)(0:39.5:3336:1)(0:33.5:3912:1)(0:39.5:4482:1)(0:33.5:2760:1)(0:39.5:3330:1)(0:33.5:3918:1)(0:39.5:4476:1)(0:33.5:2766:1)(0:39.5:3324:1)(0:33.5:3924:1)(0:39.5:4470:1)(0:33.5:2772:1)(0:39.5:3318:1)(0:33.5:3930:1)(0:39.5:4464:1)(0:33.5:2778:1)(0:39.5:3312:1)(0:33.5:3600:1)(0:39.5:4218:1)(0:33.5:2448:1)(0:39.5:3066:1)(0:33.5:3606:1)(0:39.5:4212:1)(0:33.5:2454:1)(0:39.5:3060:1)(0:33.5:3612:1)(0:39.5:4206:1)(0:33.5:2460:1)(0:39.5:3054:1)(0:33.5:3618:1)(0:39.5:4200:1)(0:33.5:2466:1)(0:39.5:3048:1)(0:33.5:3624:1)(0:39.5:4194:1)(0:33.5:2472:1)(0:39.5:3042:1)(0:33.5:3630:1)(0:39.5:4188:1)(0:33.5:2478:1)(0:39.5:3036:1)(0:33.5:3636:1)(0:39.5:4182:1)(0:33.5:2484:1)(0:39.5:3030:1)(0:33.5:3642:1)(0:39.5:4176:1)(0:33.5:2490:1)(0:39.5:3024:1)(0:33.5:3936:1)(0:39.5:4554:1)(0:33.5:2784:1)(0:39.5:3402:1)(0:33.5:3942:1)(0:39.5:4548:1)(0:33.5:2790:1)(0:39.5:3396:1)(0:33.5:3948:1)(0:39.5:4542:1)(0:33.5:2796:1)(0:39.5:3390:1)(0:33.5:3954:1)(0:39.5:4536:1)(0:33.5:2802:1)(0:39.5:3384:1)(0:33.5:3960:1)(0:39.5:4530:1)(0:33.5:2808:1)(0:39.5:3378:1)(0:33.5:3966:1)(0:39.5:4524:1)(0:33.5:2814:1)(0:39.5:3372:1)(0:33.5:3972:1)(0:39.5:4518:1)(0:33.5:2820:1)(0:39.5:3366:1)(0:33.5:3978:1)(0:39.5:4512:1)(0:33.5:2826:1)(0:39.5:3360:1)(0:33.5:3648:1)(0:39.5:4266:1)(0:33.5:2496:1)(0:39.5:3114:1)(0:33.5:3654:1)(0:39.5:4260:1)(0:33.5:2502:1)(0:39.5:3108:1)(0:33.5:3660:1)(0:39.5:4254:1)(0:33.5:2508:1)(0:39.5:3102:1)(0:33.5:3666:1)(0:39.5:4248:1)(0:33.5:2514:1)(0:39.5:3096:1)(0:33.5:3672:1)(0:39.5:4242:1)(0:33.5:2520:1)(0:39.5:3090:1)(0:33.5:3678:1)(0:39.5:4236:1)(0:33.5:2526:1)(0:39.5:3084:1)(0:33.5:3684:1)(0:39.5:4230:1)(0:33.5:2532:1)(0:39.5:3078:1)(0:33.5:3690:1)(0:39.5:4224:1)(0:33.5:2538:1)(0:39.5:3072:1)(0:33.5:3984:1)(0:39.5:4602:1)(0:33.5:2832:1)(0:39.5:3450:1)(0:33.5:3990:1)(0:39.5:4596:1)(0:33.5:2838:1)(0:39.5:3444:1)(0:33.5:3996:1)(0:39.5:4590:1)(0:33.5:2844:1)(0:39.5:3438:1)(0:33.5:4002:1)(0:39.5:4584:1)(0:33.5:2850:1)(0:39.5:3432:1)(0:33.5:4008:1)(0:39.5:4578:1)(0:33.5:2856:1)(0:39.5:3426:1)(0:33.5:4014:1)(0:39.5:4572:1)(0:33.5:2862:1)(0:39.5:3420:1)(0:33.5:4020:1)(0:39.5:4566:1)(0:33.5:2868:1)(0:39.5:3414:1)(0:33.5:4026:1)(0:39.5:4560:1)(0:33.5:2874:1)(0:39.5:3408:1)(0:33.5:3696:1)(0:39.5:4314:1)(0:33.5:2544:1)(0:39.5:3162:1)(0:33.5:3702:1)(0:39.5:4308:1)(0:33.5:2550:1)(0:39.5:3156:1)(0:33.5:3708:1)(0:39.5:4302:1)(0:33.5:2556:1)(0:39.5:3150:1)(0:33.5:3714:1)(0:39.5:4296:1)(0:33.5:2562:1)(0:39.5:3144:1)(0:33.5:3720:1)(0:39.5:4290:1)(0:33.5:2568:1)(0:39.5:3138:1)(0:33.5:3726:1)(0:39.5:4284:1)(0:33.5:2574:1)(0:39.5:3132:1)(0:33.5:3732:1)(0:39.5:4278:1)(0:33.5:2580:1)(0:39.5:3126:1)(0:33.5:3738:1)(0:39.5:4272:1)(0:33.5:2586:1)(0:39.5:3120:1) diff --git a/tests/test_io/test_spikeglx.py b/tests/test_io/test_spikeglx.py index 6350a500..779b2598 100644 --- a/tests/test_io/test_spikeglx.py +++ b/tests/test_io/test_spikeglx.py @@ -289,9 +289,9 @@ def test_NPH_short_linear_probe_type_0(): assert np.allclose(filters, 1) -def test_ultra_probe(): +def test_np_ultra_probe(): # Data provided by Alessio - probe = read_spikeglx(data_path / "npUltra.meta") + probe = read_spikeglx(data_path / "NP-Ultra.meta") assert probe.manufacturer == "imec" @@ -315,6 +315,68 @@ def test_ultra_probe(): assert unique_y_values.size == expected_electode_rows +def test_NP1110_probe_bank0(): + # For bank0, all electrodes are packet at the tip, in an 48x8 grid + probe = read_spikeglx(data_path / "NP1110_bank0_g0_t0.imec0.ap") + + assert probe.manufacturer == "imec" + + # Test contact geometry + contact_width = 5.0 + contact_shape = "square" + + assert np.all(probe.contact_shape_params == {"width": contact_width}) + assert np.all(probe.contact_shapes == contact_shape) + + contact_positions = probe.contact_positions + x = contact_positions[:, 0] + y = contact_positions[:, 1] + + min_y = 0 + max_y = 282 + assert np.all((y >= min_y) & (y <= max_y)) + + expected_electrode_columns = 8 + unique_x_values = np.unique(x) + assert unique_x_values.size == expected_electrode_columns + + expected_electode_rows = 48 + unique_y_values = np.unique(y) + assert unique_y_values.size == expected_electode_rows + + +def test_NP1110_probe_bank4_2_192(): + # For this dataset, bank4 is selected with a 2x192 configuration + probe = read_spikeglx(data_path / "NP1110_2x192_bank4_g0_t0.imec0.ap.meta") + + assert probe.manufacturer == "imec" + + # Test contact geometry + contact_width = 5.0 + contact_shape = "square" + + assert np.all(probe.contact_shape_params == {"width": contact_width}) + assert np.all(probe.contact_shapes == contact_shape) + + contact_positions = probe.contact_positions + x = contact_positions[:, 0] + y = contact_positions[:, 1] + + # For bank0, all electrodes are packet at the tip, in an 48x8 grid + min_y = 1152 + max_y = 2298 + assert np.all((y >= min_y) & (y <= max_y)) + + # For bank4 with 2x192, we expect 4 columns (2 banks interleaved) and 192 rows + expected_electrode_columns = 4 + unique_x_values = np.unique(x) + assert unique_x_values.size == expected_electrode_columns + + expected_electode_rows = 192 + unique_y_values = np.unique(y) + assert unique_y_values.size == expected_electode_rows + + def test_CatGT_NP1(): probe = read_spikeglx(data_path / "catgt.meta") assert "1.0" in probe.description @@ -400,4 +462,4 @@ def test_snsGeomMap(): if __name__ == "__main__": # test_NP2_1_shanks() - test_snsGeomMap() + test_NP1110_probes() From d5c25f1a27fbbd3196ecac6c8ca9e60aec13d154 Mon Sep 17 00:00:00 2001 From: Alessio Buccino Date: Fri, 20 Mar 2026 12:24:06 +0100 Subject: [PATCH 11/12] Finalize tests --- src/probeinterface/neuropixels_tools.py | 1 + tests/test_io/test_spikeglx.py | 125 ++++++++++++++++++++---- 2 files changed, 109 insertions(+), 17 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index ce711332..4d9fa31e 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -8,6 +8,7 @@ """ from __future__ import annotations + from pathlib import Path from typing import Union, Optional import warnings diff --git a/tests/test_io/test_spikeglx.py b/tests/test_io/test_spikeglx.py index 779b2598..27a275ca 100644 --- a/tests/test_io/test_spikeglx.py +++ b/tests/test_io/test_spikeglx.py @@ -5,6 +5,7 @@ import pytest from probeinterface import ( + probe, read_spikeglx, parse_spikeglx_meta, get_saved_channel_indices_from_spikeglx_meta, @@ -44,6 +45,8 @@ def test_get_saved_channel_indices_from_spikeglx_meta(): def test_NP1(): probe = read_spikeglx(data_path / "Noise_g0_t0.imec0.ap.meta") assert "1.0" in probe.description + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations def test_NP_phase3A(): @@ -62,12 +65,16 @@ def test_NP_phase3A(): assert np.all(probe.contact_shape_params == {"width": contact_width}) assert np.all(probe.contact_shapes == contact_shape) + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations def test_NP2_1_shanks(): probe = read_spikeglx(data_path / "p2_g0_t0.imec0.ap.meta") assert "2.0" in probe.description assert probe.get_shank_count() == 1 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations def test_NP2_4_shanks(): @@ -78,6 +85,8 @@ def test_NP2_4_shanks(): assert probe.ndim == 2 assert probe.get_shank_count() == 4 assert probe.get_contact_count() == 384 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry contact_width = 12.0 @@ -101,6 +110,8 @@ def test_NP2_2013_all(): # all channels are from the first shank assert probe.get_shank_count() == 4 assert probe.get_contact_count() == 384 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry contact_width = 12.0 @@ -124,6 +135,8 @@ def test_NP2_2013_subset(): # all channels are from the first shank assert probe.get_shank_count() == 4 assert probe.get_contact_count() == 120 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry contact_width = 12.0 @@ -146,6 +159,8 @@ def test_NP2_4_shanks_with_different_electrodes_saved(): assert probe.ndim == 2 assert probe.get_shank_count() == 4 assert probe.get_contact_count() == 384 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry contact_width = 12.0 @@ -165,6 +180,9 @@ def test_NP1_large_depth_span(): probe = read_spikeglx(data_path / "allan-longcol_g0_t0.imec0.ap.meta") assert "1.0" in probe.description assert probe.get_shank_count() == 1 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations + ypos = probe.contact_positions[:, 1] assert (np.max(ypos) - np.min(ypos)) > 7600 @@ -175,6 +193,9 @@ def test_NP1_other_example(): print(probe) assert "1.0" in probe.description assert probe.get_shank_count() == 1 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations + ypos = probe.contact_positions[:, 1] assert (np.max(ypos) - np.min(ypos)) > 7600 @@ -184,6 +205,9 @@ def tes_NP1_384_channels(): probe = read_spikeglx(data_path / "NP1_saved_only_subset_of_channels.meta") assert probe.get_shank_count() == 1 assert probe.get_contact_count() == 151 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations + assert 152 not in probe.contact_annotations["channel_ids"] @@ -196,6 +220,8 @@ def test_NPH_long_staggered(): assert probe.ndim == 2 assert probe.get_shank_count() == 1 assert probe.get_contact_count() == 384 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry x_pitch = 56.0 @@ -250,6 +276,8 @@ def test_NPH_short_linear_probe_type_0(): assert probe.ndim == 2 assert probe.get_shank_count() == 1 assert probe.get_contact_count() == 384 + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry x_pitch = 56.0 @@ -294,6 +322,8 @@ def test_np_ultra_probe(): probe = read_spikeglx(data_path / "NP-Ultra.meta") assert probe.manufacturer == "imec" + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry contact_width = 5.0 @@ -315,11 +345,18 @@ def test_np_ultra_probe(): assert unique_y_values.size == expected_electode_rows -def test_NP1110_probe_bank0(): +def test_NP1110_probes(): # For bank0, all electrodes are packet at the tip, in an 48x8 grid - probe = read_spikeglx(data_path / "NP1110_bank0_g0_t0.imec0.ap") + expected_electrode_columns = 8 + expected_electode_rows = 48 + min_y = 0 + max_y = 292 + probe = read_spikeglx(data_path / "NP1110_bank0_g0_t0.imec0.ap.meta") assert probe.manufacturer == "imec" + assert probe.model_name == "NP1110" + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry contact_width = 5.0 @@ -328,28 +365,29 @@ def test_NP1110_probe_bank0(): assert np.all(probe.contact_shape_params == {"width": contact_width}) assert np.all(probe.contact_shapes == contact_shape) + # Check contact positions are within expected bounds contact_positions = probe.contact_positions x = contact_positions[:, 0] y = contact_positions[:, 1] - min_y = 0 - max_y = 282 assert np.all((y >= min_y) & (y <= max_y)) - - expected_electrode_columns = 8 unique_x_values = np.unique(x) assert unique_x_values.size == expected_electrode_columns - - expected_electode_rows = 48 unique_y_values = np.unique(y) assert unique_y_values.size == expected_electode_rows - -def test_NP1110_probe_bank4_2_192(): # For this dataset, bank4 is selected with a 2x192 configuration - probe = read_spikeglx(data_path / "NP1110_2x192_bank4_g0_t0.imec0.ap.meta") + # (2 banks interleaved, so 4 columns) and 192 rows + expected_electrode_columns = 4 + expected_electode_rows = 192 + min_y = 1152 + max_y = 2298 + probe = read_spikeglx(data_path / "NP1110_2x192_bank4_g0_t0.imec0.ap.meta") assert probe.manufacturer == "imec" + assert probe.model_name == "NP1110" + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry contact_width = 5.0 @@ -358,21 +396,74 @@ def test_NP1110_probe_bank4_2_192(): assert np.all(probe.contact_shape_params == {"width": contact_width}) assert np.all(probe.contact_shapes == contact_shape) + # Check contact positions are within expected bounds contact_positions = probe.contact_positions x = contact_positions[:, 0] y = contact_positions[:, 1] - # For bank0, all electrodes are packet at the tip, in an 48x8 grid - min_y = 1152 - max_y = 2298 assert np.all((y >= min_y) & (y <= max_y)) + unique_x_values = np.unique(x) + assert unique_x_values.size == expected_electrode_columns + unique_y_values = np.unique(y) + assert unique_y_values.size == expected_electode_rows - # For bank4 with 2x192, we expect 4 columns (2 banks interleaved) and 192 rows - expected_electrode_columns = 4 + # For this dataset, a 48x8 config in the middle of the probe is used + expected_electrode_columns = 8 + expected_electode_rows = 48 + min_y = 480 + max_y = 862 + probe = read_spikeglx(data_path / "NP1110_botrow80_g0_t0.imec0.ap.meta") + + assert probe.manufacturer == "imec" + assert probe.model_name == "NP1110" + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations + + # Test contact geometry + contact_width = 5.0 + contact_shape = "square" + + assert np.all(probe.contact_shape_params == {"width": contact_width}) + assert np.all(probe.contact_shapes == contact_shape) + + # Check contact positions are within expected bounds + contact_positions = probe.contact_positions + x = contact_positions[:, 0] + y = contact_positions[:, 1] + + assert np.all((y >= min_y) & (y <= max_y)) unique_x_values = np.unique(x) assert unique_x_values.size == expected_electrode_columns + unique_y_values = np.unique(y) + assert unique_y_values.size == expected_electode_rows - expected_electode_rows = 192 + # For this dataset, a 384x1 config is selectedin the middle of the probe, with interleaved columns + expected_electrode_columns = 2 + expected_electode_rows = 384 + min_y = 2304 + max_y = 4602 + probe = read_spikeglx(data_path / "NP1110_vstripe_g0_t0.imec0.ap.meta") + + assert probe.manufacturer == "imec" + assert probe.model_name == "NP1110" + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations + + # Test contact geometry + contact_width = 5.0 + contact_shape = "square" + + assert np.all(probe.contact_shape_params == {"width": contact_width}) + assert np.all(probe.contact_shapes == contact_shape) + + # Check contact positions are within expected bounds + contact_positions = probe.contact_positions + x = contact_positions[:, 0] + y = contact_positions[:, 1] + + assert np.all((y >= min_y) & (y <= max_y)) + unique_x_values = np.unique(x) + assert unique_x_values.size == expected_electrode_columns unique_y_values = np.unique(y) assert unique_y_values.size == expected_electode_rows From c12c326cbd76b1ad9cdf5c050b110b0eada23d2b Mon Sep 17 00:00:00 2001 From: Alessio Buccino Date: Fri, 20 Mar 2026 12:57:31 +0100 Subject: [PATCH 12/12] move _build_canonical_electrode_id to utils section --- src/probeinterface/neuropixels_tools.py | 66 ++++++++++++------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index 4d9fa31e..76551b07 100644 --- a/src/probeinterface/neuropixels_tools.py +++ b/src/probeinterface/neuropixels_tools.py @@ -315,6 +315,39 @@ def build_neuropixels_probe(probe_part_number: str) -> Probe: return probe +def _build_canonical_contact_id(electrode_id: int, shank_id: int | None = None) -> str: + """ + Build the canonical contact ID string for a Neuropixels electrode. + + This establishes the standard naming convention used throughout probeinterface + for Neuropixels contact identification. + + Parameters + ---------- + electrode_id : int + Physical electrode ID on the probe (e.g., 0-959 for NP1.0) + shank_id : int or None, default: None + Shank ID for multi-shank probes. If None, assumes single-shank probe. + + Returns + ------- + contact_id : str + Canonical contact ID string, either "e{electrode_id}" for single-shank + or "s{shank_id}e{electrode_id}" for multi-shank probes. + + Examples + -------- + >>> _build_canonical_contact_id(123) + 'e123' + >>> _build_canonical_contact_id(123, shank_id=0) + 's0e123' + """ + if shank_id is not None: + return f"s{shank_id}e{electrode_id}" + else: + return f"e{electrode_id}" + + def _annotate_contacts_from_mux_table(probe: Probe, adc_groups_array: np.array): """ Annotate a Probe object with ADC group and sample order information based on the MUX table. @@ -409,39 +442,6 @@ def _annotate_probe_with_adc_sampling_info(probe: Probe, adc_sampling_table: str ######################### -def _build_canonical_contact_id(electrode_id: int, shank_id: int | None = None) -> str: - """ - Build the canonical contact ID string for a Neuropixels electrode. - - This establishes the standard naming convention used throughout probeinterface - for Neuropixels contact identification. - - Parameters - ---------- - electrode_id : int - Physical electrode ID on the probe (e.g., 0-959 for NP1.0) - shank_id : int or None, default: None - Shank ID for multi-shank probes. If None, assumes single-shank probe. - - Returns - ------- - contact_id : str - Canonical contact ID string, either "e{electrode_id}" for single-shank - or "s{shank_id}e{electrode_id}" for multi-shank probes. - - Examples - -------- - >>> _build_canonical_contact_id(123) - 'e123' - >>> _build_canonical_contact_id(123, shank_id=0) - 's0e123' - """ - if shank_id is not None: - return f"s{shank_id}e{electrode_id}" - else: - return f"e{electrode_id}" - - def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: """ Parse IMRO (Imec ReadOut) table string into structured per-channel data.