Skip to content

Commit 84ce669

Browse files
Updated model fields to the latest BEP32 state (#278)
* bring up to speed with latest BEP * Apply suggestion from @asmacdo Co-authored-by: Austin Macdonald <[email protected]> * roll back ch and zfill for string channel name --------- Co-authored-by: Austin Macdonald <[email protected]>
1 parent 76689e8 commit 84ce669

File tree

6 files changed

+76
-50
lines changed

6 files changed

+76
-50
lines changed

src/nwb2bids/bids_models/_channels.py

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,26 @@ def _infer_scalar_field(
4040

4141

4242
class Channel(BaseMetadataModel):
43-
channel_name: str
44-
reference: str
43+
name: str
44+
electrode_name: str
4545
type: str = "N/A"
46-
unit: str = "V"
46+
units: str = "V"
4747
sampling_frequency: float | None = None
48+
low_cutoff: float | None = None
49+
high_cutoff: float | None = None
50+
reference: str | None = None
51+
notch: str | None = None
4852
channel_label: str | None = None
4953
stream_id: str | None = None
5054
description: str | None = None
55+
software_filter_types: str | None = None
5156
status: typing.Literal["good", "bad"] | None = None
5257
status_description: str | None = None
5358
gain: float | None = None
5459
time_offset: float | None = None
55-
time_reference_channels: str | None = None
60+
time_reference_channel: str | None = None
5661
ground: str | None = None
57-
# recording_mode: str | None = None # TODO: icephys only
62+
recording_mode: str | None = None
5863

5964

6065
class ChannelTable(BaseMetadataContainerModel):
@@ -129,18 +134,14 @@ def from_nwbfiles(cls, nwbfiles: list[pydantic.InstanceOf[pynwb.NWBFile]]) -> ty
129134

130135
channels = [
131136
Channel(
132-
channel_name=(
133-
f"ch{channel_name.values[0]}"
137+
name=(
138+
f"{channel_name.values[0]}"
134139
if (channel_name := electrode.get("channel_name", None)) is not None
135-
else f"ch{electrode.index[0]}"
136-
),
137-
reference=(
138-
f"contact{contact_ids.values[0]}" # TODO: do a deep dive into edge cases of this reference
139-
if (contact_ids := electrode.get("contact_ids", None)) is not None
140-
else f"e{electrode.index[0]}"
140+
else f"ch{str(electrode.index[0]).zfill(3)}"
141141
),
142+
electrode_name=f"e{str(electrode.index[0]).zfill(3)}",
142143
type="N/A", # TODO: in dedicated follow-up, could classify LFP based on container
143-
unit="V",
144+
units="V",
144145
sampling_frequency=sampling_frequency,
145146
# channel_label: str | None = None # TODO: only support with additional metadata
146147
stream_id=stream_id,
@@ -150,7 +151,7 @@ def from_nwbfiles(cls, nwbfiles: list[pydantic.InstanceOf[pynwb.NWBFile]]) -> ty
150151
gain=gain,
151152
# Special extraction from SpikeInterface field
152153
time_offset=shift[0] if (shift := electrode.get("inter_sample_shift", None)) is not None else None,
153-
# time_reference_channels: str | None = None # TODO: only support with additional metadata
154+
# time_reference_channel: str | None = None # TODO: only support with additional metadata
154155
# ground: str | None = None # TODO: only support with additional metadata
155156
)
156157
for electrode in nwbfile.electrodes
@@ -161,7 +162,6 @@ def from_nwbfiles(cls, nwbfiles: list[pydantic.InstanceOf[pynwb.NWBFile]]) -> ty
161162
for neurodata_object in nwbfile.acquisition.values()
162163
if isinstance(neurodata_object, pynwb.icephys.PatchClampSeries)
163164
]
164-
# TODO: handle intracellular_recordings case
165165
electrode_name_to_series = collections.defaultdict(list)
166166
for series in icephys_series:
167167
electrode_name_to_series[series.electrode.name].append(series)
@@ -197,19 +197,18 @@ def from_nwbfiles(cls, nwbfiles: list[pydantic.InstanceOf[pynwb.NWBFile]]) -> ty
197197

198198
channels = [
199199
Channel(
200-
channel_name=electrode.name,
201-
reference="n/a", # TODO: think about if/how this could be any other value
200+
name=electrode.name,
201+
electrode_name=electrode.name,
202202
type=electrode_name_to_type.get(electrode.name, "n/a"),
203-
unit="V",
203+
units="V",
204204
sampling_frequency=electrode_name_to_sampling_frequency.get(electrode.name, None),
205205
# channel_label: str | None = None # TODO: only support with additional metadata
206206
stream_id=electrode_name_to_stream_ids.get(electrode.name, None),
207207
# description: str | None = None # TODO: only support with additional metadata
208208
# status: typing.Literal["good", "bad"] | None = None # TODO: only support with additional metadata
209209
# status_description: str | None = None # TODO: only support with additional metadata
210210
gain=electrode_name_to_gain.get(electrode.name, None),
211-
# time_offset=
212-
# time_reference_channels: str | None = None # TODO: only support with additional metadata
211+
# time_reference_channel: str | None = None # TODO: only support with additional metadata
213212
# ground: str | None = None # TODO: only support with additional metadata
214213
recording_mode=type_to_recording_mode[electrode_name_to_type.get(electrode.name, "n/a")],
215214
# TODO: add extra columns

src/nwb2bids/bids_models/_electrodes.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@
1515
class Electrode(BaseMetadataModel):
1616
name: str
1717
probe_name: str
18-
hemisphere: str = "N/A"
1918
x: float = numpy.nan
2019
y: float = numpy.nan
2120
z: float = numpy.nan
21+
hemisphere: str = "N/A"
2222
impedance: float = numpy.nan # in kOhms
2323
shank_id: str = "N/A"
24+
size: float | None = None # in square micrometers
25+
electrode_shape: str | None = None
26+
material: str | None = None
2427
location: str | None = None
28+
pipette_solution: str | None = None
29+
internal_pipette_diameter: float | None = None # in micrometers
30+
external_pipette_diameter: float | None = None # in micrometers
2531

2632
def __eq__(self, other: typing_extensions.Self) -> bool:
2733
if not isinstance(other, Electrode):
@@ -122,15 +128,13 @@ def from_nwbfiles(cls, nwbfiles: list[pydantic.InstanceOf[pynwb.NWBFile]]) -> ty
122128
x=getattr(electrode, "x", numpy.nan),
123129
y=getattr(electrode, "y", numpy.nan),
124130
z=getattr(electrode, "z", numpy.nan),
125-
# impedance= # Impedance must be in kOhms for BEP32 but NWB specifies Ohms
126-
# shank_id=
127131
# TODO: pretty much only through additional metadata
132+
# impedance=
128133
# size=
129134
# electrode_shape=
130135
# material=
131-
# location=
136+
location=getattr(electrode, "location", None),
132137
# TODO: some icephys specific ones (would NOT use the ecephys electrode table anyway...)
133-
# Probably better off in a designated model
134138
# pipette_solution=
135139
# internal_pipette_diameter=
136140
# external_pipette_diameter=

src/nwb2bids/bids_models/_probes.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,27 @@
1515
class Probe(BaseMetadataModel):
1616
probe_name: str
1717
type: str = "n/a"
18+
AP: float | None = None
19+
ML: float | None = None
20+
DV: float | None = None
21+
AP_angle: float | None = None
22+
ML_angle: float | None = None
1823
manufacturer: str | None = None
24+
model: str | None = None
25+
device_serial_number: str | None = None
26+
electrode_count: int | None = None
27+
width: float | None = None # in millimeters
28+
height: float | None = None # in millimeters
29+
depth: float | None = None
30+
rotation_angle: float | None = None
31+
coordinate_reference_point: str | None = None
32+
anatomical_reference_point: str | None = None
33+
hemisphere: typing.Literal["L", "R"] | None = None
34+
associated_brain_region: str | None = None
35+
associated_brain_region_id: str | None = None
36+
associated_brain_region_quality_type: str | None = None
37+
reference_atlas: str | None = None
38+
material: str | None = None
1939

2040

2141
class ProbeTable(BaseMetadataContainerModel):
@@ -89,7 +109,7 @@ def from_nwbfiles(cls, nwbfiles: list[pydantic.InstanceOf[pynwb.NWBFile]]) -> ty
89109
probes = [
90110
Probe(
91111
probe_name=device.name,
92-
type="n/a", # TODO
112+
type="n/a", # TODO via additional metadata
93113
manufacturer=device.manufacturer,
94114
description=device.description,
95115
# TODO: handle more extra custom columns

src/nwb2bids/testing/_mocks/_tutorials.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,19 @@ def _generate_icephys_file(*, nwbfile_path: pathlib.Path, subject_id: str = "001
109109
name="patch01",
110110
description="This is an example icephys electrode used for demonstration purposes.",
111111
device=probe1,
112+
location="VISp2/3",
112113
)
113114
electrode2 = nwbfile.create_icephys_electrode(
114115
name="patch02",
115116
description="This is an example icephys electrode used for demonstration purposes.",
116117
device=probe2,
118+
location="VISp2/3",
117119
)
118120
electrode3 = nwbfile.create_icephys_electrode(
119121
name="sharp01",
120122
description="This is an example icephys electrode used for demonstration purposes.",
121123
device=probe3,
124+
location="PL5",
122125
)
123126

124127
# Icephys series

tests/integration/test_convert_nwb_dataset.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def test_ecephys_tutorial_convert_nwb_dataset(
152152
)
153153
electrodes_tsv_lines = electrodes_tsv_file_path.read_text().splitlines()
154154
expected_electrodes_tsv_lines = [
155-
"name\tprobe_name\themisphere\tx\ty\tz\timpedance\tshank_id\tlocation",
155+
"name\tprobe_name\tx\ty\tz\themisphere\timpedance\tshank_id\tlocation",
156156
"e000\tExampleProbe\tN/A\tN/A\tN/A\tN/A\t150.0\tExampleShank\thippocampus",
157157
"e001\tExampleProbe\tN/A\tN/A\tN/A\tN/A\t150.0\tExampleShank\thippocampus",
158158
"e002\tExampleProbe\tN/A\tN/A\tN/A\tN/A\t150.0\tExampleShank\thippocampus",
@@ -167,15 +167,15 @@ def test_ecephys_tutorial_convert_nwb_dataset(
167167
channels_tsv_file_path = temporary_bids_directory / "sub-001" / "ses-A" / "ecephys" / "sub-001_ses-A_channels.tsv"
168168
channels_tsv_lines = channels_tsv_file_path.read_text().splitlines()
169169
expected_channels_tsv_lines = [
170-
"channel_name\treference\ttype\tunit\tsampling_frequency\tstream_id\tgain",
171-
"ch0\te0\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
172-
"ch1\te1\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
173-
"ch2\te2\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
174-
"ch3\te3\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
175-
"ch4\te4\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
176-
"ch5\te5\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
177-
"ch6\te6\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
178-
"ch7\te7\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
170+
"name\telectrode_name\ttype\tunits\tsampling_frequency\tstream_id\tgain",
171+
"ch000\te000\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
172+
"ch001\te001\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
173+
"ch002\te002\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
174+
"ch003\te003\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
175+
"ch004\te004\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
176+
"ch005\te005\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
177+
"ch006\te006\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
178+
"ch007\te007\tN/A\tV\t30000.0\tExampleElectricalSeries\t3.02734375e-06",
179179
]
180180
assert channels_tsv_lines == expected_channels_tsv_lines
181181

@@ -237,7 +237,7 @@ def test_ecephys_minimal_convert_nwb_dataset(
237237
)
238238
electrodes_tsv_lines = electrodes_tsv_file_path.read_text().splitlines()
239239
expected_electrodes_tsv_lines = [
240-
"name\tprobe_name\themisphere\tx\ty\tz\timpedance\tshank_id\tlocation",
240+
"name\tprobe_name\tx\ty\tz\themisphere\timpedance\tshank_id\tlocation",
241241
"e000\tExampleProbe\tN/A\tN/A\tN/A\tN/A\tN/A\tExampleShank\tunknown",
242242
"e001\tExampleProbe\tN/A\tN/A\tN/A\tN/A\tN/A\tExampleShank\tunknown",
243243
"e002\tExampleProbe\tN/A\tN/A\tN/A\tN/A\tN/A\tExampleShank\tunknown",
@@ -252,15 +252,15 @@ def test_ecephys_minimal_convert_nwb_dataset(
252252
channels_tsv_file_path = temporary_bids_directory / "sub-001" / "ses-A" / "ecephys" / "sub-001_ses-A_channels.tsv"
253253
channels_tsv_lines = channels_tsv_file_path.read_text().splitlines()
254254
expected_channels_tsv_lines = [
255-
"channel_name\treference\ttype\tunit",
256-
"ch0\te0\tN/A\tV",
257-
"ch1\te1\tN/A\tV",
258-
"ch2\te2\tN/A\tV",
259-
"ch3\te3\tN/A\tV",
260-
"ch4\te4\tN/A\tV",
261-
"ch5\te5\tN/A\tV",
262-
"ch6\te6\tN/A\tV",
263-
"ch7\te7\tN/A\tV",
255+
"name\telectrode_name\ttype\tunits",
256+
"ch000\te000\tN/A\tV",
257+
"ch001\te001\tN/A\tV",
258+
"ch002\te002\tN/A\tV",
259+
"ch003\te003\tN/A\tV",
260+
"ch004\te004\tN/A\tV",
261+
"ch005\te005\tN/A\tV",
262+
"ch006\te006\tN/A\tV",
263+
"ch007\te007\tN/A\tV",
264264
]
265265
assert channels_tsv_lines == expected_channels_tsv_lines
266266

tests/unit/test_remote_dataset_converter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ def test_remote_dataset_converter_metadata_extraction(temporary_bids_directory:
7777
assert session_metadata.channel_table is not None
7878
assert len(session_metadata.channel_table.channels) == 65
7979
assert session_metadata.channel_table.channels[0] == nwb2bids.bids_models.Channel(
80-
channel_name="ch0",
81-
reference="e0",
80+
name="ch000",
81+
electrode_name="e000",
8282
type="N/A",
83-
unit="V",
83+
units="V",
8484
sampling_frequency=1250.0,
8585
stream_id="LFP",
8686
gain=1.9499999999999999e-07,

0 commit comments

Comments
 (0)