@@ -1066,6 +1066,13 @@ def read_openephys(
10661066 channel_ids = np .array ([int (ch [2 :]) for ch in channel_names ])
10671067 channel_order = np .argsort (channel_ids )
10681068
1069+ # Detect missing channels
1070+ sorted_channel_ids = sorted (channel_ids )
1071+ all_channel_ids_range = set (range (sorted_channel_ids [0 ], sorted_channel_ids [- 1 ] + 1 ))
1072+ missing_channels = sorted (all_channel_ids_range - set (channel_ids ))
1073+ if missing_channels :
1074+ warnings .warn (f"Missing channels detected in XML: { missing_channels } " )
1075+
10691076 # sort channel_names and channel_values
10701077 channel_names = channel_names [channel_order ]
10711078 channel_values = np .array (list (channels .attrib .values ()))[channel_order ]
@@ -1087,6 +1094,60 @@ def read_openephys(
10871094 return None
10881095 xpos = np .array ([float (electrode_xpos .attrib [ch ]) for ch in channel_names ])
10891096 ypos = np .array ([float (electrode_ypos .attrib [ch ]) for ch in channel_names ])
1097+
1098+ # Fix missing channels if detected
1099+ if missing_channels :
1100+ # Detect repeating pattern in <ELECTRODE_XPOS> values
1101+ xpos_values = [int (float (value )) for value in electrode_xpos .attrib .values ()]
1102+ pattern_length = next (
1103+ (i for i in range (1 , len (xpos_values ) // 2 ) if xpos_values [:i ] == xpos_values [i :2 * i ]),
1104+ len (xpos_values )
1105+ )
1106+ xpos_pattern = xpos_values [:pattern_length ]
1107+
1108+ # Detect repeating pattern in <ELECTRODE_YPOS> values
1109+ ypos_values = [int (float (value )) for value in electrode_ypos .attrib .values ()]
1110+ ypos_step = np .unique (np .diff (sorted (set (ypos_values ))))[0 ]
1111+
1112+ # Determine fill value for channel values
1113+ fill_value = channel_values [0 ] # TODO: how to do this more robustly?
1114+
1115+ # Extract shank id from fill value if it contains a colon
1116+ if ":" in fill_value :
1117+ shank_id_for_missing = int (fill_value .split (":" )[1 ])
1118+ else :
1119+ shank_id_for_missing = 0
1120+
1121+ # Add missing channels to xpos, ypos, channel_names, and channel_values
1122+ for missing_channel in missing_channels :
1123+ # Calculate positions for missing channel
1124+ pattern_value_xpos = xpos_pattern [missing_channel % pattern_length ]
1125+ pattern_value_ypos = (missing_channel // 2 ) * ypos_step
1126+
1127+ # Add to arrays
1128+ xpos = np .append (xpos , pattern_value_xpos )
1129+ ypos = np .append (ypos , pattern_value_ypos )
1130+ channel_names = np .append (channel_names , f"CH{ missing_channel } " )
1131+ channel_values = np .append (channel_values , fill_value )
1132+
1133+ # Update shank_ids if it exists
1134+ if shank_ids is not None :
1135+ shank_ids = np .append (shank_ids , shank_id_for_missing )
1136+
1137+ warnings .warn (f"Fixed missing channel { missing_channel } with x={ pattern_value_xpos } , y={ pattern_value_ypos } " )
1138+
1139+ # Re-sort arrays by channel number
1140+ channel_ids = np .array ([int (ch [2 :]) for ch in channel_names ])
1141+ channel_order = np .argsort (channel_ids )
1142+ channel_names = channel_names [channel_order ]
1143+ channel_values = channel_values [channel_order ]
1144+ xpos = xpos [channel_order ]
1145+ ypos = ypos [channel_order ]
1146+
1147+ # Re-sort shank_ids if it exists
1148+ if shank_ids is not None :
1149+ shank_ids = shank_ids [channel_order ]
1150+
10901151 positions = np .array ([xpos , ypos ]).T
10911152
10921153 probe_part_number = np_probe .get ("probe_part_number" , None )
0 commit comments