33import warnings
44from pathlib import Path
55from typing import Union , Optional
6+ from xml .etree import ElementTree as Etree
67
78import numpy as np
89
@@ -64,6 +65,7 @@ def __init__(
6465 if xml_file_path is not None :
6566 xml_file_path = str (Path (xml_file_path ).absolute ())
6667 self ._kwargs .update (dict (file_path = str (Path (file_path ).absolute ()), xml_file_path = xml_file_path ))
68+ self .xml_file_path = xml_file_path if xml_file_path is not None else Path (file_path ).with_suffix (".xml" )
6769
6870 @classmethod
6971 def map_to_neo_kwargs (cls , file_path , xml_file_path = None ):
@@ -78,6 +80,71 @@ def map_to_neo_kwargs(cls, file_path, xml_file_path=None):
7880
7981 return neo_kwargs
8082
83+ def _parse_xml_file (self , xml_file_path ):
84+ """
85+ Comes from NeuroPhy package by Diba Lab
86+ """
87+ tree = Etree .parse (xml_file_path )
88+ myroot = tree .getroot ()
89+
90+ for sf in myroot .findall ("acquisitionSystem" ):
91+ n_channels = int (sf .find ("nChannels" ).text )
92+
93+ channel_groups , skipped_channels , anatomycolors = [], [], {}
94+ for x in myroot .findall ("anatomicalDescription" ):
95+ for y in x .findall ("channelGroups" ):
96+ for z in y .findall ("group" ):
97+ chan_group = []
98+ for chan in z .findall ("channel" ):
99+ if int (chan .attrib ["skip" ]) == 1 :
100+ skipped_channels .append (int (chan .text ))
101+
102+ chan_group .append (int (chan .text ))
103+ if chan_group :
104+ channel_groups .append (np .array (chan_group ))
105+
106+ for x in myroot .findall ("neuroscope" ):
107+ for y in x .findall ("channels" ):
108+ for i , z in enumerate (y .findall ("channelColors" )):
109+ try :
110+ channel_id = str (z .find ("channel" ).text )
111+ color = z .find ("color" ).text
112+
113+ except AttributeError :
114+ channel_id = i
115+ color = "#0080ff"
116+ anatomycolors [channel_id ] = color
117+
118+ discarded_channels = [ch for ch in range (n_channels ) if all (ch not in group for group in channel_groups )]
119+ kept_channels = [ch for ch in range (n_channels ) if ch not in skipped_channels and ch not in discarded_channels ]
120+
121+ return channel_groups , kept_channels , discarded_channels , anatomycolors
122+
123+ def _set_neuroscope_groups (self ):
124+ """
125+ Set the group ids and colors based on the xml file.
126+ These group ids are usually different brain/body anatomical areas, or shanks from multi-shank probes.
127+ The group ids are set as a property of the recording extractor.
128+ """
129+ n = self .get_num_channels ()
130+ group_ids = np .full (n , - 1 , dtype = int ) # Initialize all positions to -1
131+
132+ channel_groups , kept_channels , discarded_channels , colors = self ._parse_xml_file (self .xml_file_path )
133+ for group_id , numbers in enumerate (channel_groups ):
134+ group_ids [numbers ] = group_id # Assign group_id to the positions in `numbers`
135+ self .set_property ("neuroscope_group" , group_ids )
136+ discarded_ppty = np .full (n , False , dtype = bool )
137+ discarded_ppty [discarded_channels ] = True
138+ self .set_property ("discarded_channels" , discarded_ppty )
139+ self .set_property ("colors" , values = list (colors .values ()), ids = list (colors .keys ()))
140+
141+ def prepare_neuroscope_for_ephyviewer (self ):
142+ """
143+ Prepare the recording extractor for ephyviewer by setting the group ids and colors.
144+ This function is not called when the extractor is initialized, and the user must call it manually.
145+ """
146+ self ._set_neuroscope_groups ()
147+
81148
82149class NeuroScopeSortingExtractor (BaseSorting ):
83150 """
0 commit comments