Skip to content

Commit 3208bc7

Browse files
authored
Merge pull request #1476 from h-mayorquin/digital_channel_one_file_per_channel
Fix digital streams in Intan for the `one-file-per-channel` format in rhd.
2 parents 254e9a0 + 2f4e786 commit 3208bc7

File tree

3 files changed

+87
-63
lines changed

3 files changed

+87
-63
lines changed

neo/rawio/intanrawio.py

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,21 @@ class IntanRawIO(BaseRawIO):
5353
extension. Additionally it functions with RHS v 1.0 and RHD 1.0, 1.1, 1.2, 1.3, 2.0,
5454
3.0, and 3.1 files.
5555
56+
* The reader can handle three file formats 'header-attached', 'one-file-per-signal' and
57+
'one-file-per-channel'.
58+
5659
* Intan files contain amplifier channels labeled 'A', 'B' 'C' or 'D'
5760
depending on the port in which they were recorded along with the following
58-
additional channels.
61+
additional streams.
5962
0: 'RHD2000' amplifier channel
6063
1: 'RHD2000 auxiliary input channel',
6164
2: 'RHD2000 supply voltage channel',
6265
3: 'USB board ADC input channel',
6366
4: 'USB board digital input channel',
6467
5: 'USB board digital output channel'
6568
66-
* Due to the structure of the digital input and output channels these can be accessed
67-
as one long vector, which must be post-processed.
69+
* For the "header-attached" and "one-file-per-signal" formats, the structure of the digital input and output channels is
70+
one long vector, which must be post-processed to extract individual digital channel information. See the intantech website for more information on performing this post-processing.
6871
6972
Examples
7073
--------
@@ -511,7 +514,6 @@ def read_rhs(filename, file_format: str):
511514

512515
# 0: RHS2000 amplifier channel.
513516
for chan_info in channels_by_type[0]:
514-
name = chan_info["native_channel_name"]
515517
chan_info["sampling_rate"] = sr
516518
chan_info["units"] = "uV"
517519
chan_info["gain"] = 0.195
@@ -525,6 +527,7 @@ def read_rhs(filename, file_format: str):
525527
chan_info["dtype"] = "int16"
526528
ordered_channels.append(chan_info)
527529
if file_format == "header-attached":
530+
name = chan_info["native_channel_name"]
528531
data_dtype += [(name, "uint16", BLOCK_SIZE)]
529532
else:
530533
data_dtype[0] = "int16"
@@ -533,8 +536,8 @@ def read_rhs(filename, file_format: str):
533536
# if we have dc amp we need to grab the correct number of channels
534537
channel_number_dict[10] = channel_number_dict[0]
535538
for chan_info in channels_by_type[0]:
536-
name = chan_info["native_channel_name"]
537539
chan_info_dc = dict(chan_info)
540+
name = chan_info["native_channel_name"]
538541
chan_info_dc["native_channel_name"] = name + "_DC"
539542
chan_info_dc["sampling_rate"] = sr
540543
chan_info_dc["units"] = "mV"
@@ -553,8 +556,8 @@ def read_rhs(filename, file_format: str):
553556
if file_format != "one-file-per-channel":
554557
channel_number_dict[11] = channel_number_dict[0] # should be one stim / amplifier channel
555558
for chan_info in channels_by_type[0]:
556-
name = chan_info["native_channel_name"]
557559
chan_info_stim = dict(chan_info)
560+
name = chan_info["native_channel_name"]
558561
chan_info_stim["native_channel_name"] = name + "_STIM"
559562
chan_info_stim["sampling_rate"] = sr
560563
# stim channel are complicated because they are coded
@@ -576,43 +579,51 @@ def read_rhs(filename, file_format: str):
576579

