Organizing Electrophysiology Data

Organizing Electrophysiology Data

Authors
Dr. Atle E. Rimehaug | Dr. Michael Denker

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, InRange

Generate 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.nix

Section 1: Organizing Data: 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 Segment holds 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, and segment.epochs.
  • A Block is the top-level container that holds everything; its block.segments list 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)
block
Block 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)
block
Block 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 s

Exercise: 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)
block1
Block 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)
block1
Block 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 s

Exercise: 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))
2

Section 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 AnalogSignal objects, 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-channel channel_names;
  • a set of SpikeTrain objects, each annotated as 'sua', 'mua', or 'noise';
  • an Event object 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()
block
Block 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 s

Exercise: How many segments does the block contain? Hint: use len.

Solution
len(block.segments)
1

Exercise: Assign the first segment to a variable segment. How many AnalogSignal objects does the segment contain?

Solution
segment = block.segments[0]
len(segment.analogsignals)
2

Exercise: 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 Hz

Exercise: 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 Hz

Exercise: How many SpikeTrain objects does the segment contain?

Solution
len(segment.spiketrains)
8

Exercise: 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)
4

Exercise: 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)
2

Exercise: 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)
2

The 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))
6

Exercise: 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
True

The 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]
3

Section 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 Event with a mask returns a new Event with just those time points.
  • Column slicing (sig[:, indices]) selects channels of an AnalogSignal.
  • 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_events
Event 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]))
2

Section 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_epoch builds an Epoch object 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 (pre and post). Leaving event2=None cuts a fixed window around single events rather than between pairs of events.
  • cut_segment_by_epoch uses such an epoch to slice a segment into a list of new segments, one per epoch. With reset_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')
epoch
Epoch 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)
2

Exercise: 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_mv
AnalogSignal 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 s

Exercise: 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_rate
array(100.) * Hz

Exercise: 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 2

Exercise: 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 File contains one or more Block objects, each representing a recording session or dataset. (Note: This is not a Neo Block!)
  • Each Block contains DataArray objects. A DataArray wraps a numpy array and stores its physical unit and a human-readable label.
  • Every axis of a DataArray is described by a dimension descriptor: a SampledDimension for regularly sampled axes (e.g. a time axis at 1 kHz), or a SetDimension for 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 nixio

Exercise: 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.001

Exercise: 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.0

NixIO 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=2

Exercise: 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