Skip to content

Commit f38bb7b

Browse files
authored
Merge branch 'master' into add_brw_4.x_sparse
2 parents 1227512 + db9eca8 commit f38bb7b

File tree

9 files changed

+330
-65
lines changed

9 files changed

+330
-65
lines changed

neo/rawio/blackrockrawio.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@ def _parse_header(self):
276276
self.internal_unit_ids = [] # pair of chan['packet_id'], spikes['unit_class_nb']
277277
for i in range(len(self.__nev_ext_header[b"NEUEVWAV"])):
278278

279-
# electrode_id values are stored at uint16 which can overflow when
280-
# multiplying by 1000 below. We convert to a regular python int which
279+
# electrode_id values are stored at uint16 which can overflow when
280+
# multiplying by 1000 below. We convert to a regular python int which
281281
# won't overflow
282282
channel_id = int(self.__nev_ext_header[b"NEUEVWAV"]["electrode_id"][i])
283283

neo/rawio/intanrawio.py

Lines changed: 186 additions & 54 deletions
Large diffs are not rendered by default.

neo/rawio/neuralynxrawio/neuralynxrawio.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def _parse_header(self):
263263
t_start = copy.copy(file_mmap[0][0])
264264
else: # empty file
265265
t_start = 0
266-
stream_prop = (info["sampling_rate"], n_packets, t_start)
266+
stream_prop = (float(info["sampling_rate"]), int(n_packets), float(t_start))
267267
if stream_prop not in stream_props:
268268
stream_props[stream_prop] = {"stream_id": len(stream_props), "filenames": [filename]}
269269
else:

neo/rawio/spikegadgetsrawio.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,9 @@ def _parse_header(self):
227227
if num_ephy_channels % num_chan_per_chip == 0:
228228
all_hw_chans = [int(schan.attrib["hwChan"]) for trode in sconf for schan in trode]
229229
missing_hw_chans = set(range(num_ephy_channels)) - set(all_hw_chans)
230-
channel_ids = self._produce_ephys_channel_ids(num_ephy_channels_xml, num_chan_per_chip, missing_hw_chans)
230+
channel_ids = self._produce_ephys_channel_ids(
231+
num_ephy_channels_xml, num_chan_per_chip, missing_hw_chans
232+
)
231233
raw_channel_ids = False
232234
else:
233235
raw_channel_ids = True

neo/rawio/spikeglxrawio.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,6 @@ def get_segment_tuple(info):
376376
for info in info_list:
377377
info["seg_index"] = segment_tuple_to_segment_index[get_segment_tuple(info)]
378378

379-
380379
for info in info_list:
381380
# device_kind is imec, nidq
382381
if info.get("device_kind") == "imec":

neo/test/generate_datasets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def random_segment():
165165
rec_datetime=random_datetime(),
166166
**random_annotations(4),
167167
)
168-
168+
169169
n_sigs = random.randint(0, 5)
170170
for i in range(n_sigs):
171171
seg.analogsignals.append(random_signal())

neo/test/iotest/test_intanio.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class TestIntanIO(
2323
"intan/intan_fpc_rhs_test_240329_091637/info.rhs", # Format one-file-per-channel
2424
"intan/intan_fps_rhs_test_240329_091536/info.rhs", # Format one-file-per-signal
2525
"intan/rhd_fpc_multistim_240514_082044/info.rhd", # Multiple digital channels one-file-per-channel rhd
26+
"intan/rhs_stim_data_single_file_format/intanTestFile.rhs", # header-attached rhs data with stimulus current
27+
"intan/test_fcs_dc_250327_154333/info.rhs", # this is an example of only having dc amp rather than amp files
28+
#"intan/test_fpc_stim_250327_151617/info.rhs", # wrong files Heberto will fix
2629
]
2730

2831

neo/test/rawiotest/test_intanrawio.py

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ class TestIntanRawIO(
2121
"intan/intan_fpc_rhs_test_240329_091637/info.rhs", # Format one-file-per-channel
2222
"intan/intan_fps_rhs_test_240329_091536/info.rhs", # Format one-file-per-signal
2323
"intan/rhd_fpc_multistim_240514_082044/info.rhd", # Multiple digital channels one-file-per-channel rhd
24+
"intan/rhs_stim_data_single_file_format/intanTestFile.rhs", # header-attached rhs data with stimulus current
25+
"intan/test_fcs_dc_250327_154333/info.rhs", # this is an example of only having dc amp rather than amp files
26+
"intan/test_fpc_stim_250327_151617/info.rhs", # wrong files names Heberto will fix naimgin in the future
27+
2428
]
2529

