Organizing Electrophysiology Data
Authors
Neo (Part 2): Organizing Electrophysiology Data
In the first session you worked with the Neo data objects AnalogSignal, SpikeTrain, Event, Epoch. In this session, you will learn how to organise these data objects into containers called Block and Segment for a complete, trial-based dataset, much like one you would obtain by loading a recording from disk.
You will also get practice exploring the contents of a dataset, select subsets of the data using metadata, cut the recording into per-trial segments with Neo’s utility functions, and finally transform and visualise a signal.
Setup
Import Libraries
import neo
import numpy as np
import quantities as pq
import matplotlib.pyplot as plt
from neo.utils import add_epoch, cut_segment_by_epoch, get_events
from neo.core.filters import IsNot, IsIn, GreaterThan, LessThan, GreaterThanOrEquals, LessThanOrEquals, InRangeGenerate Data
Please run the following cell, which will generate a data file that we will use later.
from pathlib import Path
rng = np.random.default_rng(0)
# A 96-channel LFP-like signal sampled at 1 kHz for 10 s.
lfp = neo.AnalogSignal(rng.normal(size=(10000, 96)), units='uV',
sampling_rate=1000*pq.Hz, t_start=0*pq.s,
name='LFP')
lfp.array_annotate(channel_names=np.array([f'chan{i+1}' for i in range(96)]))
# A smaller 6-channel signal sampled at 30 kHz for 10 s.
hires = neo.AnalogSignal(rng.normal(size=(300000, 6)), units='uV',
sampling_rate=30000*pq.Hz, t_start=0*pq.s,
name='Subsample unprocessed signal')
hires.array_annotate(channel_names=np.array([f'ainp{i+1}' for i in range(6)]))
# A handful of spike trains, each labelled as single-unit, multi-unit or noise.
unit_types = ['sua', 'mua', 'noise', 'sua', 'mua', 'sua', 'noise', 'sua']
spiketrains = []
for i, unit_type in enumerate(unit_types):
times = np.sort(rng.uniform(0, 10, size=rng.integers(50, 150)))
st = neo.SpikeTrain(times*pq.s, t_start=0*pq.s, t_stop=10*pq.s, name=f'unit {i}')
st.annotate(unit_type=unit_type)
spiketrains.append(st)
# Trial-start events, alternating correct and error, every 3 s.
ts_times = np.arange(1, 10, 3) * pq.s
performance = np.array(['correct_trial' if i % 2 == 0 else 'error_trial'
for i in range(len(ts_times))])
events = neo.Event(ts_times, labels=np.array(['TS-ON'] * len(ts_times)),
name='TrialEvents')
events.array_annotate(performance_in_trial_str=performance)
# Assemble everything into a Block with one Segment.
segment = neo.Segment(name='recording')
segment.analogsignals.extend([lfp, hires])
segment.spiketrains.extend(spiketrains)
segment.events.append(events)
block = neo.Block(name='dataset')
block.segments.append(segment)
# Write the block to a NIX file that the notebook reads back in.
out_path = Path('dataset.nix')
out_path.unlink(missing_ok=True)
with neo.NixIO(str(out_path), mode='ow') as io:
io.write_block(block)
print(f'Wrote dataset to {out_path}')Wrote dataset to dataset.nixSection 1: Organizing Data: Block and Segment
Block and Segment
Background
A real recording often contains several of the data objects AnalogSignal, SpikeTrain, Event, Epoch, and Neo organises them in a simple two-level hierarchy:
- A
Segmentholds all data objects that share a common clock — for example everything recorded during a single trial or a single continuous recording. Its data is stored in lists:segment.analogsignals,segment.spiketrains,segment.events, andsegment.epochs. - A
Blockis the top-level container that holds everything; itsblock.segmentslist holds one or more segments.
Once data is organised this way, the filter method lets you search a block (or segment) for all objects of a given type or with a given annotation, without walking the hierarchy by hand.
Exercises
In this section you will build a Block containing a Segment, add data objects to it, navigate the hierarchy, and search it with filter.
| Code | Description |
|---|---|
neo.Block(name='my block') |
Create a block (the top-level container). |
neo.Segment(name='trial 1') |
Create a segment (a group of data with a shared clock). |
block.segments.append(seg) |
Add a segment to a block. |
neo.AnalogSignal(np.random.random((100, 4)), units='uV', sampling_rate=1000*pq.Hz) |
Create an AnalogSignal from a NumPy array, units, and a sampling rate. |
seg.analogsignals.append(sig) |
Add an AnalogSignal to a segment. |
neo.SpikeTrain([0.1, 0.4, 0.8], units='s', t_start=0*pq.s, t_stop=1*pq.s) |
Create a SpikeTrain from a list of spike times. |
seg.spiketrains.append(st) |
Add a SpikeTrain to a segment. |
obj.annotate(key=value) |
Attach a named annotation to any Neo object. |
print(seg.spiketrains) |
Display the list of spike trains in a segment. |
block.filter(objects=neo.AnalogSignal) |
Find all analog signals anywhere in the block. |
block.filter(objects=neo.SpikeTrain) |
Find all spike trains anywhere in the block. |
Example: Create a block named example block containing one segment named example segment. Dissplay the block.
block = neo.Block(name='my block')
seg = neo.Segment(name='trial 1')
block.segments.append(seg)
blockBlock with [<neo.core.segment.Segment object at 0x796e00a13110>] segments
name: 'my block'
# segments (N=[<neo.core.segment.Segment object at 0x796e00a13110>])
0: Segment with name: 'trial 1' # analogsignals (N=[])Add an AnalogSignal with 100 samples in 4 different channels with units uV and sampling rate 1000 Hz to that segment. Display the block container to check that the segment now has the AnalogSignal object.
sig = neo.AnalogSignal(np.random.random((100, 4)), units='uV', sampling_rate=1000*pq.Hz)
seg.analogsignals.append(sig)
blockBlock with [<neo.core.segment.Segment object at 0x796e00a13110>] segments
name: 'my block'
# segments (N=[<neo.core.segment.Segment object at 0x796e00a13110>])
0: Segment with [<AnalogSignal(array([[0.217367 , 0.34893813, 0.51625995, 0.43344605],
[0.32492489, 0.20020743, 0.67667439, 0.13800391],
[0.80193073, 0.02450037, 0.70741781, 0.57114035],
[0.41226845, 0.58410967, 0.54369847, 0.7958192 ],
[0.06109799, 0.35445678, 0.59942562, 0.97473813],
[0.90620298, 0.39658403, 0.11916485, 0.70459 ],
[0.7298442 , 0.9778186 , 0.07000659, 0.4469036 ],
[0.73351133, 0.05041363, 0.98016085, 0.73267895],
[0.09072818, 0.83615268, 0.12700443, 0.02395714],
[0.92318499, 0.59910776, 0.98774226, 0.92270215],
[0.08475328, 0.87063823, 0.48761728, 0.16924039],
[0.42628876, 0.28885798, 0.95018169, 0.7509574 ],
[0.36210871, 0.19714106, 0.7513759 , 0.88407308],
[0.66335017, 0.40420713, 0.25382884, 0.45509082],
[0.85358283, 0.99014981, 0.39741388, 0.43924654],
[0.14403562, 0.45029857, 0.82914636, 0.14640953],
[0.46937724, 0.83181495, 0.44829343, 0.68311018],
[0.40786586, 0.96298507, 0.31379853, 0.96615944],
[0.87745346, 0.24750147, 0.06365357, 0.46835792],
[0.27962987, 0.47593379, 0.54894141, 0.8768769 ],
[0.6649027 , 0.53142762, 0.36889423, 0.86699681],
[0.2467311 , 0.35803216, 0.35448572, 0.65602387],
[0.45764168, 0.21769411, 0.08113454, 0.34585709],
[0.04335507, 0.05205278, 0.65864226, 0.26909962],
[0.58038707, 0.5626738 , 0.67096555, 0.48943613],
[0.56494128, 0.62726695, 0.45903324, 0.72136211],
[0.74592533, 0.70293759, 0.39549308, 0.84466774],
[0.19546367, 0.81666684, 0.35138077, 0.33286485],
[0.5913931 , 0.73226743, 0.36643243, 0.19368379],
[0.17731153, 0.63627866, 0.25587445, 0.2229965 ],
[0.49819229, 0.97067024, 0.45035044, 0.39437785],
[0.12494719, 0.49872863, 0.62396874, 0.45512381],
[0.69387919, 0.81537467, 0.77257699, 0.11698581],
[0.09678496, 0.29454482, 0.32680222, 0.90666589],
[0.61486963, 0.35523205, 0.4394021 , 0.96310654],
[0.24837182, 0.17194805, 0.99034434, 0.9792719 ],
[0.74167606, 0.23380758, 0.5909582 , 0.05341586],
[0.82146963, 0.90324436, 0.91617827, 0.45180968],
[0.32349456, 0.08952234, 0.63127782, 0.91277156],
[0.71508164, 0.07621077, 0.1513623 , 0.60236517],
[0.20400476, 0.62579926, 0.96945959, 0.25369496],
[0.16045375, 0.8629679 , 0.52353014, 0.17874773],
[0.74106373, 0.18528856, 0.14129199, 0.27095403],
[0.4879966 , 0.15226884, 0.93318282, 0.30072195],
[0.19525753, 0.57281687, 0.73058896, 0.14141631],
[0.22105202, 0.83839086, 0.95349158, 0.34607576],
[0.73788021, 0.1770859 , 0.41733917, 0.38844367],
[0.77644933, 0.31537838, 0.78413422, 0.24615305],
[0.00668141, 0.39936199, 0.04353854, 0.25671659],
[0.03214832, 0.45194267, 0.66184994, 0.89484749],
[0.72763547, 0.74097694, 0.98295378, 0.60910494],
[0.54053955, 0.11799039, 0.27145158, 0.3194415 ],
[0.30207851, 0.89018352, 0.02261753, 0.89319996],
[0.3537182 , 0.74787213, 0.31305765, 0.71514002],
[0.97574293, 0.75465425, 0.35010109, 0.57163881],
[0.4731333 , 0.30506162, 0.52243232, 0.36218346],
[0.28175466, 0.50968116, 0.38608258, 0.40358579],
[0.51695478, 0.85474675, 0.85774048, 0.84621208],
[0.15897893, 0.45373403, 0.94443085, 0.9325776 ],
[0.25073291, 0.5943132 , 0.97380375, 0.92231432],
[0.35821384, 0.71748719, 0.30976215, 0.16130552],
[0.37425569, 0.63485209, 0.18244676, 0.12778917],
[0.06108208, 0.59688104, 0.50377873, 0.52455861],
[0.83639846, 0.04141602, 0.22538742, 0.51417511],
[0.78577046, 0.52873215, 0.68593581, 0.48329374],
[0.26058759, 0.69023617, 0.00289102, 0.14051131],
[0.01463525, 0.29499572, 0.22274016, 0.24510624],
[0.73379759, 0.43681881, 0.04314026, 0.93150594],
[0.22342956, 0.6266855 , 0.31738537, 0.03371984],
[0.97803818, 0.44051264, 0.60824868, 0.73362355],
[0.78723924, 0.66276966, 0.23916369, 0.62163246],
[0.71040393, 0.06991749, 0.98101301, 0.51514206],
[0.0821866 , 0.70404957, 0.72840406, 0.48547848],
[0.45443395, 0.15621613, 0.17911828, 0.03956511],
[0.23424694, 0.41055641, 0.7066062 , 0.98101608],
[0.5747051 , 0.77899437, 0.25603957, 0.9831105 ],
[0.28750575, 0.71722367, 0.39670689, 0.57363489],
[0.7982244 , 0.62949219, 0.57060217, 0.44593883],
[0.19825536, 0.98512959, 0.69419809, 0.7668913 ],
[0.09056463, 0.45531655, 0.07847721, 0.14693561],
[0.52506629, 0.83372737, 0.22320466, 0.89293756],
[0.53522865, 0.45356899, 0.96892514, 0.53857607],
[0.20160523, 0.67869879, 0.248409 , 0.73112456],
[0.32203211, 0.27556238, 0.36312517, 0.20047435],
[0.06899307, 0.9229278 , 0.81702242, 0.41710287],
[0.14494965, 0.99735291, 0.94911428, 0.45586484],
[0.20008054, 0.78333888, 0.13750651, 0.65110425],
[0.74495973, 0.4729519 , 0.46274407, 0.33829962],
[0.76848101, 0.71928529, 0.06376184, 0.16765253],
[0.94581353, 0.26161307, 0.02458549, 0.19938645],
[0.35482888, 0.18543557, 0.72909228, 0.82440544],
[0.25710995, 0.10172886, 0.54675609, 0.02942142],
[0.49069787, 0.99730936, 0.25178275, 0.22492832],
[0.59470998, 0.0990552 , 0.22297428, 0.59916495],
[0.08932617, 0.99201394, 0.81158043, 0.80204734],
[0.64921487, 0.63776038, 0.37171653, 0.78075762],
[0.81160287, 0.78522705, 0.56671516, 0.79639266],
[0.48031125, 0.5199653 , 0.02510022, 0.67543871],
[0.62950204, 0.63690502, 0.12682213, 0.38034912],
[0.50851116, 0.22648952, 0.8195524 , 0.73535656]]) * uV, [0.0 s, 0.1 s], sampling rate: 1000.0 Hz)>] analogsignals
name: 'trial 1'
# analogsignals (N=[<AnalogSignal(array([[0.217367 , 0.34893813, 0.51625995, 0.43344605],
[0.32492489, 0.20020743, 0.67667439, 0.13800391],
[0.80193073, 0.02450037, 0.70741781, 0.57114035],
[0.41226845, 0.58410967, 0.54369847, 0.7958192 ],
[0.06109799, 0.35445678, 0.59942562, 0.97473813],
[0.90620298, 0.39658403, 0.11916485, 0.70459 ],
[0.7298442 , 0.9778186 , 0.07000659, 0.4469036 ],
[0.73351133, 0.05041363, 0.98016085, 0.73267895],
[0.09072818, 0.83615268, 0.12700443, 0.02395714],
[0.92318499, 0.59910776, 0.98774226, 0.92270215],
[0.08475328, 0.87063823, 0.48761728, 0.16924039],
[0.42628876, 0.28885798, 0.95018169, 0.7509574 ],
[0.36210871, 0.19714106, 0.7513759 , 0.88407308],
[0.66335017, 0.40420713, 0.25382884, 0.45509082],
[0.85358283, 0.99014981, 0.39741388, 0.43924654],
[0.14403562, 0.45029857, 0.82914636, 0.14640953],
[0.46937724, 0.83181495, 0.44829343, 0.68311018],
[0.40786586, 0.96298507, 0.31379853, 0.96615944],
[0.87745346, 0.24750147, 0.06365357, 0.46835792],
[0.27962987, 0.47593379, 0.54894141, 0.8768769 ],
[0.6649027 , 0.53142762, 0.36889423, 0.86699681],
[0.2467311 , 0.35803216, 0.35448572, 0.65602387],
[0.45764168, 0.21769411, 0.08113454, 0.34585709],
[0.04335507, 0.05205278, 0.65864226, 0.26909962],
[0.58038707, 0.5626738 , 0.67096555, 0.48943613],
[0.56494128, 0.62726695, 0.45903324, 0.72136211],
[0.74592533, 0.70293759, 0.39549308, 0.84466774],
[0.19546367, 0.81666684, 0.35138077, 0.33286485],
[0.5913931 , 0.73226743, 0.36643243, 0.19368379],
[0.17731153, 0.63627866, 0.25587445, 0.2229965 ],
[0.49819229, 0.97067024, 0.45035044, 0.39437785],
[0.12494719, 0.49872863, 0.62396874, 0.45512381],
[0.69387919, 0.81537467, 0.77257699, 0.11698581],
[0.09678496, 0.29454482, 0.32680222, 0.90666589],
[0.61486963, 0.35523205, 0.4394021 , 0.96310654],
[0.24837182, 0.17194805, 0.99034434, 0.9792719 ],
[0.74167606, 0.23380758, 0.5909582 , 0.05341586],
[0.82146963, 0.90324436, 0.91617827, 0.45180968],
[0.32349456, 0.08952234, 0.63127782, 0.91277156],
[0.71508164, 0.07621077, 0.1513623 , 0.60236517],
[0.20400476, 0.62579926, 0.96945959, 0.25369496],
[0.16045375, 0.8629679 , 0.52353014, 0.17874773],
[0.74106373, 0.18528856, 0.14129199, 0.27095403],
[0.4879966 , 0.15226884, 0.93318282, 0.30072195],
[0.19525753, 0.57281687, 0.73058896, 0.14141631],
[0.22105202, 0.83839086, 0.95349158, 0.34607576],
[0.73788021, 0.1770859 , 0.41733917, 0.38844367],
[0.77644933, 0.31537838, 0.78413422, 0.24615305],
[0.00668141, 0.39936199, 0.04353854, 0.25671659],
[0.03214832, 0.45194267, 0.66184994, 0.89484749],
[0.72763547, 0.74097694, 0.98295378, 0.60910494],
[0.54053955, 0.11799039, 0.27145158, 0.3194415 ],
[0.30207851, 0.89018352, 0.02261753, 0.89319996],
[0.3537182 , 0.74787213, 0.31305765, 0.71514002],
[0.97574293, 0.75465425, 0.35010109, 0.57163881],
[0.4731333 , 0.30506162, 0.52243232, 0.36218346],
[0.28175466, 0.50968116, 0.38608258, 0.40358579],
[0.51695478, 0.85474675, 0.85774048, 0.84621208],
[0.15897893, 0.45373403, 0.94443085, 0.9325776 ],
[0.25073291, 0.5943132 , 0.97380375, 0.92231432],
[0.35821384, 0.71748719, 0.30976215, 0.16130552],
[0.37425569, 0.63485209, 0.18244676, 0.12778917],
[0.06108208, 0.59688104, 0.50377873, 0.52455861],
[0.83639846, 0.04141602, 0.22538742, 0.51417511],
[0.78577046, 0.52873215, 0.68593581, 0.48329374],
[0.26058759, 0.69023617, 0.00289102, 0.14051131],
[0.01463525, 0.29499572, 0.22274016, 0.24510624],
[0.73379759, 0.43681881, 0.04314026, 0.93150594],
[0.22342956, 0.6266855 , 0.31738537, 0.03371984],
[0.97803818, 0.44051264, 0.60824868, 0.73362355],
[0.78723924, 0.66276966, 0.23916369, 0.62163246],
[0.71040393, 0.06991749, 0.98101301, 0.51514206],
[0.0821866 , 0.70404957, 0.72840406, 0.48547848],
[0.45443395, 0.15621613, 0.17911828, 0.03956511],
[0.23424694, 0.41055641, 0.7066062 , 0.98101608],
[0.5747051 , 0.77899437, 0.25603957, 0.9831105 ],
[0.28750575, 0.71722367, 0.39670689, 0.57363489],
[0.7982244 , 0.62949219, 0.57060217, 0.44593883],
[0.19825536, 0.98512959, 0.69419809, 0.7668913 ],
[0.09056463, 0.45531655, 0.07847721, 0.14693561],
[0.52506629, 0.83372737, 0.22320466, 0.89293756],
[0.53522865, 0.45356899, 0.96892514, 0.53857607],
[0.20160523, 0.67869879, 0.248409 , 0.73112456],
[0.32203211, 0.27556238, 0.36312517, 0.20047435],
[0.06899307, 0.9229278 , 0.81702242, 0.41710287],
[0.14494965, 0.99735291, 0.94911428, 0.45586484],
[0.20008054, 0.78333888, 0.13750651, 0.65110425],
[0.74495973, 0.4729519 , 0.46274407, 0.33829962],
[0.76848101, 0.71928529, 0.06376184, 0.16765253],
[0.94581353, 0.26161307, 0.02458549, 0.19938645],
[0.35482888, 0.18543557, 0.72909228, 0.82440544],
[0.25710995, 0.10172886, 0.54675609, 0.02942142],
[0.49069787, 0.99730936, 0.25178275, 0.22492832],
[0.59470998, 0.0990552 , 0.22297428, 0.59916495],
[0.08932617, 0.99201394, 0.81158043, 0.80204734],
[0.64921487, 0.63776038, 0.37171653, 0.78075762],
[0.81160287, 0.78522705, 0.56671516, 0.79639266],
[0.48031125, 0.5199653 , 0.02510022, 0.67543871],
[0.62950204, 0.63690502, 0.12682213, 0.38034912],
[0.50851116, 0.22648952, 0.8195524 , 0.73535656]]) * uV, [0.0 s, 0.1 s], sampling rate: 1000.0 Hz)>])
0: AnalogSignal with 4 channels of length 100; units uV; datatype float64
sampling rate: 1000.0 Hz
time: 0.0 s to 0.1 sExercise: Create a new block named 'block1' and store it in a variable block1. Add one segment named 'trial 1' to it. Display block1.
Solution
block1 = neo.Block(name='block1')
seg1 = neo.Segment(name='trial 1')
block1.segments.append(seg1)
block1Block with [<neo.core.segment.Segment object at 0x796e61b1c690>] segments
name: 'block1'
# segments (N=[<neo.core.segment.Segment object at 0x796e61b1c690>])
0: Segment with name: 'trial 1' # analogsignals (N=[])Add an AnalogSignal with 20 samples in 8 different channels with units mV and sampling rate 30 kHz to that segment. Display the block container to check that the segment has the AnalogSignal object.
Solution
sig = neo.AnalogSignal(np.random.random((500, 8)), units='uV', sampling_rate=30*pq.kHz)
seg1.analogsignals.append(sig)
block1Block with [<neo.core.segment.Segment object at 0x796e61b1c690>] segments
name: 'block1'
# segments (N=[<neo.core.segment.Segment object at 0x796e61b1c690>])
0: Segment with [<AnalogSignal(array([[0.31385486, 0.76043248, 0.45142754, ..., 0.48707806, 0.34379616,
0.04510295],
[0.67673109, 0.5881264 , 0.92849928, ..., 0.35967138, 0.71296387,
0.55719012],
[0.04567633, 0.53017849, 0.85565208, ..., 0.14785683, 0.52170599,
0.76013003],
...,
[0.43206517, 0.58124026, 0.80044508, ..., 0.3882088 , 0.42018751,
0.05134151],
[0.66993326, 0.6432051 , 0.14970099, ..., 0.81569998, 0.0429619 ,
0.52394085],
[0.56774556, 0.37461895, 0.71640984, ..., 0.88988559, 0.14830603,
0.94341334]], shape=(500, 8)) * uV, [0.0 s, 0.01666666666666667 s], sampling rate: 30.0 kHz)>] analogsignals
name: 'trial 1'
# analogsignals (N=[<AnalogSignal(array([[0.31385486, 0.76043248, 0.45142754, ..., 0.48707806, 0.34379616,
0.04510295],
[0.67673109, 0.5881264 , 0.92849928, ..., 0.35967138, 0.71296387,
0.55719012],
[0.04567633, 0.53017849, 0.85565208, ..., 0.14785683, 0.52170599,
0.76013003],
...,
[0.43206517, 0.58124026, 0.80044508, ..., 0.3882088 , 0.42018751,
0.05134151],
[0.66993326, 0.6432051 , 0.14970099, ..., 0.81569998, 0.0429619 ,
0.52394085],
[0.56774556, 0.37461895, 0.71640984, ..., 0.88988559, 0.14830603,
0.94341334]], shape=(500, 8)) * uV, [0.0 s, 0.01666666666666667 s], sampling rate: 30.0 kHz)>])
0: AnalogSignal with 8 channels of length 500; units uV; datatype float64
sampling rate: 30.0 kHz
time: 0.0 s to 0.01666666666666667 sExercise: Add a SpikeTrain object containing the timestamps [0.1, 0.4, 0.8] to the segment’s spiketrains list, annotated with unit_id=0. Display the spiketrains of the segment using print.
Solution
st = neo.SpikeTrain([0.1, 0.4, 0.8], units='s', t_start=0*pq.s, t_stop=1*pq.s)
st.annotate(unit_id=0)
seg1.spiketrains.append(st)
print(seg1.spiketrains)[<SpikeTrain(array([0.1, 0.4, 0.8]) * s, [0.0 s, 1.0 s])>]Exercise: Add another SpikeTrain object containing the timestamps [0.2, 0.6] to the segment’s spiketrains list, annotated with unit_id=1. Print the segment spiketrains to see that it now contains both spike trains.
Solution
st = neo.SpikeTrain([0.2, 0.6], units='s', t_start=0*pq.s, t_stop=1*pq.s)
st.annotate(unit_id=1)
seg1.spiketrains.append(st)
print(seg1.spiketrains)[<SpikeTrain(array([0.1, 0.4, 0.8]) * s, [0.0 s, 1.0 s])>, <SpikeTrain(array([0.2, 0.6]) * s, [0.0 s, 1.0 s])>]Exercise: Use filter to get a list of all AnalogSignal objects in the block.
Solution
block1.filter(objects=neo.AnalogSignal)[AnalogSignal with 8 channels of length 500; units uV; datatype float64
sampling rate: 30.0 kHz
time: 0.0 s to 0.01666666666666667 s]Exercise: Use filter to get a list of all SpikeTrain objects in the block, and report how many there are.
Solution
len(block1.filter(objects=neo.SpikeTrain))2Section 2: Exploring a Dataset
Background
When you load a recording, the first thing to do is find out what it contains: how many continuous recording parts (segments) there are, how many channels were recorded and at what sampling rates, and how many spike trains are present. The Block and Segment objects expose this through their lists (block.segments, segment.analogsignals, segment.spiketrains, segment.events), and the filter method lets you select objects by type or by annotation.
Exercises
In this section you will inspect the structure of block, read off the properties of its signals, and use annotations to select a meaningful subset of the spike trains.
| Code | Description |
|---|---|
block |
Print a summary of a block and its contents. |
block.segments |
The list of segments in the block. |
len(block.segments) |
The number of segments in the block. |
segment = block.segments[0] |
Access the first (here, only) segment. |
segment.analogsignals |
The list of AnalogSignal objects in a segment. |
sig.name, sig.shape, sig.sampling_rate |
Properties of an AnalogSignal. |
segment.spiketrains |
The list of SpikeTrain objects in a segment. |
len(segment.spiketrains) |
The number of spike trains in a segment. |
block.filter(objects=neo.SpikeTrain, targdict={'unit_type': 'sua'}) |
Find all spike trains with a given annotation. |
The Dataset
Run the cell below to load the dataset used for the rest of this notebook from the NIX file in the data folder. It contains a single Block with one Segment that contains:
- two
AnalogSignalobjects, both spanning 10 s: a 96-channel LFP-like signal sampled at 1 kHz, and a smaller 6-channel signal sampled at 30 kHz, each with per-channelchannel_names; - a set of
SpikeTrainobjects, each annotated as'sua','mua', or'noise'; - an
Eventobject marking trial-start (TS-ON) times, with a per-event annotation recording whether each trial was a'correct_trial'or an'error_trial'.
This is the kind of structure you obtain when loading a real recording from disk.
with neo.NixIO('dataset.nix', mode='ro') as io:
block = io.read_block()
blockBlock with [<neo.core.segment.Segment object at 0x796e00a716e0>] segments
name: 'dataset'
annotations: {'nix_name': 'neo.block.2a1dd887fea94ef08644f82912592501'}
rec_datetime: datetime.datetime(2026, 6, 23, 14, 30, 41)
# segments (N=[<neo.core.segment.Segment object at 0x796e00a716e0>])
0: Segment with [<AnalogSignal(array([[ 0.12573022, -0.13210486, 0.64042265, ..., -0.73548329,
0.24978537, 1.03145308],
[ 0.16100958, -0.58552882, -1.34121971, ..., -0.47433299,
-1.94426498, -1.3077532 ],
[ 1.08683078, -0.05060406, -0.28312507, ..., 0.11566183,
-1.07054441, -1.0026843 ],
...,
[ 0.55947905, 0.18952103, -0.67187467, ..., -0.05874888,
-0.11022754, -1.18243894],
[-0.73628695, -1.45294563, 0.08538314, ..., -0.25738861,
2.05335138, 2.77651512],
[ 1.06179533, -0.97950888, -0.9572926 , ..., -1.33537531,
-0.5644885 , 1.39068265]], shape=(10000, 96)) * uV, [0.0 s, 10.0 s], sampling rate: 1000.0 Hz)>, <AnalogSignal(array([[ 0.03830457, 0.76155729, -1.32356554, -0.08666677, 1.4826299 ,
-0.18484429],
[ 0.87552353, 0.44731624, -0.94533032, -0.10376092, 0.03962154,
0.5461 ],
[ 0.73429673, -0.20856981, 1.56456884, 0.10141642, 1.03134815,
-0.00998905],
...,
[-0.41569788, -0.19087587, -0.26756384, -0.58221111, -0.13351223,
-2.12068732],
[-0.74101601, -1.15277753, -0.4678359 , -0.06320785, 0.51200115,
1.0106027 ],
[-0.53238621, 0.94964953, -0.91726567, 0.50577158, 0.91066149,
-0.67364567]], shape=(300000, 6)) * uV, [0.0 s, 10.0 s], sampling rate: 30000.0 Hz)>] analogsignals, [<Event: TS-ON@1.0 s, TS-ON@4.0 s, TS-ON@7.0 s>] events, [<SpikeTrain(array([0.06255232, 0.17229314, 0.35546969, 0.43940627, 0.45286489,
0.57216993, 0.66150297, 0.68403626, 0.97544401, 0.99629525,
1.22504582, 1.29172344, 1.30962194, 1.34612355, 1.63453615,
1.95132128, 2.03042548, 2.32087773, 2.41268398, 2.63514632,
2.64414615, 2.68943961, 2.72288669, 2.75712501, 2.87749682,
2.95589664, 3.10577317, 3.16506945, 3.27508983, 3.39733024,
3.49028214, 3.68942795, 3.72045857, 3.782087 , 3.80290789,
3.92791594, 3.94978005, 3.99226142, 4.11441249, 4.30439043,
4.33699924, 4.37429665, 4.39064016, 4.67752775, 4.79572961,
4.86501922, 5.35588595, 5.40147166, 5.52550048, 5.69186469,
5.77148307, 5.80108914, 5.8564252 , 5.98664512, 6.49610868,
6.91378433, 6.93531879, 7.07945198, 7.18180104, 7.21523697,
7.33628371, 7.42658881, 7.57237965, 7.71111907, 7.84640558,
7.91092659, 8.12080393, 8.28589428, 8.28729526, 8.4264617 ,
8.51440986, 8.56606964, 8.89497594, 8.99710392, 9.25551989,
9.39371355, 9.53716274, 9.70237072, 9.72987502, 9.73175672,
9.75786951]) * s, [0.0 s, 10.0 s])>, <SpikeTrain(array([0.06847113, 0.35692403, 0.47880872, 0.48950007, 0.65169639,
0.73077159, 1.03506481, 1.07152666, 1.31838402, 1.73696167,
1.90173813, 2.15313503, 2.53680088, 2.64073043, 2.87868126,
2.93151029, 2.98539483, 3.10384025, 3.36022195, 3.85066322,
4.13597126, 4.40799009, 4.60926438, 4.66693605, 4.6813333 ,
5.28031343, 5.48201958, 5.66978309, 5.90705153, 5.92030833,
5.97395297, 6.08105221, 6.14824079, 6.58727822, 6.61681404,
6.66232052, 6.68099174, 6.83537055, 7.49668801, 7.53058542,
7.84807152, 8.22811811, 8.26902639, 8.44344397, 8.66505094,
8.72865709, 8.87451201, 9.07465781, 9.42552066, 9.57387672]) * s, [0.0 s, 10.0 s])>, <SpikeTrain(array([0.01232907, 0.22999569, 0.46210796, 0.51565567, 0.59753085,
0.64771418, 0.6571189 , 0.67341255, 0.73707138, 0.74436944,
0.75966458, 0.83854459, 0.904926 , 0.91684295, 0.97050639,
0.99742267, 1.08107826, 1.17926204, 1.19857316, 1.22142795,
1.27108835, 1.32588978, 1.32637494, 1.37522424, 1.39565337,
1.57317691, 1.76784732, 1.7897853 , 1.79949939, 1.83389945,
1.83874258, 1.89130713, 1.93086077, 1.96143733, 1.97640726,
2.16976593, 2.30311486, 2.42426439, 2.45787881, 2.4987029 ,
2.49958426, 2.54155714, 2.60265081, 2.66613987, 2.67160359,
2.79534109, 2.80532753, 2.8107193 , 2.94718733, 3.0560039 ,
3.1456284 , 3.37515786, 3.5503807 , 3.55586834, 3.57096197,
3.62182409, 3.63013031, 3.66762218, 3.67146927, 3.7670692 ,
3.78421137, 4.01765329, 4.13306497, 4.15046408, 4.17796879,
4.38856325, 4.42220325, 4.4394032 , 4.54240677, 4.59266349,
4.61447018, 4.64969123, 4.66113929, 4.83909232, 4.90514783,
5.15910683, 5.16926089, 5.3326661 , 5.37653543, 5.45761439,
5.47589775, 5.5211612 , 5.56834364, 5.64856932, 5.64919109,
6.02397102, 6.25420755, 6.37958951, 6.43196564, 6.47220268,
6.5482084 , 6.61504371, 6.72198195, 6.73454563, 6.74791382,
6.77443294, 6.9032628 , 6.98475687, 7.00158971, 7.09098782,
7.1426876 , 7.23164097, 7.8729888 , 7.88267637, 7.95936937,
8.06649273, 8.11519263, 8.21227269, 8.23116095, 8.2539788 ,
8.40367619, 8.5157866 , 8.54416162, 8.60509854, 8.67143819,
8.68029821, 8.91423068, 8.91843104, 8.99768767, 9.13657443,
9.14893181, 9.15746063, 9.27080888, 9.27224126, 9.31642566,
9.54168979, 9.61129809, 9.68222417, 9.86707865, 9.96997018,
9.99923607]) * s, [0.0 s, 10.0 s])>, <SpikeTrain(array([0.21429884, 0.40667116, 0.64734906, 0.71004978, 0.87376134,
1.19515042, 1.23587196, 1.30507311, 1.58759466, 1.77564389,
1.94213951, 2.03701155, 2.17617765, 2.35439758, 2.62886634,
3.01854063, 3.29418715, 3.84584969, 3.94852346, 4.01465219,
4.27464272, 4.43547889, 4.51482121, 4.55254238, 5.17417082,
5.20617247, 5.21965515, 5.28875305, 5.5199543 , 6.01535424,
6.38901614, 6.4031589 , 6.41454536, 6.56322995, 6.69488466,
6.85408323, 6.8747921 , 7.11791425, 7.20549555, 7.47275701,
7.56178323, 7.80438797, 8.00221239, 8.52865236, 8.71205845,
8.7555999 , 9.21365201, 9.51807755, 9.87743812, 9.9484045 ]) * s, [0.0 s, 10.0 s])>, <SpikeTrain(array([5.47709347e-03, 6.32224660e-02, 1.47904136e-01, 2.33875192e-01,
4.55599696e-01, 5.55410233e-01, 5.97094279e-01, 6.06911102e-01,
8.36710101e-01, 9.32104229e-01, 9.54172495e-01, 1.09270602e+00,
1.12928245e+00, 1.15698012e+00, 1.19049460e+00, 1.26608088e+00,
1.36968210e+00, 1.37870629e+00, 1.45196498e+00, 1.67205045e+00,
1.68307278e+00, 1.73829814e+00, 1.88104842e+00, 1.89462184e+00,
2.11389491e+00, 2.15689818e+00, 2.25674184e+00, 2.30180802e+00,
2.47474417e+00, 2.55520962e+00, 2.68554699e+00, 2.87925627e+00,
2.90825539e+00, 2.95406582e+00, 3.01023100e+00, 3.34473475e+00,
3.39065375e+00, 3.47846606e+00, 4.05914025e+00, 4.06955818e+00,
4.36941899e+00, 4.52114282e+00, 4.65526263e+00, 4.71157499e+00,
4.84126575e+00, 5.00205068e+00, 5.11467241e+00, 5.18505640e+00,
5.24015772e+00, 5.24124156e+00, 5.33847406e+00, 5.56378996e+00,
5.70968214e+00, 5.82727013e+00, 5.90769960e+00, 6.01861313e+00,
6.07168053e+00, 6.46426253e+00, 6.48129715e+00, 6.49022453e+00,
6.50704257e+00, 6.56042423e+00, 6.64775242e+00, 6.68624803e+00,
6.84796099e+00, 6.86147446e+00, 7.06573817e+00, 7.06860157e+00,
7.07277134e+00, 7.23159978e+00, 7.70648172e+00, 7.94198746e+00,
7.97321010e+00, 8.18218637e+00, 8.22411099e+00, 8.27307330e+00,
8.52219019e+00, 8.54350340e+00, 8.54793213e+00, 8.93424149e+00,
8.95075497e+00, 8.98224947e+00, 8.98583788e+00, 8.99365143e+00,
9.34331562e+00, 9.40687794e+00, 9.41832953e+00, 9.46571908e+00,
9.72245375e+00, 9.72930521e+00, 9.88244697e+00, 9.93239354e+00,
9.94182156e+00]) * s, [0.0 s, 10.0 s])>, <SpikeTrain(array([4.40148079e-03, 5.06979003e-02, 1.13843461e-01, 2.43953835e-01,
2.98223945e-01, 5.40820257e-01, 7.14768749e-01, 7.57642040e-01,
7.91728189e-01, 8.36295324e-01, 1.06014712e+00, 1.09809771e+00,
1.20613863e+00, 1.25789985e+00, 1.36942501e+00, 1.43034281e+00,
1.80771295e+00, 1.81250537e+00, 2.02716014e+00, 2.06844096e+00,
2.32019786e+00, 2.56278491e+00, 2.65516057e+00, 2.69207930e+00,
2.77951713e+00, 3.08720383e+00, 3.18822082e+00, 3.28375504e+00,
3.30795497e+00, 3.35830457e+00, 3.37275614e+00, 3.70619451e+00,
3.70627924e+00, 3.92976380e+00, 3.95249831e+00, 4.08655461e+00,
4.15039669e+00, 4.20714162e+00, 4.38708213e+00, 4.38956320e+00,
4.62382012e+00, 4.70430713e+00, 4.87859368e+00, 4.89935801e+00,
5.25744676e+00, 5.35412254e+00, 5.50323591e+00, 5.66068590e+00,
5.67501515e+00, 5.76442574e+00, 5.85815032e+00, 5.88286882e+00,
6.14362592e+00, 6.20859755e+00, 6.23694562e+00, 6.47374669e+00,
6.48163885e+00, 6.66614934e+00, 6.75313656e+00, 6.78734058e+00,
6.83465519e+00, 6.92577876e+00, 6.99989808e+00, 7.02739645e+00,
7.04652934e+00, 7.07176994e+00, 7.10176417e+00, 7.10545121e+00,
7.28592097e+00, 7.31570443e+00, 7.35612613e+00, 7.40684579e+00,
7.43958065e+00, 7.49201459e+00, 8.03000814e+00, 8.25968634e+00,
8.26229814e+00, 8.42061153e+00, 8.57732611e+00, 8.63175225e+00,
8.77226634e+00, 8.89645709e+00, 9.01501383e+00, 9.14876616e+00,
9.23400122e+00, 9.50052199e+00, 9.54147097e+00, 9.70337743e+00,
9.83226293e+00, 9.86462338e+00, 9.88633361e+00]) * s, [0.0 s, 10.0 s])>, <SpikeTrain(array([6.75181079e-03, 2.25336916e-01, 8.69319901e-01, 9.25027390e-01,
1.08992363e+00, 1.15191644e+00, 1.22435176e+00, 1.33862622e+00,
1.40499452e+00, 1.51115576e+00, 1.75836586e+00, 1.81618003e+00,
1.84949304e+00, 1.87425365e+00, 1.88229227e+00, 1.98739788e+00,
2.26405892e+00, 2.30788685e+00, 2.43149410e+00, 2.43903433e+00,
2.46898600e+00, 2.49947535e+00, 2.60188373e+00, 2.67182294e+00,
2.71415322e+00, 2.90696533e+00, 3.05991229e+00, 3.08919790e+00,
3.29951961e+00, 3.44746030e+00, 3.53860927e+00, 3.59020474e+00,
3.67047593e+00, 3.70436943e+00, 3.71979857e+00, 3.76041592e+00,
3.77138006e+00, 3.90224666e+00, 4.00082541e+00, 4.08591489e+00,
4.22226225e+00, 4.47910473e+00, 4.49331907e+00, 4.68373480e+00,
5.01542003e+00, 5.02584761e+00, 5.11692055e+00, 5.18878922e+00,
5.39556165e+00, 5.44336743e+00, 5.49479813e+00, 5.60266327e+00,
5.77668078e+00, 5.86508473e+00, 5.92655693e+00, 5.98894690e+00,
6.08643908e+00, 6.10946807e+00, 6.14675439e+00, 6.28587039e+00,
6.33524818e+00, 6.53655325e+00, 6.63703917e+00, 6.67344235e+00,
6.85024904e+00, 6.85815344e+00, 7.04229306e+00, 7.27003596e+00,
7.55440597e+00, 7.60478211e+00, 7.93423785e+00, 8.16120994e+00,
8.38696775e+00, 8.48593574e+00, 8.82511840e+00, 8.88533177e+00,
9.03647671e+00, 9.19936019e+00, 9.28581393e+00, 9.34750688e+00,
9.48910630e+00, 9.56002464e+00, 9.62841762e+00, 9.65486765e+00,
9.79390567e+00, 9.98607720e+00]) * s, [0.0 s, 10.0 s])>, <SpikeTrain(array([0.06469192, 0.10621435, 0.14154379, 0.19332316, 0.28984526,
0.34958026, 0.364381 , 0.39874483, 0.50729933, 0.56298177,
0.69522629, 0.71105398, 0.7151176 , 0.7768351 , 0.7946275 ,
0.93947359, 0.96344243, 1.03534031, 1.04539714, 1.14798385,
1.34405908, 1.5897129 , 1.6231431 , 1.76306627, 1.77072373,
1.81933989, 1.82969342, 1.96714655, 1.99501775, 2.03345332,
2.15656138, 2.2158512 , 2.51266078, 2.54962876, 2.61250014,
2.64346183, 2.82561236, 2.92800557, 3.24000507, 3.26651816,
3.27093769, 3.27904108, 3.28481952, 3.28554356, 3.45102684,
3.48882028, 3.55280674, 3.59327492, 3.66418642, 3.99401233,
4.04496238, 4.20644363, 4.4041731 , 4.40495947, 4.43598122,
4.46290522, 4.47499591, 4.49022684, 4.50906266, 4.76277785,
4.78698295, 4.84478462, 4.90564363, 4.91468169, 4.97970451,
5.15321067, 5.2256521 , 5.34687545, 5.52877281, 5.66724245,
5.70456178, 5.7337321 , 5.80289466, 6.13789608, 6.22496053,
6.23683904, 6.31594257, 6.35906557, 6.42115732, 6.45749525,
6.46497999, 6.51350743, 6.82026205, 6.82395354, 6.83642842,
6.88780865, 7.07561735, 7.07758011, 7.21059672, 7.22226279,
7.25039296, 7.32469098, 7.38164366, 7.59652772, 7.60726748,
7.70214899, 7.73340797, 7.74566238, 7.83442392, 7.8376963 ,
7.83800436, 7.94845631, 7.95745629, 7.97449737, 8.03240633,
8.12627372, 8.1286061 , 8.23711259, 8.37148331, 8.48114518,
8.48887076, 8.51023613, 8.54172011, 8.61633225, 8.66015286,
8.90645198, 8.92819739, 8.94194942, 8.99068392, 9.01556249,
9.015941 , 9.04300311, 9.04373845, 9.09654648, 9.16708183,
9.19180427, 9.21513602, 9.2357088 , 9.35166969, 9.40052109,
9.43909549, 9.51780372, 9.54392623, 9.57692816, 9.58785673,
9.60973461, 9.62962076, 9.66426688, 9.87679177, 9.96557063]) * s, [0.0 s, 10.0 s])>] spiketrains
name: 'recording'
annotations: {'nix_name': 'neo.segment.61be69f5b2cd4350b98b39b78499fe19'}
# analogsignals (N=[<AnalogSignal(array([[ 0.12573022, -0.13210486, 0.64042265, ..., -0.73548329,
0.24978537, 1.03145308],
[ 0.16100958, -0.58552882, -1.34121971, ..., -0.47433299,
-1.94426498, -1.3077532 ],
[ 1.08683078, -0.05060406, -0.28312507, ..., 0.11566183,
-1.07054441, -1.0026843 ],
...,
[ 0.55947905, 0.18952103, -0.67187467, ..., -0.05874888,
-0.11022754, -1.18243894],
[-0.73628695, -1.45294563, 0.08538314, ..., -0.25738861,
2.05335138, 2.77651512],
[ 1.06179533, -0.97950888, -0.9572926 , ..., -1.33537531,
-0.5644885 , 1.39068265]], shape=(10000, 96)) * uV, [0.0 s, 10.0 s], sampling rate: 1000.0 Hz)>, <AnalogSignal(array([[ 0.03830457, 0.76155729, -1.32356554, -0.08666677, 1.4826299 ,
-0.18484429],
[ 0.87552353, 0.44731624, -0.94533032, -0.10376092, 0.03962154,
0.5461 ],
[ 0.73429673, -0.20856981, 1.56456884, 0.10141642, 1.03134815,
-0.00998905],
...,
[-0.41569788, -0.19087587, -0.26756384, -0.58221111, -0.13351223,
-2.12068732],
[-0.74101601, -1.15277753, -0.4678359 , -0.06320785, 0.51200115,
1.0106027 ],
[-0.53238621, 0.94964953, -0.91726567, 0.50577158, 0.91066149,
-0.67364567]], shape=(300000, 6)) * uV, [0.0 s, 10.0 s], sampling rate: 30000.0 Hz)>])
0: AnalogSignal with 96 channels of length 10000; units uV; datatype float64
name: 'LFP'
annotations: {'nix_name': 'neo.analogsignal.ca67602f93a84f96bbf1251185bb5508'}
sampling rate: 1000.0 Hz
time: 0.0 s to 10.0 s
1: AnalogSignal with 6 channels of length 300000; units uV; datatype float64
name: 'Subsample unprocessed signal'
annotations: {'nix_name': 'neo.analogsignal.d4766f521df347a99e99d5b680f7331a'}
sampling rate: 30000.0 Hz
time: 0.0 s to 10.0 sExercise: How many segments does the block contain? Hint: use len.
Solution
len(block.segments)1Exercise: Assign the first segment to a variable segment. How many AnalogSignal objects does the segment contain?
Solution
segment = block.segments[0]
len(segment.analogsignals)2Exercise: For the first AnalogSignal in the segment, print its name, shape and sampling rate.
Solution
sig = segment.analogsignals[0]
print(sig.name, sig.shape, sig.sampling_rate)LFP (10000, 96) 1000.0 HzExercise: For the second AnalogSignal in the segment, print its name, shape and sampling rate. Which of the two signals has the higher temporal resolution?
Solution
sig = segment.analogsignals[1]
print(sig.name, sig.shape, sig.sampling_rate)Subsample unprocessed signal (300000, 6) 30000.0 HzExercise: How many SpikeTrain objects does the segment contain?
Solution
len(segment.spiketrains)8Exercise: Use filter to get all spike trains annotated as single-unit activity (unit_type equal to 'sua'), and report how many there are.
Solution
sua = block.filter(objects=neo.SpikeTrain, targdict={'unit_type': 'sua'})
len(sua)4Exercise: Use filter to get all spike trains annotated as multi-unit activity (unit_type equal to 'mua'), and report how many there are.
Solution
mua = block.filter(objects=neo.SpikeTrain, targdict={'unit_type': 'mua'})
len(mua)2Exercise: Use filter to get all spike trains annotated as noise (unit_type equal to 'noise'). How many noise spike trains does the block contain?
Solution
noise = block.filter(objects=neo.SpikeTrain, targdict={'unit_type': 'noise'})
len(noise)2The exact-value targdict matching used above works well for equality tests, but Neo also supports FilterCondition objects from neo.core.filters that express richer queries — inequality tests and set membership — directly inside block.filter() without needing a separate list comprehension.
| Condition | Description |
|---|---|
IsNot(x) |
annotation ≠ x |
IsIn([a, b]) |
annotation is one of a, b |
Exercise: Use block.filter() with IsNot('noise') on the unit_type annotation to retrieve all spike trains that are not noise in a single call. Print how many are returned, and compare with the sum of the SUA and MUA counts found earlier.
Solution
non_noise = block.filter(objects=neo.SpikeTrain,
targdict={'unit_type': IsNot('noise')})
print(len(non_noise))6Exercise: Use IsIn(['sua', 'mua']) to retrieve all spike trains whose unit_type is either 'sua' or 'mua' in one call. Confirm the result is the same as the IsNot('noise') query above.
Solution
sua_or_mua = block.filter(objects=neo.SpikeTrain,
targdict={'unit_type': IsIn(['sua', 'mua'])})
print(len(sua_or_mua))
print(len(sua_or_mua) == len(non_noise))6
TrueThe equality and membership conditions used above (IsNot, IsIn) are useful for string annotations. When annotations carry numeric values, Neo’s filter also supports inequality and range conditions from neo.core.filters:
| Condition | Description |
|---|---|
GreaterThan(x) |
annotation value > x |
LessThan(x) |
annotation value < x |
GreaterThanOrEquals(x) |
annotation value ≥ x |
LessThanOrEquals(x) |
annotation value ≤ x |
InRange(lo, hi) |
lo ≤ value ≤ hi |
These conditions are passed as values in targdict, e.g. block.filter(objects=neo.SpikeTrain, targdict={'unit_id': GreaterThan(3)}).
Exercise: Add four more empty SpikeTrain objects (t_stop=1*pq.s) to block1’s segment (which already has two spike trains attached from a previous exercise), annotated with unit_id from 2 to 5. Then use GreaterThan(3) to retrieve all spike trains in block1 whose unit_id is strictly greater than 3. Print the unit_id annotation of each returned spike train.
Solution
for uid in range(2, 6):
st = neo.SpikeTrain([], units='s', t_stop=1*pq.s)
st.annotate(unit_id=uid)
seg1.spiketrains.append(st)
high_units = block1.filter(objects=neo.SpikeTrain,
targdict={'unit_id': GreaterThan(3)})
print([st.annotations['unit_id'] for st in high_units])[4, 5]Exercise: Use InRange(1, 3) to retrieve all spike trains in block1 whose unit_id lies between 1 and 3 (bounds inclusive). Print the matching unit_id values and confirm the count.
Solution
mid_units = block1.filter(objects=neo.SpikeTrain,
targdict={'unit_id': InRange(1, 3)})
print([st.annotations['unit_id'] for st in mid_units])
print(len(mid_units))[1, 2, 3]
3Section 3: Selecting and Masking Data
Background
Analysis usually focuses on a subset of the data: certain channels, certain trials, or a certain time window. Neo gives you several complementary ways to slice data out of an object:
- Boolean masks on array annotations select the events (or channels) whose metadata satisfies a condition. A mask is a boolean array you build by comparing an array annotation to a value. Indexing an
Eventwith a mask returns a newEventwith just those time points. - Column slicing (
sig[:, indices]) selects channels of anAnalogSignal. time_slice(t_start, t_stop)returns a new object containing only the data in a time window, and works on signals, spike trains, and whole segments.
Exercises
In this section you will select events from correct trials with a mask, pick out a range of channels, and extract a time window of a signal.
| Code | Description |
|---|---|
segment.events[0] |
Access the first Event object in a segment. |
ev.array_annotations['performance_in_trial_str'] |
Read a per-event array annotation. |
mask = ev.array_annotations['key'] == 'value' |
Build a boolean mask from a condition. |
ev[mask] |
Select the events where the mask is True. |
len(events) |
The number of time points in an Event. |
sig[:, 10:20] |
Select a range of channels (columns) of a signal. |
sig.shape |
The shape of the underlying data array. |
sig.array_annotations['channel_names'] |
Read the names of the channels. |
sig.time_slice(5*pq.s, 6*pq.s) |
Extract the data between two time points. |
Example: The segment’s Event object marks trial starts. Build a mask selecting only the events from correct trials, and use it to create a new Event containing just those time points. performance_in_trial_str is an array annotation that shows, for each individual event contained within the object, a string indicating the performance of the trial during which the event was recorded.
events = segment.events[0]
mask = events.array_annotations['performance_in_trial_str'] == 'correct_trial'
correct_events = events[mask]
correct_eventsEvent containing 2 events with labels; time units s; datatype float64
name: 'TrialEvents'
annotations: {'nix_name': 'neo.event.4263dcb220c14277b484863027daa315'}Exercise: How many trial-start events were there in total, and how many came from correct trials? Hint: use len.
Solution
len(events), len(correct_events)(3, 2)Exercise: Select the channels at indices 10 to 19 (inclusive) of the LFP signal (the first AnalogSignal of the segment) and assign it to a variable named lfp_subset. Check the shape of the data in this subset of LFP channels.
Solution
lfp = segment.analogsignals[0]lfp_subset = lfp[:, 10:20]
lfp_subset.shape(10000, 10)Exercise: Read the channel_names array annotation of lfp_subset to confirm which channels you selected.
Solution
lfp_subset.array_annotations['channel_names']array(['chan11', 'chan12', 'chan13', 'chan14', 'chan15', 'chan16',
'chan17', 'chan18', 'chan19', 'chan20'], dtype='<U6')Exercise: Use time_slice to extract the data of lfp between 5 and 6 seconds, and check the shape of the result (it should contain 1 second of data at 1 kHz from 96 channels).
Solution
window = lfp.time_slice(5*pq.s, 6*pq.s)
window.shape(1000, 96)Exercise: Use time_slice to extract the data of lfp_subset between 1 and 5 seconds, and check the shape of the result (it should contain 4 seconds of data at 1 kHz from 10 chanels).
Solution
window = lfp_subset.time_slice(1*pq.s, 5*pq.s)
window.shape(4000, 10)Beyond building boolean masks by hand, neo.utils.get_events() provides a convenience function that filters an event by any combination of its array annotations in a single call, returning a list of matching Event objects.
Exercise: Use neo.utils.get_events(segment, performance_in_trial_str='correct_trial') to retrieve the correct-trial events in one call. Print how many events are returned and compare with the mask approach used above.
Solution
correct_via_util = get_events(segment, performance_in_trial_str='correct_trial')
print(len(correct_via_util[0]))2Section 4: Epochs and Cutting Trials
Background
A common task is to align data to an experimental event — for example, to look at every signal in a window around each trial start. Neo provides two utility functions in neo.utils that make this straightforward:
add_epochbuilds anEpochobject spanning a fixed window around a set of events. You give it the segment, the events to anchor on (event1), and the window edges relative to each event (preandpost). Leavingevent2=Nonecuts a fixed window around single events rather than between pairs of events.cut_segment_by_epochuses such an epoch to slice a segment into a list of new segments, one per epoch. Withreset_time=True, every resulting segment starts at time 0, so trials are aligned and directly comparable.
Exercises
In this section you will build analysis epochs around the correct-trial starts and cut the recording into one aligned segment per trial.
| Code | Description |
|---|---|
add_epoch(segment, event1=ev, event2=None, pre=-100*pq.ms, post=500*pq.ms, attach_result=False, name='analysis_epochs') |
Build an epoch in a window around each event. |
len(epoch) |
The number of epochs created. |
epoch.durations |
The duration of each epoch. |
cut_segment_by_epoch(segment, epoch, reset_time=True) |
Cut a segment into one segment per epoch, aligned to time 0. |
seg.analogsignals[0].t_start |
The start time of a signal in a cut segment. |
Run the cell below to rebuild the correct-trial start events used in this section.
Solution
segment = block.segments[0]
events = segment.events[0]
correct_events = events[events.array_annotations['performance_in_trial_str'] == 'correct_trial']Example: Build an Epoch spanning from 100 ms before to 500 ms after each correct-trial start. The epoch is not attached to the segment (attach_result=False).
epoch = add_epoch(segment,
event1=correct_events, event2=None,
pre=-100*pq.ms, post=500*pq.ms,
attach_result=False,
name='analysis_epochs')
epochEpoch containing 2 epochs with labels; time units s; datatype float64
name: 'analysis_epochs'Exercise: How many epochs were created, and what is the duration of each? (There should be one epoch per correct-trial event, and each should last 600 ms.)
Solution
len(epoch), epoch.durations(2, array([0.6, 0.6]) * s)Exercise: Use cut_segment_by_epoch to cut the segment into one segment per epoch, aligning every trial to time 0 with reset_time=True. Store the result in a variable trial_segments.
Solution
trial_segments = cut_segment_by_epoch(segment, epoch, reset_time=True)
len(trial_segments)2Exercise: Confirm the alignment worked by checking the t_start of the first AnalogSignal in each trial segment — they should all start at the same time.
Solution
[seg.analogsignals[0].t_start for seg in trial_segments][array(0.) * s, array(0.) * s]Exercise: Verify that per-channel annotations survived the cut by printing the array_annotations of the first AnalogSignal in the first trial segment.
Solution
trial_segments[0].analogsignals[0].array_annotations{'channel_names': array(['chan1', 'chan2', 'chan3', 'chan4', 'chan5', 'chan6', 'chan7',
'chan8', 'chan9', 'chan10', 'chan11', 'chan12', 'chan13', 'chan14',
'chan15', 'chan16', 'chan17', 'chan18', 'chan19', 'chan20',
'chan21', 'chan22', 'chan23', 'chan24', 'chan25', 'chan26',
'chan27', 'chan28', 'chan29', 'chan30', 'chan31', 'chan32',
'chan33', 'chan34', 'chan35', 'chan36', 'chan37', 'chan38',
'chan39', 'chan40', 'chan41', 'chan42', 'chan43', 'chan44',
'chan45', 'chan46', 'chan47', 'chan48', 'chan49', 'chan50',
'chan51', 'chan52', 'chan53', 'chan54', 'chan55', 'chan56',
'chan57', 'chan58', 'chan59', 'chan60', 'chan61', 'chan62',
'chan63', 'chan64', 'chan65', 'chan66', 'chan67', 'chan68',
'chan69', 'chan70', 'chan71', 'chan72', 'chan73', 'chan74',
'chan75', 'chan76', 'chan77', 'chan78', 'chan79', 'chan80',
'chan81', 'chan82', 'chan83', 'chan84', 'chan85', 'chan86',
'chan87', 'chan88', 'chan89', 'chan90', 'chan91', 'chan92',
'chan93', 'chan94', 'chan95', 'chan96'], dtype='<U6')}Exercise: Compare spike counts across trial segments for the first spike train: print len(seg.spiketrains[0]) for each trial segment as a list. Do the counts differ across trials? Why would you expect that?
Solution
[len(seg.spiketrains[0]) for seg in trial_segments][6, 7]Section 5: Transforming and Visualizing
Background
Because Neo objects are unit-aware NumPy arrays, they support a range of transformations that preserve their metadata, and they integrate cleanly with plotting libraries such as Matplotlib. Useful operations include:
rescale(unit)— convert the data to a different but compatible physical unit (e.g. microvolts to millivolts).magnitude— strip the units and return a plain NumPy array, which is what you pass to plotting functions.downsample(factor)— return a new signal with the sampling rate reduced by an integer factor.
For plotting, sig.times gives the x-axis values and sig.magnitude gives the y-axis values, while array_annotations supply per-channel labels for the legend.
Exercises
In this section you will rescale and downsample a signal, and produce a labelled plot of a few channels.
| Code | Description |
|---|---|
sig.time_slice(0*pq.s, 1*pq.s) |
Extract a time window of the signal. |
sig.rescale(pq.mV) |
Convert the data to another compatible unit. |
sig.magnitude |
The data as a plain NumPy array, without units. |
sig.downsample(10) |
Reduce the sampling rate by an integer factor. |
sig.sampling_rate |
The sampling rate of the signal. |
sig.times |
The time stamps of the samples (with units). |
sig.dimensionality, sig.times.dimensionality |
The units of the signal and of its time stamps, useful for axis labels. |
sig.array_annotations['channel_names'] |
Per-channel labels for a legend. |
plt.plot(x, y, label='my_label') |
Plot a line and give it a legend label. |
Example: Take the first second of the LFP signal and rescale it from microvolts to millivolts.
lfp = segment.analogsignals[0]
lfp_1s = lfp.time_slice(0*pq.s, 1*pq.s)
lfp_mv = lfp_1s.rescale(pq.mV)
lfp_mvAnalogSignal with 96 channels of length 1000; units mV; datatype float64
name: 'LFP'
annotations: {'nix_name': 'neo.analogsignal.ca67602f93a84f96bbf1251185bb5508'}
sampling rate: 1000.0 Hz
time: 0.0 s to 1.0 sExercise: Get the underlying data of lfp_1s as a plain NumPy array (without units) and check its type and shape.
Solution
data = lfp_1s.magnitude
type(data), data.shape(numpy.ndarray, (1000, 96))Exercise: Downsample lfp by a factor of 10 and confirm the new sampling rate is 100 Hz.
Solution
lfp_ds = lfp.downsample(10)
lfp_ds.sampling_ratearray(100.) * HzExercise: Plot the first second of the channels at indices 10 to 14 of the LFP signal. Use sig.times for the x-axis and sig.magnitude for the y-axis, and label each line with its name from the channel_names array annotation. Add axis labels, a title, and a legend. For the axis labels, do not hard code the units, as you can obtain them from the object you are plotting using the .dimensionality attribute (see code reference).
Solution
subset = lfp.time_slice(0*pq.s, 1*pq.s)[:, 10:15]
names = subset.array_annotations['channel_names']
for i, name in enumerate(names):
plt.plot(subset.times, subset.magnitude[:, i], label=name)
plt.xlabel(f'Time ({subset.times.dimensionality})')
plt.ylabel(f'Voltage ({subset.dimensionality})')
plt.title('LFP channels 10-14')
plt.legend()
plt.show()Section 6: Visualizing Trial Data (Bonus)
Now that the recording is cut into aligned trial segments, we can produce the most common visualization in systems neuroscience: a raster plot, which shows the spike times of one unit stacked across all trials. We can also compute a trial-averaged signal (i.e., the mean LFP across trials).
Exercise: Collect the first spike train (spiketrains[0]) from every trial segment and plot a raster. For each trial i, use ax.plot(st.rescale('ms').magnitude, [i]*len(st), '|k', markersize=6). Add axis labels, a title, and a vertical dashed red line at x=0 to mark the reference event.
Solution
fig, ax = plt.subplots(figsize=(8, 3))
for i, seg in enumerate(trial_segments):
st = seg.spiketrains[0]
ax.plot(st.rescale('ms').magnitude, [i]*len(st), '|k', markersize=6)
ax.axvline(x=0, color='red', linestyle='--', label='event')
ax.set_xlabel('Time (ms)')
ax.set_ylabel('Trial')
ax.set_title('Raster plot – spiketrain 0')
ax.legend()
plt.show()Exercise: Use time_slice to extract only the first 300 ms after the reference event (0*pq.ms to 300*pq.ms) from the first spike train of the first trial segment. Print len before and after slicing.
Solution
st_full = trial_segments[0].spiketrains[0]
st_early = st_full.time_slice(0*pq.ms, 300*pq.ms)
print(len(st_full), len(st_early))6 2Exercise: Compute and plot the trial-averaged LFP of channel 0. Collect seg.analogsignals[0][:, 0].magnitude from every trial segment, compute np.mean(..., axis=0), and plot it against the time axis taken from the first trial segment. Add axis labels and a vertical dashed red line at x=0 marking the reference event.
Solution
lfp_trials = [seg.analogsignals[0][:, 0].magnitude for seg in trial_segments]
mean_lfp = np.mean(lfp_trials, axis=0)
time_axis = trial_segments[0].analogsignals[0].times.rescale('ms').magnitude
fig, ax = plt.subplots(figsize=(8, 3))
ax.plot(time_axis, mean_lfp)
ax.axvline(x=0, color='red', linestyle='--', label='event')
ax.set_xlabel('Time (ms)')
ax.set_ylabel(f'LFP ({trial_segments[0].analogsignals[0].dimensionality})')
ax.set_title('Trial-averaged LFP – channel 0')
ax.legend()
plt.show()Note: This section uses
nixio, which requires a compiled HDF5 library. It runs correctly with the course’s pixi environment but cannot run in JupyterLite (browser-based execution). Run these cells with a local kernel.
Section 7: From Neo to NIX: Storing Neuroscience Data
NIX is an HDF5-based file format purpose-built for neuroscience data. Its central idea is an explicit object model that keeps data and its description inseparable:
- A
Filecontains one or moreBlockobjects, each representing a recording session or dataset. (Note: This is not a Neo Block!) - Each
BlockcontainsDataArrayobjects. ADataArraywraps a numpy array and stores its physicalunitand a human-readablelabel. - Every axis of a
DataArrayis described by a dimension descriptor: aSampledDimensionfor regularly sampled axes (e.g. a time axis at 1 kHz), or aSetDimensionfor categorical axes (e.g. channel names).
This object model maps naturally onto Neo: when neo.io.NixIO writes a Neo Block to disk it creates a NIX file containing a NIX Block, with every AnalogSignal channel stored as its own DataArray with proper dimension descriptors. Neo’s UUID-based internal naming is designed for round-tripping; when you write your own analysis results to NIX you are free to use clean, human-readable names.
The exercises below first introduce the raw nixio API, then show how to peek inside a Neo-generated NIX file and how to store analysis results with descriptive metadata. As an outlook, besides storing Neo objects in the NIX file format, you can store additional, non-electrophysiology data in the NIX file using nixio, such as behavioral data or analysis results.
| Code | Description |
|---|---|
nixio.File.open('f.nix', nixio.FileMode.Overwrite) |
Create a new file (overwrites if it exists) |
nixio.File.open('f.nix', nixio.FileMode.ReadOnly) |
Open an existing file for reading |
nixio.File.open('f.nix', nixio.FileMode.ReadWrite) |
Open an existing file for reading and writing |
with nixio.File.open(...) as nf: |
Context manager — always use this form to ensure the file is closed |
nf.blocks |
List of all blocks in the file |
blk = nf.create_block('name', 'type') |
Create a Block inside the file |
da = blk.create_data_array('name', 'type', data=arr, unit='mV', label='voltage') |
Create a DataArray from a numpy array |
blk.data_arrays[0] |
Access a DataArray by index |
da[:] |
Read all data back as a numpy array |
da.shape, da.unit, da.label |
Properties of a DataArray |
da.append_sampled_dimension(dt, unit='s', label='time') |
Attach a regularly-sampled axis (e.g. a time axis) |
da.append_set_dimension(labels=['Fz', 'Cz', 'Pz']) |
Attach a categorical axis with explicit labels |
da.dimensions[0].dimension_type |
Type of the first axis descriptor |
da.dimensions[0].sampling_interval |
Sampling interval of a SampledDimension |
da.dimensions[1].labels |
Category labels of a SetDimension |
import nixioExercise: Create a file 'recording.nix' in Overwrite mode using the context manager. Inside the with block, print nf.blocks to confirm the file starts empty, then create a block named 'session' with type 'nix.session'. Print nf.blocks again to confirm the block was added.
Solution
with nixio.File.open('recording.nix', nixio.FileMode.Overwrite) as nf:
print('before:', nf.blocks)
blk = nf.create_block('session', 'nix.session')
print('after: ', nf.blocks)before: []
after: [Block: {name = session, type = nix.session}]Exercise: Reopen 'recording.nix' in ReadWrite mode. Retrieve the block (nf.blocks[0]). Create a DataArray named 'voltage' from the 1D sine-wave signal defined in the cell below, with unit='mV' and label='membrane voltage'. Append a SampledDimension with sampling_interval=0.001, unit='s', and label='time'. Print da.shape.
Solution
dt = 0.001 # 1 ms sampling interval
t = np.arange(0, 1.0, dt)
voltage = np.sin(2 * np.pi * 5 * t) * 10 # 5 Hz sine, amplitude 10 mV
with nixio.File.open('recording.nix', nixio.FileMode.ReadWrite) as nf:
blk = nf.blocks[0]
da = blk.create_data_array(
'voltage', 'nix.sampled',
data=voltage, unit='mV', label='membrane voltage'
)
da.append_sampled_dimension(dt, unit='s', label='time')
print(da.shape)(1000,)Exercise: Reopen 'recording.nix' in ReadOnly mode. Access the first block and first DataArray. Print da.name, da.unit, da.label, and da.shape. Then print the first 5 values with da[:5].
Solution
with nixio.File.open('recording.nix', nixio.FileMode.ReadOnly) as nf:
blk = nf.blocks[0]
da = blk.data_arrays[0]
print(da.name, da.unit, da.label, da.shape)
print(da[:5])voltage mV membrane voltage (1000,)
[0. 0.31410759 0.6279052 0.94108313 1.25333234]Exercise: Reopen 'recording.nix' in ReadOnly mode. Read da.dimensions[0].dimension_type and da.dimensions[0].sampling_interval for the voltage DataArray. How does the sampling interval relate to the dt used when creating the signal?
Solution
with nixio.File.open('recording.nix', nixio.FileMode.ReadOnly) as nf:
da = nf.blocks[0].data_arrays[0]
print(da.dimensions[0].dimension_type)
print(da.dimensions[0].sampling_interval)DimensionType.Sample
0.001Exercise: Reopen 'recording.nix' in ReadWrite mode. Create a second DataArray named 'eeg' from the 2D EEG-like array defined in the cell below (shape (1000, 3), unit 'uV', label 'EEG signal'). Attach a SampledDimension (1 ms interval, unit 's', label 'time') for the first axis and a SetDimension with labels=['Fz', 'Cz', 'Pz'] for the second. Print da2.shape.
Solution
channels = ['Fz', 'Cz', 'Pz']
eeg_array = np.random.default_rng(0).standard_normal((1000, 3)) * 10
with nixio.File.open('recording.nix', nixio.FileMode.ReadWrite) as nf:
blk = nf.blocks[0]
da2 = blk.create_data_array(
'eeg', 'nix.sampled',
data=eeg_array, unit='uV', label='EEG signal'
)
da2.append_sampled_dimension(0.001, unit='s', label='time')
da2.append_set_dimension(labels=channels)
print(da2.shape)(1000, 3)Exercise: Reopen 'recording.nix' in ReadOnly mode. Access the second DataArray (blk.data_arrays[1]). Print the dimension_type and sampling_interval of the first dimension, and the dimension_type and labels of the second dimension. Confirm the channel names survived the round trip.
Solution
with nixio.File.open('recording.nix', nixio.FileMode.ReadOnly) as nf:
da2 = nf.blocks[0].data_arrays[1]
print('dim 0 type:', da2.dimensions[0].dimension_type)
print('dim 0 interval:', da2.dimensions[0].sampling_interval)
print('dim 1 type:', da2.dimensions[1].dimension_type)
print('dim 1 labels:', da2.dimensions[1].labels)dim 0 type: DimensionType.Sample
dim 0 interval: 0.001
dim 1 type: DimensionType.Set
dim 1 labels: ('Fz', 'Cz', 'Pz')Exercise: Open data/dataset.nix' in ReadOnly mode with raw nixio. Access the first block with nf.blocks[0]. Print the block’s name, the total number of DataArrays, and the number of MultiTags. Then print the name of blk.data_arrays[0]. What naming scheme does NixIO use?
Solution
with nixio.File.open('dataset.nix', nixio.FileMode.ReadOnly) as nf:
blk = nf.blocks[0]
print('Block name:', blk.name)
print('DataArrays:', len(blk.data_arrays))
print('MultiTags: ', len(blk.multi_tags))
print('First DataArray name:', blk.data_arrays[0].name)Block name: neo.block.2a1dd887fea94ef08644f82912592501
DataArrays: 111
MultiTags: 9
First DataArray name: neo.analogsignal.ca67602f93a84f96bbf1251185bb5508.0NixIO uses UUID-based names (neo.analogsignal.<hash>.<channel_index>) and stores one DataArray per channel. This internal convention is designed for round-tripping through Neo — it is not meant to be navigated directly with nixio. When you create a NIX file for your own results, you can choose clean, descriptive names and store multi-channel data as a single 2-D DataArray with a SetDimension for the channel axis.
The cells below rebuild the trial_segments and mean_lfp variables used in the previous section, so this section can be run independently.
# reload block
with neo.NixIO('dataset.nix', mode='ro') as io:
block = io.read_block()
segment = block.segments[0]
# rebuild trial cuts (correct events, -100 ms to +500 ms window)
events = segment.events[0]
correct_mask = events.array_annotations['performance_in_trial_str'] == 'correct_trial'
correct_ev = events[correct_mask]
epoch = add_epoch(segment, event1=correct_ev,
pre=-100*pq.ms, post=500*pq.ms,
attach_result=False, name='analysis_epochs')
trial_segments = cut_segment_by_epoch(segment, epoch, reset_time=True)
# compute trial-averaged LFP (channel 0)
lfp_trials = [seg.analogsignals[0][:, 0].magnitude for seg in trial_segments]
mean_lfp = np.mean(lfp_trials, axis=0)
n_trials = len(trial_segments)
dt_s = float(trial_segments[0].analogsignals[0].sampling_period
.rescale('s').magnitude)
print(f'mean_lfp shape: {mean_lfp.shape}, dt={dt_s} s, n_trials={n_trials}')mean_lfp shape: (600, 1), dt=0.001 s, n_trials=2Exercise: Create a file 'analysis_results.nix' in Overwrite mode. Inside it, create a block named 'analysis'. Store mean_lfp as a DataArray named 'mean LFP' with unit='uV' and label='LFP channel 0'. Append a SampledDimension using dt_s as the sampling interval (unit 's', label 'time'). Print da.shape.
Solution
with nixio.File.open('analysis_results.nix', nixio.FileMode.Overwrite) as nf:
blk = nf.create_block('analysis', 'nix.session')
da = blk.create_data_array(
'mean LFP', 'nix.sampled',
data=mean_lfp, unit='uV', label='LFP channel 0'
)
da.append_sampled_dimension(dt_s, unit='s', label='time')
print(da.shape)(600, 1)Exercise: Reopen 'analysis_results.nix' in ReadWrite mode. Create a metadata Section named 'analysis info' with type 'nix.metadata'. Add three properties: 'analysis_type' ('trial-averaged LFP'), 'n_trials' (the integer n_trials), and 'source_file' ('data/dataset.nix'). Link the section to the DataArray via da.metadata = sec.
Solution
with nixio.File.open('analysis_results.nix', nixio.FileMode.ReadWrite) as nf:
blk = nf.blocks[0]
da = blk.data_arrays[0]
sec = nf.create_section('analysis info', 'nix.metadata')
sec.create_property('analysis_type', 'trial-averaged LFP')
sec.create_property('n_trials', n_trials)
sec.create_property('source_file', 'data/dataset.nix')
da.metadata = sec
print('Metadata linked.')Metadata linked.Exercise: Reopen 'analysis_results.nix' in ReadOnly mode. Read back the DataArray and print its name, shape, unit, and the sampling interval of its first dimension. Then iterate over da.metadata.props and print each property name and its first value.
Solution
with nixio.File.open('analysis_results.nix', nixio.FileMode.ReadOnly) as nf:
da = nf.blocks[0].data_arrays[0]
print(f'name={da.name!r} shape={da.shape} unit={da.unit!r}')
print(f'sampling interval: {da.dimensions[0].sampling_interval} s')
print('\nMetadata:')
for prop in da.metadata.props:
print(f' {prop.name}: {prop.values[0]}')name='mean LFP' shape=(600, 1) unit='uV'
sampling interval: 0.001 s
Metadata:
analysis_type: trial-averaged LFP
n_trials: 2
source_file: data/dataset.nix