577580
# 3: Analog input channel.
578581
# 4: Analog output channel.
579-
for sig_type in [
580-
3,
581-
4,
582-
]:
582+
for sig_type in [3, 4]:
583583
for chan_info in channels_by_type[sig_type]:
584-
name = chan_info["native_channel_name"]
585584
chan_info["sampling_rate"] = sr
586585
chan_info["units"] = "V"
587586
chan_info["gain"] = 0.0003125
588587
chan_info["offset"] = -32768 * 0.0003125
589588
chan_info["dtype"] = "uint16"
590589
ordered_channels.append(chan_info)
591590
if file_format == "header-attached":
591+
name = chan_info["native_channel_name"]
592592
data_dtype += [(name, "uint16", BLOCK_SIZE)]
593593
else:
594594
data_dtype[sig_type] = "uint16"
595595

596596
# 5: Digital input channel.
597597
# 6: Digital output channel.
598598
for sig_type in [5, 6]:
599-
if len(channels_by_type[sig_type]) > 0:
600-
name = {5: "DIGITAL-IN", 6: "DIGITAL-OUT"}[sig_type]
601-
chan_info = channels_by_type[sig_type][0]
602-
# So currently until we have get_digitalsignal_chunk we need to do a tiny hack to
603-
# make this memory map work correctly. So since our digital data is not organized
604-
# by channel like analog/ADC are we have to overwrite the native name to create
605-
# a single permanent name that we can find with channel id
606-
chan_info["native_channel_name"] = name # overwite to allow memmap to work
607-
chan_info["sampling_rate"] = sr
608-
chan_info["units"] = "TTL" # arbitrary units TTL for logic
609-
chan_info["gain"] = 1.0
610-
chan_info["offset"] = 0.0
611-
chan_info["dtype"] = "uint16"
612-
ordered_channels.append(chan_info)
613-
if file_format == "header-attached":
614-
data_dtype += [(name, "uint16", BLOCK_SIZE)]
615-
else:
599+
if file_format in ["header-attached", "one-file-per-signal"]:
600+
if len(channels_by_type[sig_type]) > 0:
601+
name = {5: "DIGITAL-IN", 6: "DIGITAL-OUT"}[sig_type]
602+
chan_info = channels_by_type[sig_type][0]
603+
# So currently until we have get_digitalsignal_chunk we need to do a tiny hack to
604+
# make this memory map work correctly. So since our digital data is not organized
605+
# by channel like analog/ADC are we have to overwrite the native name to create
606+
# a single permanent name that we can find with channel id
607+
chan_info["native_channel_name"] = name
608+
chan_info["sampling_rate"] = sr
609+
chan_info["units"] = "TTL" # arbitrary units TTL for logic
610+
chan_info["gain"] = 1.0
611+
chan_info["offset"] = 0.0
612+
chan_info["dtype"] = "uint16"
613+
ordered_channels.append(chan_info)
614+
if file_format == "header-attached":
615+
data_dtype += [(name, "uint16", BLOCK_SIZE)]
616+
else:
617+
data_dtype[sig_type] = "uint16"
618+
# This case behaves as a binary with 0 and 1 coded as uint16
619+
elif file_format == "one-file-per-channel":
620+
for chan_info in channels_by_type[sig_type]:
621+
chan_info["sampling_rate"] = sr
622+
chan_info["units"] = "TTL"
623+
chan_info["gain"] = 1.0
624+
chan_info["offset"] = 0.0
625+
chan_info["dtype"] = "uint16"
626+
ordered_channels.append(chan_info)
616627
data_dtype[sig_type] = "uint16"
617628

618629
if global_info["notch_filter_mode"] == 2 and global_info["major_version"] >= V("3.0"):
@@ -788,7 +799,6 @@ def read_rhd(filename, file_format: str):
788799

789800
# 0: RHD2000 amplifier channel
790801
for chan_info in channels_by_type[0]:
791-
name = chan_info["native_channel_name"]
792802
chan_info["sampling_rate"] = sr
793803
chan_info["units"] = "uV"
794804
chan_info["gain"] = 0.195
@@ -801,34 +811,35 @@ def read_rhd(filename, file_format: str):
801811
ordered_channels.append(chan_info)
802812

803813
if file_format == "header-attached":
814+
name = chan_info["native_channel_name"]
804815
data_dtype += [(name, "uint16", BLOCK_SIZE)]
805816
else:
806817
data_dtype[0] = "int16"
807818

