Skip to content

Commit 0bf7a5e

Browse files
authored
Merge pull request #1009 from JuliaSprenger/enh/nix_array_anno
Signal array annotations in NixIOfr
2 parents 5f50d31 + a0de9be commit 0bf7a5e

File tree

2 files changed

+57
-33
lines changed

2 files changed

+57
-33
lines changed

neo/rawio/nixrawio.py

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44
The RawIO assumes all segments and all blocks have the same structure.
55
It supports all kinds of NEO objects.
66
7-
Author: Chek Yin Choi
7+
Author: Chek Yin Choi, Julia Sprenger
88
"""
99

10-
from .baserawio import (BaseRawIO, _signal_channel_dtype, _signal_stream_dtype,
11-
_spike_channel_dtype, _event_channel_dtype)
10+
import os.path
1211

13-
from ..io.nixio import NixIO
14-
from ..io.nixio import check_nix_version
1512
import numpy as np
13+
14+
from .baserawio import (BaseRawIO, _signal_channel_dtype, _signal_stream_dtype,
15+
_spike_channel_dtype, _event_channel_dtype)
16+
from ..io.nixio import check_nix_version
17+
1618
try:
1719
import nixio as nix
1820

@@ -50,7 +52,7 @@ def _source_name(self):
5052
def _parse_header(self):
5153
self.file = nix.File.open(self.filename, nix.FileMode.ReadOnly)
5254
signal_channels = []
53-
size_list = []
55+
anasig_ids = {0: []} # ids of analogsignals by segment
5456
stream_ids = []
5557
for bl in self.file.blocks:
5658
for seg in bl.groups:
@@ -61,19 +63,18 @@ def _parse_header(self):
6163
units = str(da.unit)
6264
dtype = str(da.dtype)
6365
sr = 1 / da.dimensions[0].sampling_interval
64-
da_leng = da.size
65-
if da_leng not in size_list:
66-
size_list.append(da_leng)
67-
stream_ids.append(str(len(size_list)))
68-
# very important! group_id use to store
69-
# channel groups!!!
70-
# use only for different signal length
71-
stream_index = size_list.index(da_leng)
72-
stream_id = stream_ids[stream_index]
66+
anasig_id = da.name.split('.')[-2]
67+
if anasig_id not in anasig_ids[0]:
68+
anasig_ids[0].append(anasig_id)
69+
stream_id = anasig_ids[0].index(anasig_id)
70+
if stream_id not in stream_ids:
71+
stream_ids.append(stream_id)
7372
gain = 1
7473
offset = 0.
7574
signal_channels.append((ch_name, chan_id, sr, dtype,
7675
units, gain, offset, stream_id))
76+
# only read structure of first segment and assume the same
77+
# across segments
7778
break
7879
break
7980
signal_channels = np.array(signal_channels, dtype=_signal_channel_dtype)
@@ -210,21 +211,6 @@ def _parse_header(self):
210211
props = group.metadata.inherited_properties()
211212
seg_ann.update(self._filter_properties(props, "segment"))
212213

213-
# TODO handle annotation at stream level
214-
'''
215-
sig_idx = 0
216-
groupdas = NixIO._group_signals(grp.data_arrays)
217-
for nix_name, signals in groupdas.items():
218-
  da = signals[0]
219-
  if da.type == 'neo.analogsignal' and seg_ann['signals']:
220-
  # collect and group DataArrays
221-
  sig_ann = seg_ann['signals'][sig_idx]
222-
  sig_chan_ann = self.raw_annotations['signal_channels'][sig_idx]
223-
  props = da.metadata.inherited_properties()
224-
  sig_ann.update(self._filter_properties(props, 'analogsignal'))
225-
  sig_chan_ann.update(self._filter_properties(props, 'analogsignal'))
226-
  sig_idx += 1
227-
'''
228214
sp_idx = 0
229215
ev_idx = 0
230216
for mt in group.multi_tags:
@@ -242,6 +228,38 @@ def _parse_header(self):
242228
event_ann.update(self._filter_properties(props, 'event'))
243229
ev_idx += 1
244230

231+
# adding array annotations to analogsignals
232+
annotated_anasigs = []
233+
sig_ann = seg_ann['signals']
234+
# this implementation relies on analogsignals always being
235+
# stored in the same stream order across segments
236+
stream_id = 0
237+
for da_idx, da in enumerate(group.data_arrays):
238+
if da.type != "neo.analogsignal":
239+
continue
240+
anasig_id = da.name.split('.')[-2]
241+
# skip already annotated signals as each channel already
242+
# contains the complete set of annotations and
243+
# array_annotations
244+
if anasig_id in annotated_anasigs:
245+
continue
246+
annotated_anasigs.append(anasig_id)
247+
248+
# collect annotation properties
249+
props = [p for p in da.metadata.props
250+
if p.type != 'ARRAYANNOTATION']
251+
props_dict = self._filter_properties(props, "analogsignal")
252+
sig_ann[stream_id].update(props_dict)
253+
254+
# collect array annotation properties
255+
props = [p for p in da.metadata.props
256+
if p.type == 'ARRAYANNOTATION']
257+
props_dict = self._filter_properties(props, "analogsignal")
258+
sig_ann[stream_id]['__array_annotations__'].update(
259+
props_dict)
260+
261+
stream_id += 1
262+
245263
def _segment_t_start(self, block_index, seg_index):
246264
t_start = 0
247265
for mt in self.file.blocks[block_index].groups[seg_index].multi_tags:

neo/test/iotest/test_nixio_fr.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,9 @@ def test_annotations(self):
106106
annotations = {'something': 'hello hello000'}
107107
seg = Segment(**annotations)
108108
an =AnalogSignal([[1, 2, 3], [4, 5, 6]], units='V',
109-
sampling_rate=1*pq.Hz)
110-
an.annotations['ansigrandom'] = 'hello chars'
109+
sampling_rate=1 * pq.Hz)
110+
an.annotate(ansigrandom='hello chars')
111+
an.array_annotate(custom_id=[1, 2, 3])
111112
sp = SpikeTrain([3, 4, 5]* s, t_stop=10.0)
112113
sp.annotations['railway'] = 'hello train'
113114
ev = Event(np.arange(0, 30, 10)*pq.Hz,
@@ -123,14 +124,19 @@ def test_annotations(self):
123124
bl.segments.append(seg)
124125
io.write_block(bl)
125126
io.close()
127+
126128
with NixIOfr(filename=self.testfilename) as frio:
127129
frbl = frio.read_block()
128130
assert 'my_custom_annotation' in frbl.annotations
129131
assert 'something' in frbl.segments[0].annotations
130-
# assert 'ansigrandom' in frbl.segments[0].analogsignals[0].annotations
132+
assert 'ansigrandom' in frbl.segments[0].analogsignals[0].annotations
131133
assert 'railway' in frbl.segments[0].spiketrains[0].annotations
132134
assert 'venue' in frbl.segments[0].events[0].annotations
133135
assert 'evven' in frbl.segments[0].events[1].annotations
136+
assert 'custom_id' in frbl.segments[0].analogsignals[0].array_annotations
137+
for anasig in frbl.segments[0].analogsignals:
138+
for value in anasig.array_annotations.values():
139+
assert anasig.shape[-1] == len(value)
134140
os.remove(self.testfilename)
135141

136142

0 commit comments

Comments
 (0)