Skip to content

Commit 8720bb2

Browse files
h-mayorquinzm711
andauthored
Add another tests for one file per channel intan rhs (#1677)
* add stim demultiplex rhs intan * fix tests * docstring and demultiplex to decode name change * Update neo/rawio/intanrawio.py Co-authored-by: Zach McKenzie <[email protected]> * Update neo/rawio/intanrawio.py Co-authored-by: Zach McKenzie <[email protected]> * Update neo/rawio/intanrawio.py Co-authored-by: Zach McKenzie <[email protected]> * fix * refactor and comments * add test * add comments to test * add gin comment * update header parsing for stim + update docstring * add stim to channel check * wip * oops * fix for header attached * fix for one-file-per-signal * add one new test file * clean up logic * add new tests and change custom names --------- Co-authored-by: Zach McKenzie <[email protected]>
1 parent 90589d4 commit 8720bb2

File tree

2 files changed

+80
-4
lines changed

2 files changed

+80
-4
lines changed

neo/rawio/intanrawio.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ def _demultiplex_digital_data(self, raw_digital_data, channel_ids, i_start, i_st
566566
set high and the rest low, the 16-bit word would be 2^0 + 2^4 + 2^5 = 1 + 16 + 32 = 49.
567567
568568
The native_order property for each channel corresponds to its bit position in the packed word.
569+
569570
"""
570571
dtype = np.uint16 # We fix this to match the memmap dtype
571572
output = np.zeros((i_stop - i_start, len(channel_ids)), dtype=dtype)
@@ -952,6 +953,7 @@ def read_rhs(filename, file_format: str):
952953
chan_info_dc = dict(chan_info)
953954
name = chan_info["native_channel_name"]
954955
chan_info_dc["native_channel_name"] = name + "_DC"
956+
chan_info_dc["custom_channel_name"] = chan_info_dc["custom_channel_name"] + "_DC"
955957
chan_info_dc["sampling_rate"] = sr
956958
chan_info_dc["units"] = "mV"
957959
chan_info_dc["gain"] = 19.23
@@ -971,7 +973,6 @@ def read_rhs(filename, file_format: str):
971973
# Add stim channels
972974
for chan_info in stream_name_to_channel_info_list["RHS2000 amplifier channel"]:
973975
# stim channel presence is not indicated in the header so for some formats each amplifier channel has a stim channel, but for other formats this isn't the case.
974-
975976
if file_format == "one-file-per-channel":
976977
# Some amplifier channels don't have a corresponding stim channel,
977978
# so we need to make sure we don't add channel info for stim channels that don't exist.
@@ -985,6 +986,7 @@ def read_rhs(filename, file_format: str):
985986
chan_info_stim = dict(chan_info)
986987
name = chan_info["native_channel_name"]
987988
chan_info_stim["native_channel_name"] = name + "_STIM"
989+
chan_info_stim["custom_channel_name"] = chan_info_stim["custom_channel_name"] + "_STIM"
988990
chan_info_stim["sampling_rate"] = sr
989991
chan_info_stim["units"] = "A" # Amps
990992
chan_info_stim["gain"] = global_info["stim_step_size"]

neo/test/rawiotest/test_intanrawio.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class TestIntanRawIO(
2323
"intan/rhd_fpc_multistim_240514_082044/info.rhd", # Multiple digital channels one-file-per-channel rhd
2424
"intan/rhs_stim_data_single_file_format/intanTestFile.rhs", # header-attached rhs data with stimulus current
2525
"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 Heberto will fix
26+
"intan/test_fpc_stim_250327_151617/info.rhs", # wrong files names Heberto will fix naimgin in the future
27+
2728
]
2829

2930
def test_annotations(self):
@@ -70,7 +71,7 @@ def test_annotations(self):
7071
)
7172
np.testing.assert_array_equal(signal_array_annotations["board_stream_num"][:10], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
7273

73-
def test_correct_reading_one_file_per_channel(self):
74+
def test_correct_reading_one_file_per_channel_amplifiers(self):
7475
"Issue: https://github.com/NeuralEnsemble/python-neo/issues/1599"
7576
# Test reading of one-file-per-channel format file. The channels should match the raw files
7677
file_path = Path(self.get_local_path("intan/intan_fpc_test_231117_052630/info.rhd"))
@@ -84,11 +85,84 @@ def test_correct_reading_one_file_per_channel(self):
8485
amplifier_file_paths = [path for path in folder_path.iterdir() if "amp" in path.name]
8586
channel_names = [path.name[4:-4] for path in amplifier_file_paths]
8687

88+
amplifier_stream_index = 0
8789
for channel_name, amplifier_file_path in zip(channel_names, amplifier_file_paths):
8890
data_raw = np.fromfile(amplifier_file_path, dtype=np.int16).squeeze()
89-
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()
9092
np.testing.assert_allclose(data_raw, data_from_neo)
9193

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+
92166

93167
def test_correct_decoding_of_stimulus_current(self):
94168
# See https://github.com/NeuralEnsemble/python-neo/pull/1660 for discussion

0 commit comments

Comments
 (0)