808819
# 1: RHD2000 auxiliary input channel
809820
for chan_info in channels_by_type[1]:
810-
name = chan_info["native_channel_name"]
811821
chan_info["sampling_rate"] = sr / 4.0
812822
chan_info["units"] = "V"
813823
chan_info["gain"] = 0.0000374
814824
chan_info["offset"] = 0.0
815825
chan_info["dtype"] = "uint16"
816826
ordered_channels.append(chan_info)
817827
if file_format == "header-attached":
828+
name = chan_info["native_channel_name"]
818829
data_dtype += [(name, "uint16", BLOCK_SIZE // 4)]
819830
else:
820831
data_dtype[1] = "uint16"
821832

822833
# 2: RHD2000 supply voltage channel
823834
for chan_info in channels_by_type[2]:
824-
name = chan_info["native_channel_name"]
825835
chan_info["sampling_rate"] = sr / BLOCK_SIZE
826836
chan_info["units"] = "V"
827837
chan_info["gain"] = 0.0000748
828838
chan_info["offset"] = 0.0
829839
chan_info["dtype"] = "uint16"
830840
ordered_channels.append(chan_info)
831841
if file_format == "header-attached":
842+
name = chan_info["native_channel_name"]
832843
data_dtype += [(name, "uint16")]
833844
else:
834845
data_dtype[2] = "uint16"
@@ -847,7 +858,6 @@ def read_rhd(filename, file_format: str):
847858

848859
# 3: USB board ADC input channel
849860
for chan_info in channels_by_type[3]:
850-
name = chan_info["native_channel_name"]
851861
chan_info["sampling_rate"] = sr
852862
chan_info["units"] = "V"
853863
if global_info["eval_board_mode"] == 0:
@@ -862,32 +872,41 @@ def read_rhd(filename, file_format: str):
862872
chan_info["dtype"] = "uint16"
863873
ordered_channels.append(chan_info)
864874
if file_format == "header-attached":
875+
name = chan_info["native_channel_name"]
865876
data_dtype += [(name, "uint16", BLOCK_SIZE)]
866877
else:
867878
data_dtype[3] = "uint16"
868879

869880
# 4: USB board digital input channel
870881
# 5: USB board digital output channel
871882
for sig_type in [4, 5]:
872-
# Now these are included so that user can obtain the
873-
# dig signals and process them at the same time
874-
if len(channels_by_type[sig_type]) > 0:
875-
name = {4: "DIGITAL-IN", 5: "DIGITAL-OUT"}[sig_type]
876-
chan_info = channels_by_type[sig_type][0]
877-
# So currently until we have get_digitalsignal_chunk we need to do a tiny hack to
878-
# make this memory map work correctly. So since our digital data is not organized
879-
# by channel like analog/ADC are we have to overwrite the native name to create
880-
# a single permanent name that we can find with channel id
881-
chan_info["native_channel_name"] = name # overwite to allow memmap to work
882-
chan_info["sampling_rate"] = sr
883-
chan_info["units"] = "TTL" # arbitrary units TTL for logic
884-
chan_info["gain"] = 1.0
885-
chan_info["offset"] = 0.0
886-
chan_info["dtype"] = "uint16"
887-
ordered_channels.append(chan_info)
888-
if file_format == "header-attached":
889-
data_dtype += [(name, "uint16", BLOCK_SIZE)]
890-
else:
883+
if file_format in ["header-attached", "one-file-per-signal"]:
884+
if len(channels_by_type[sig_type]) > 0:
885+
name = {4: "DIGITAL-IN", 5: "DIGITAL-OUT"}[sig_type]
886+
chan_info = channels_by_type[sig_type][0]
887+
# So currently until we have get_digitalsignal_chunk we need to do a tiny hack to
888+
# make this memory map work correctly. So since our digital data is not organized
889+
# by channel like analog/ADC are we have to overwrite the native name to create
890+
# a single permanent name that we can find with channel id
891+
chan_info["native_channel_name"] = name
892+
chan_info["sampling_rate"] = sr
893+
chan_info["units"] = "TTL" # arbitrary units TTL for logic
894+
chan_info["gain"] = 1.0
895+
chan_info["offset"] = 0.0
896+
chan_info["dtype"] = "uint16"
897+
ordered_channels.append(chan_info)
898+
if file_format == "header-attached":
899+
data_dtype += [(name, "uint16", BLOCK_SIZE)]
900+
else:
901+
data_dtype[sig_type] = "uint16"
902+
elif file_format == "one-file-per-channel":
903+
for chan_info in channels_by_type[sig_type]:
904+
chan_info["sampling_rate"] = sr
905+
chan_info["units"] = "TTL"
906+
chan_info["gain"] = 1.0
907+
chan_info["offset"] = 0.0
908+
chan_info["dtype"] = "uint16"
909+
ordered_channels.append(chan_info)
891910
data_dtype[sig_type] = "uint16"
892911

893912
if global_info["notch_filter_mode"] == 2 and version >= V("3.0"):

neo/test/iotest/test_intanio.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ class TestIntanIO(
1515
ioclass = IntanIO
1616
entities_to_download = ["intan"]
1717
entities_to_test = [
18-
"intan/intan_rhs_test_1.rhs",
19-
"intan/intan_rhd_test_1.rhd",
20-
"intan/intan_fpc_test_231117_052630/info.rhd",
21-
"intan/intan_fps_test_231117_052500/info.rhd",
22-
"intan/intan_fpc_rhs_test_240329_091637/info.rhs",
23-
"intan/intan_fps_rhs_test_240329_091536/info.rhs",
18+
"intan/intan_rhs_test_1.rhs", # Format header-attached
19+
"intan/intan_rhd_test_1.rhd", # Format header-attached
20+
"intan/rhs_fpc_multistim_240514_082243/rhs_fpc_multistim_240514_082243.rhs", # Format header-attached newer version
21+
"intan/intan_fpc_test_231117_052630/info.rhd", # Format one-file-per-channel
22+
"intan/intan_fps_test_231117_052500/info.rhd", # Format one file per signal
23+
"intan/intan_fpc_rhs_test_240329_091637/info.rhs", # Format one-file-per-channel
24+
"intan/intan_fps_rhs_test_240329_091536/info.rhs", # Format one-file-per-signal
25+
"intan/rhd_fpc_multistim_240514_082044/info.rhd", # Multiple digital channels one-file-per-channel rhd
2426
]
2527

2628

neo/test/rawiotest/test_intanrawio.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ class TestIntanRawIO(
1212
rawioclass = IntanRawIO
1313
entities_to_download = ["intan"]
1414
entities_to_test = [
15-
"intan/intan_rhs_test_1.rhs",
16-
"intan/intan_rhd_test_1.rhd",
17-
"intan/intan_fpc_test_231117_052630/info.rhd",
18-
"intan/intan_fps_test_231117_052500/info.rhd",
19-
"intan/intan_fpc_rhs_test_240329_091637/info.rhs",
20-
"intan/intan_fps_rhs_test_240329_091536/info.rhs",
15+
"intan/intan_rhs_test_1.rhs", # Format header-attached
16+
"intan/intan_rhd_test_1.rhd", # Format header-attached
17+
"intan/rhs_fpc_multistim_240514_082243/rhs_fpc_multistim_240514_082243.rhs", # Format header-attached newer version
18+
"intan/intan_fpc_test_231117_052630/info.rhd", # Format one-file-per-channel
19+
"intan/intan_fps_test_231117_052500/info.rhd", # Format one file per signal
20+
"intan/intan_fpc_rhs_test_240329_091637/info.rhs", # Format one-file-per-channel
21+
"intan/intan_fps_rhs_test_240329_091536/info.rhs", # Format one-file-per-signal
22+
"intan/rhd_fpc_multistim_240514_082044/info.rhd", # Multiple digital channels one-file-per-channel rhd
2123
]
2224

2325

26+
2427
if __name__ == "__main__":
2528
unittest.main()

0 commit comments

Comments
 (0)