1212from pathlib import Path
1313from typing import Union , Optional
1414import re
15+ import warnings
1516import json
1617from collections import OrderedDict
1718from packaging .version import Version , parse
@@ -1262,12 +1263,14 @@ def read_openephys(
12621263 oe_version = parse (info_chain .find ("VERSION" ).text )
12631264 signal_chain = root .find ("SIGNALCHAIN" )
12641265 neuropix_pxi = None
1266+ record_node = None
12651267 for processor in signal_chain :
12661268 if "PROCESSOR" == processor .tag :
12671269 name = processor .attrib ["name" ]
12681270 if "Neuropix-PXI" in name :
12691271 neuropix_pxi = processor
1270- break
1272+ if "Record Node" in name :
1273+ record_node = processor
12711274
12721275 if neuropix_pxi is None :
12731276 if raise_error :
@@ -1468,8 +1471,10 @@ def read_openephys(
14681471 else :
14691472 # in case of a single probe, make sure it is consistent with optional
14701473 # stream_name, probe_name, or serial number
1474+ available_probe_name = np_probes_info [0 ]['name' ]
1475+ available_serial_number = np_probes_info [0 ]['serial_number' ]
1476+
14711477 if stream_name :
1472- available_probe_name = np_probes_info [0 ]['name' ]
14731478 if available_probe_name not in stream_name :
14741479 if raise_error :
14751480 raise Exception (
@@ -1478,7 +1483,6 @@ def read_openephys(
14781483 )
14791484 return None
14801485 if probe_name :
1481- available_probe_name = np_probes_info [0 ]['name' ]
14821486 if probe_name != available_probe_name :
14831487 if raise_error :
14841488 raise Exception (
@@ -1487,7 +1491,6 @@ def read_openephys(
14871491 )
14881492 return None
14891493 if serial_number :
1490- available_serial_number = np_probes_info [0 ]['serial_number' ]
14911494 if str (serial_number ) != available_serial_number :
14921495 if raise_error :
14931496 raise Exception (
@@ -1511,6 +1514,19 @@ def read_openephys(
15111514 contact_width = 12
15121515 shank_pitch = 250
15131516
1517+ contact_ids = np_probe_info ['contact_ids' ] if np_probe_info ['contact_ids' ] is not None else None
1518+
1519+ # check if subset of channels
1520+ chans_saved = get_saved_channel_indices_from_openephys_settings (settings_file , stream_name = stream_name )
1521+
1522+ # if a recording state is found, slice probe
1523+ if chans_saved is not None :
1524+ positions = positions [chans_saved ]
1525+ if shank_ids is not None :
1526+ shank_ids = np .array (shank_ids )[chans_saved ]
1527+ if contact_ids is not None :
1528+ contact_ids = np .array (contact_ids )[chans_saved ]
1529+
15141530 probe = Probe (ndim = 2 , si_units = "um" )
15151531 probe .set_contacts (
15161532 positions = positions ,
@@ -1526,8 +1542,8 @@ def read_openephys(
15261542 probe_serial_number = np_probe .attrib ["probe_serial_number" ],
15271543 )
15281544
1529- if np_probe_info [ ' contact_ids' ] is not None :
1530- probe .set_contact_ids (np_probe_info [ ' contact_ids' ] )
1545+ if contact_ids is not None :
1546+ probe .set_contact_ids (contact_ids )
15311547
15321548 polygon = polygon_description ["default" ]
15331549 if shank_ids is None :
@@ -1547,6 +1563,69 @@ def read_openephys(
15471563 return probe
15481564
15491565
1566+ def get_saved_channel_indices_from_openephys_settings (settings_file , stream_name ):
1567+ """
1568+ Returns an array with the subset of saved channels indices (if used)
1569+
1570+ Parameters
1571+ ----------
1572+ settings_file : str or Path
1573+ The path to the settings file
1574+ stream_name : str
1575+ The stream name that contains the probe name
1576+ For example, "Record Node 100#ProbeA-AP" will select the AP stream of ProbeA.
1577+
1578+ Returns
1579+ -------
1580+ chans_saved
1581+ np.array of saved channel indices or None
1582+ """
1583+ # check if subset of channels
1584+ ET = import_safely ("xml.etree.ElementTree" )
1585+ # parse xml
1586+ tree = ET .parse (str (settings_file ))
1587+ root = tree .getroot ()
1588+
1589+ signal_chain = root .find ("SIGNALCHAIN" )
1590+ record_node = None
1591+ for processor in signal_chain :
1592+ if "PROCESSOR" == processor .tag :
1593+ name = processor .attrib ["name" ]
1594+ if "Record Node" in name :
1595+ record_node = processor
1596+ break
1597+ chans_saved = None
1598+ if record_node is not None :
1599+ custom_params = record_node .find ("CUSTOM_PARAMETERS" )
1600+ if custom_params is not None :
1601+ custom_streams = custom_params .findall ("STREAM" )
1602+ custom_stream_names = [stream .attrib ["name" ] for stream in custom_streams ]
1603+ if len (custom_streams ) > 0 :
1604+ recording_states = [stream .attrib .get ("recording_state" , None ) for stream in custom_streams ]
1605+ has_custom_states = False
1606+ for rs in recording_states :
1607+ if rs is not None and rs != "ALL" :
1608+ has_custom_states = True
1609+ if has_custom_states :
1610+ if len (custom_streams ) > 1 :
1611+ assert stream_name is not None , \
1612+ (f"More than one stream found with custom parameters: { custom_stream_names } . "
1613+ f"Use the `stream_name` argument to choose the correct stream" )
1614+ possible_custom_streams = [stream for stream in custom_streams
1615+ if stream .attrib ["name" ] in stream_name ]
1616+ if len (possible_custom_streams ) > 1 :
1617+ warnings .warn (f"More than one custom parameters associated to { stream_name } "
1618+ f"found. Using fisrt one" )
1619+ custom_stream = possible_custom_streams [0 ]
1620+ else :
1621+ custom_stream = custom_streams [0 ]
1622+ recording_state = custom_stream .attrib .get ("recording_state" , None )
1623+ if recording_state is not None :
1624+ if recording_state != "ALL" :
1625+ chans_saved = np .array ([chan for chan , r in enumerate (recording_state ) if int (r ) == 1 ])
1626+ return chans_saved
1627+
1628+
15501629def read_mearec (file : Union [str , Path ]) -> Probe :
15511630 """
15521631 Read probe position, and contact shape from a MEArec file.
0 commit comments