2630
def test_annotations(self):
@@ -67,7 +71,7 @@ def test_annotations(self):
6771
)
6872
np.testing.assert_array_equal(signal_array_annotations["board_stream_num"][:10], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
6973

70-
def test_correct_reading_one_file_per_channel(self):
74+
def test_correct_reading_one_file_per_channel_amplifiers(self):
7175
"Issue: https://github.com/NeuralEnsemble/python-neo/issues/1599"
7276
# Test reading of one-file-per-channel format file. The channels should match the raw files
7377
file_path = Path(self.get_local_path("intan/intan_fpc_test_231117_052630/info.rhd"))
@@ -81,11 +85,134 @@ def test_correct_reading_one_file_per_channel(self):
8185
amplifier_file_paths = [path for path in folder_path.iterdir() if "amp" in path.name]
8286
channel_names = [path.name[4:-4] for path in amplifier_file_paths]
8387

88+
amplifier_stream_index = 0
8489
for channel_name, amplifier_file_path in zip(channel_names, amplifier_file_paths):
8590
data_raw = np.fromfile(amplifier_file_path, dtype=np.int16).squeeze()
86-
data_from_neo = intan_reader.get_analogsignal_chunk(channel_ids=[channel_name], stream_index=0).squeeze()
91+
data_from_neo = intan_reader.get_analogsignal_chunk(channel_ids=[channel_name], stream_index=amplifier_stream_index).squeeze()
8792
np.testing.assert_allclose(data_raw, data_from_neo)
8893

94+
def test_correct_reading_one_file_per_channel_rhs_stim(self):
95+
"Zach request for testing that the channel order is correct"
96+
# Test reading of one-file-per-channel format file. The channels should match the raw files
97+
file_path = Path(self.get_local_path("intan/test_fpc_stim_250327_151617/info.rhs"))
98+
intan_reader = IntanRawIO(filename=file_path)
99+
intan_reader.parse_header()
100+
101+
# This should be the folder where the files of all the channels are stored
102+
folder_path = file_path.parent
103+
104+
# The paths for the stim channels are stim-A-000.dat, stim-A-001.dat, stim-A-002.dat,
105+
# Whereas the ids are A-001_STIM, A-002_STIM, A-003_STIM, etc
106+
stim_file_paths = [path for path in folder_path.iterdir() if "stim" in path.name]
107+
channel_ids = [f"{p.stem[5:]}_STIM" for p in stim_file_paths]
108+
109+
stim_stream_index = 2
110+
for channel_id, amplifier_file_path in zip(channel_ids, stim_file_paths):
111+
data_raw = np.fromfile(amplifier_file_path, dtype=np.uint16)
112+
decoded_data = intan_reader._decode_current_from_stim_data(data_raw, 0, data_raw.shape[0])
113+
data_from_neo = intan_reader.get_analogsignal_chunk(channel_ids=[channel_id], stream_index=stim_stream_index).squeeze()
114+
np.testing.assert_allclose(decoded_data, data_from_neo)
115+
116+
117+
def test_correct_decoding_of_stimulus_current(self):
118+
# See https://github.com/NeuralEnsemble/python-neo/pull/1660 for discussion
119+
# See https://gin.g-node.org/NeuralEnsemble/ephy_testing_data/src/master/intan/README.md#rhs_stim_data_single_file_format
120+
# For a description of the data
121+
122+
file_path = Path(self.get_local_path("intan/rhs_stim_data_single_file_format/intanTestFile.rhs"))
123+
intan_reader = IntanRawIO(filename=file_path)
124+
intan_reader.parse_header()
125+
126+
signal_streams = intan_reader.header['signal_streams']
127+
stream_ids = signal_streams['id'].tolist()
128+
stream_index = stream_ids.index('11')
129+
sampling_rate = intan_reader.get_signal_sampling_rate(stream_index=stream_index)
130+
sig_chunk = intan_reader.get_analogsignal_chunk(stream_index=stream_index, channel_ids=["D-016_STIM"])
131+
final_stim = intan_reader.rescale_signal_raw_to_float(sig_chunk, stream_index=stream_index, channel_ids=["D-016_STIM"])
132+
133+
# This contains only the first pulse and I got this by visual inspection
134+
data_to_test = final_stim[200:250]
135+
136+
positive_pulse_size = np.max(data_to_test).item()
137+
negative_pulse_size = np.min(data_to_test).item()
138+
139+
expected_value = 60 * 1e-6# 60 microamperes
140+
141+
# Assert is close float
142+
assert np.isclose(positive_pulse_size, expected_value)
143+
assert np.isclose(negative_pulse_size, -expected_value)
144+
145+
# Check that negative pulse is leading
146+
argmin = np.argmin(data_to_test)
147+
argmax = np.argmax(data_to_test)
148+
assert argmin < argmax
149+
150+
# Check that the negative pulse is 200 us long
151+
negative_pulse_frames = np.where(data_to_test > 0)[0]
152+
number_of_negative_frames = negative_pulse_frames.size
153+
duration_of_negative_pulse = number_of_negative_frames / sampling_rate
154+
155+
expected_duration = 200 * 1e-6 # 400 microseconds / 2
156+
assert np.isclose(duration_of_negative_pulse, expected_duration)
157+
158+
# Check that the positive pulse is 200 us long
159+
positive_pulse_frames = np.where(data_to_test > 0)[0]
160+
number_of_positive_frames = positive_pulse_frames.size
161+
duration_of_positive_pulse = number_of_positive_frames / sampling_rate
162+
expected_duration = 200 * 1e-6 # 400 microseconds / 2
163+
164+
assert np.isclose(duration_of_positive_pulse, expected_duration)
165+
166+
167+
def test_correct_decoding_of_stimulus_current(self):
168+
# See https://github.com/NeuralEnsemble/python-neo/pull/1660 for discussion
169+
# See https://gin.g-node.org/NeuralEnsemble/ephy_testing_data/src/master/intan/README.md#rhs_stim_data_single_file_format
170+
# For a description of the data
171+
172+
file_path = Path(self.get_local_path("intan/rhs_stim_data_single_file_format/intanTestFile.rhs"))
173+
intan_reader = IntanRawIO(filename=file_path)
174+
intan_reader.parse_header()
175+
176+
signal_streams = intan_reader.header['signal_streams']
177+
stream_ids = signal_streams['id'].tolist()
178+
stream_index = stream_ids.index('11')
179+
sampling_rate = intan_reader.get_signal_sampling_rate(stream_index=stream_index)
180+
sig_chunk = intan_reader.get_analogsignal_chunk(stream_index=stream_index, channel_ids=["D-016_STIM"])
181+
final_stim = intan_reader.rescale_signal_raw_to_float(sig_chunk, stream_index=stream_index, channel_ids=["D-016_STIM"])
182+
183+
# This contains only the first pulse and I got this by visual inspection
184+
data_to_test = final_stim[200:250]
185+
186+
positive_pulse_size = np.max(data_to_test).item()
187+
negative_pulse_size = np.min(data_to_test).item()
188+
189+
expected_value = 60 * 1e-6# 60 microamperes
190+
191+
# Assert is close float
192+
assert np.isclose(positive_pulse_size, expected_value)
193+
assert np.isclose(negative_pulse_size, -expected_value)
194+
195+
# Check that negative pulse is leading
196+
argmin = np.argmin(data_to_test)
197+
argmax = np.argmax(data_to_test)
198+
assert argmin < argmax
199+
200+
# Check that the negative pulse is 200 us long
201+
negative_pulse_frames = np.where(data_to_test > 0)[0]
202+
number_of_negative_frames = negative_pulse_frames.size
203+
duration_of_negative_pulse = number_of_negative_frames / sampling_rate
204+
205+
expected_duration = 200 * 1e-6 # 400 microseconds / 2
206+
assert np.isclose(duration_of_negative_pulse, expected_duration)
207+
208+
# Check that the positive pulse is 200 us long
209+
positive_pulse_frames = np.where(data_to_test > 0)[0]
210+
number_of_positive_frames = positive_pulse_frames.size
211+
duration_of_positive_pulse = number_of_positive_frames / sampling_rate
212+
expected_duration = 200 * 1e-6 # 400 microseconds / 2
213+
214+
assert np.isclose(duration_of_positive_pulse, expected_duration)
215+
89216

90217
if __name__ == "__main__":
91218
unittest.main()

neo/test/rawiotest/test_spikegadgetsrawio.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ class TestSpikeGadgetsRawIO(
2121
def test_parse_header_missing_channels(self):
2222

2323
file_path = Path(self.get_local_path("spikegadgets/SL18_D19_S01_F01_BOX_SLP_20230503_112642_stubbed.rec"))
24-
reader = SpikeGadgetsRawIO(filename = file_path)
24+
reader = SpikeGadgetsRawIO(filename=file_path)
2525
reader.parse_header()
2626

2727
assert_array_equal(
28-
reader.header['signal_channels']['id'],
28+
reader.header["signal_channels"]["id"],
29+
# fmt: off
2930
[
3031
'ECU_Ain1', 'ECU_Ain2', 'ECU_Ain3', 'ECU_Ain4', 'ECU_Ain5', 'ECU_Ain6',
3132
'ECU_Ain7', 'ECU_Ain8', 'ECU_Aout1', 'ECU_Aout2', 'ECU_Aout3', 'ECU_Aout4', '0',
@@ -46,6 +47,7 @@ def test_parse_header_missing_channels(self):
4647
'218', '250', '27', '59', '91', '123', '155', '187', '219', '251', '28', '60', '92',
4748
'156', '188', '220', '252', '29', '61', '93', '125', '157', '189', '221', '253', '30',
4849
'62', '94', '158', '190', '222', '254', '31', '63', '95', '127', '159', '191', '223',
49-
'255',
50+
'255'
5051
]
52+
# fmt: on
5153
)

0 commit comments

Comments
 (0)