diff --git a/src/probeinterface/neuropixels_tools.py b/src/probeinterface/neuropixels_tools.py index 2e4eb888..76551b07 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 @@ -22,15 +23,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 +85,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 +119,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,142 +179,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 probe position from the imro file used in input of SpikeGlx and Open-Ephys for neuropixels probes. - - Parameters - ---------- - file_path : Path or str - The .imro file path - - Returns - ------- - probe : Probe object - - """ - # the input is an imro file - 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 - - 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"]}, - ) - - 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) - - # 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()) - - probe.annotate(shank_tips=shank_tips) - - # wire it - probe.set_device_channel_indices(np.arange(positions.shape[0])) - - # 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"]), - ) - - # 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) - - return probe - - def build_neuropixels_probe(probe_part_number: str) -> Probe: """ Build a Neuropixels probe with all possible contacts from the probe part number. @@ -432,7 +265,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 = [] @@ -451,7 +284,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): @@ -482,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. @@ -571,139 +437,69 @@ 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. +######################### +# SpikeGLX / IMRO zone # +######################### - See Also - -------- - https://billkarsh.github.io/SpikeGLX/help/imroTables/ +def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: """ + Parse IMRO (Imec ReadOut) table string into structured per-channel data. - probe_type_num_chans, *imro_table_values_list, _ = imro_str.strip().split(")") - - # probe_type_num_chans looks like f"({probe_type},{num_chans}" - probe_type = probe_type_num_chans.split(",")[0][1:] - - probe_features = _load_np_probe_features() - pt_metadata, fields, mux_info = get_probe_metadata_from_probe_features(probe_features, 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) - - 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 - - probe = _make_npx_probe_from_description(pt_metadata, imDatPrb_pn, elec_ids, shank_ids, mux_info) - - # scalar annotations - probe.annotate( - probe_type=probe_type, - ) - - # vector annotations - 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 - vector_properties_available[imro_field_to_pi_field.get(k)] = v - - probe.annotate_contacts(**vector_properties_available) - - return probe - + IMRO format: "(probe_type,num_chans)(ch0 bank0 ref0 ...)(ch1 bank1 ref1 ...)..." + Example: "(0,384)(0 1 0 500 250 1)(1 0 0 500 250 1)..." -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`. + Note: The IMRO header contains a probe_type field (e.g., "0", "21", "24"), which is + a numeric format version identifier that specifies which IMRO table structure was used. + Different probe generations use different IMRO formats. This is a file format detail, + not a physical probe property. Parameters ---------- - probe_features : dict - Dictionary obtained when reading in the `neuropixels_probe_features.json` file. - imDatPrb_pn : str - Probe part number. + imro_table_string : str + IMRO table string from SpikeGLX metadata file + probe_part_number : str + Probe part number (e.g., "NP1000", "NP2000") 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. + imro_per_channel : dict + 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_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}, + "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() + probe_spec = probe_features["neuropixels_probes"][probe_part_number] + imro_format = probe_spec["imro_table_format_type"] + imro_fields_string = probe_features["z_imro_formats"][imro_format + "_elm_flds"] + imro_fields = tuple(imro_fields_string.replace("(", "").replace(")", "").split(" ")) - 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) + # 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): + imro_per_channel[field].append(field_value) - return probe_metadata, imro_fields, mux_information + return imro_per_channel def write_imro(file: str | Path, probe: Probe): @@ -759,99 +555,233 @@ def write_imro(file: str | Path, probe: Probe): f.write("".join(ret)) -## -# SpikeGLX zone for neuropixel -## - - -def _build_canonical_contact_id(electrode_id: int, shank_id: int | None = None) -> str: +def _get_imro_active_contact_ids(imro_per_channel: dict) -> list[str]: """ - Build the canonical contact ID string for a Neuropixels electrode. + Get canonical contact ID strings for the active electrodes in a parsed IMRO table. - This establishes the standard naming convention used throughout probeinterface - for Neuropixels contact identification. + 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 ---------- - 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. + imro_per_channel : dict + Parsed IMRO data from _parse_imro_string. Modified in place if electrode + IDs need to be resolved. 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. + 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) + assert ( + "electrode" in imro_per_channel + ), f"Could not resolve electrode IDs from IMRO fields: {list(imro_per_channel.keys())}" - Examples - -------- - >>> _build_canonical_contact_id(123) - 'e123' - >>> _build_canonical_contact_id(123, shank_id=0) - 's0e123' + 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: """ - if shank_id is not None: - return f"s{shank_id}e{electrode_id}" - else: - return f"e{electrode_id}" + 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. -def _parse_imro_string(imro_table_string: str, probe_part_number: str) -> dict: + Modifies imro_per_channel in place, adding the "electrode" key. + + Parameters + ---------- + imro_per_channel : dict + Parsed IMRO data from _parse_imro_string. Modified in place. """ - Parse IMRO (Imec ReadOut) table string into structured per-channel data. + if "channel" not in imro_per_channel: + return - IMRO format: "(probe_type,num_chans)(ch0 bank0 ref0 ...)(ch1 bank1 ref1 ...)..." - Example: "(0,384)(0 1 0 500 250 1)(1 0 0 500 250 1)..." + 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() - Note: The IMRO header contains a probe_type field (e.g., "0", "21", "24"), which is - a numeric format version identifier that specifies which IMRO table structure was used. - Different probe generations use different IMRO formats. This is a file format detail, - not a physical probe property. + +def _resolve_active_contacts_for_np1110(imro_per_channel: dict) -> 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_table_string : str - IMRO table string from SpikeGLX metadata file - probe_part_number : str - Probe part number (e.g., "NP1000", "NP2000") + imro_per_channel : dict + Parsed IMRO data with keys "group", "bankA", "bankB" (24 entries each) + and "header" dict containing "col_mode". + + References + ---------- + https://github.com/billkarsh/SpikeGLX/blob/51b96c70204c025748d69c9a588e07406728f9eb/Src-imro/IMROTbl_T1110.cpp + """ + 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 _ensure_active_contacts_available) + ) + + 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"] + + # 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 grpIdx(ch): + return 2 * ((ch % 384) // 32) + ((ch % 384) & 1) + + 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: + return 2 * grp_col + (1 - ingrp_col) + else: + return 2 * grp_col + ingrp_col + + 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) + else: + b0_row = 8 * grp_row + ingrp_row + return 48 * bank + b0_row + + 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 <= 3: + if col_mode == 1: # OUTER + return bankA if not (c & 1) else bankB + else: # INNER + return bankA if (c & 1) else bankB + else: + 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 = 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 + 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 ------- - 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,...]} + probe : Probe + Probe object with geometry, contact annotations, and device channel mapping. + + See Also + -------- + https://billkarsh.github.io/SpikeGLX/help/imroTables/ + """ - # Get IMRO field format from catalogue - probe_features = _load_np_probe_features() - probe_spec = probe_features["neuropixels_probes"][probe_part_number] - imro_format = probe_spec["imro_table_format_type"] - imro_fields_string = probe_features["z_imro_formats"][imro_format + "_elm_flds"] - imro_fields = tuple(imro_fields_string.replace("(", "").replace(")", "").split(" ")) + # ===== 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()) - # 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} - 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): - imro_per_channel[field].append(field_value) + imro_table_header_str, *imro_table_values_list, _ = imro_str.strip().split(")") + imro_table_header = tuple(map(int, imro_table_header_str[1:].split(","))) - # Ensure "electrode" key always exists with physical electrode IDs - # Different probe types encode electrode selection differently - 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() + 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) - return imro_per_channel + 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: @@ -906,22 +836,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 ===== - # 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) - ] - - # ===== 5. Slice full probe to active electrodes ===== - # Find indices of active contacts in the full probe, preserving IMRO order + # ===== 4. Slice full probe to active electrodes ===== + 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) - # ===== 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} @@ -932,7 +853,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) @@ -940,7 +861,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. @@ -949,7 +870,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 ===== + # ===== 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) @@ -959,7 +880,7 @@ def read_spikeglx(file: str | Path) -> Probe: probe.annotate(port=imDatPrb_port) probe.annotate(slot=imDatPrb_slot) - # ===== 7. Set device channel indices (wiring) ===== + # ===== 8. Set device channel indices (wiring) ===== probe.set_device_channel_indices(np.arange(probe.get_contact_count())) return probe @@ -1023,10 +944,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. @@ -1058,9 +975,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( @@ -1092,7 +1009,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)) @@ -1257,7 +1174,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 +1202,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 +1246,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 +1257,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..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 @@ -289,11 +317,13 @@ 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" + assert "adc_group" in probe.contact_annotations + assert "adc_sample_order" in probe.contact_annotations # Test contact geometry contact_width = 5.0 @@ -315,6 +345,129 @@ def test_ultra_probe(): assert unique_y_values.size == expected_electode_rows +def test_NP1110_probes(): + # For bank0, all electrodes are packet at the tip, in an 48x8 grid + 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 + 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 + + # For this dataset, bank4 is selected with a 2x192 configuration + # (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 + 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 + + # 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 + + # 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 + + def test_CatGT_NP1(): probe = read_spikeglx(data_path / "catgt.meta") assert "1.0" in probe.description @@ -400,4 +553,4 @@ def test_snsGeomMap(): if __name__ == "__main__": # test_NP2_1_shanks() - test_snsGeomMap() + test_NP1110_probes()