Skip to content

Working with Neo Block Data Structures

MyoGen uses Neo Block objects as the primary data containers for storing simulation results. This guide explains how to extract and work with data from the different Block types.

Overview

MyoGen uses five different Neo Block types to store simulation data:

Block Type Purpose Dimensions
SPIKE_TRAIN__Block Neural firing times 1D spike times
SURFACE_MUAP__Block Surface MUAP templates 3D electrode grid
SURFACE_EMG__Block Surface EMG signals 3D electrode grid
INTRAMUSCULAR_MUAP__Block Intramuscular MUAP templates 2D electrode array
INTRAMUSCULAR_EMG__Block Intramuscular EMG signals 2D electrode array

Neo Blocks provide standardized containers with:

  • Automatic unit tracking (mV, µV, seconds, etc.)
  • Metadata storage (sampling rate, duration, etc.)
  • Compatibility with analysis tools like Elephant
  • Integration with neuroscience workflows

Quick Start

Basic Access Patterns

import joblib

# Load spike trains
spike_trains = joblib.load("spike_trains.pkl")
spiketrain = spike_trains.segments[0].spiketrains[0]
spike_times = spiketrain.magnitude  # NumPy array

# Load surface EMG
surface_emg = joblib.load("surface_emg.pkl")
emg_signal = surface_emg.groups[0].segments[0].analogsignals[0]
electrode_data = emg_signal[:, row, col]  # Time series at electrode

# Load intramuscular EMG
im_emg = joblib.load("intramuscular_emg.pkl")
emg_signal = im_emg.segments[0].analogsignals[0]
electrode_data = emg_signal[:, electrode_idx]  # Time series

Block Structure Details

SPIKE_TRAIN__Block

Structure: Block → Segments (motor pools) → SpikeTrain objects

The SPIKE_TRAIN__Block stores neural firing patterns from motor neuron pools. Each segment represents a motor pool, and each spiketrain contains the firing times of an individual neuron.

# Access spike trains
motor_pool = spike_train__Block.segments[pool_idx]
spiketrain = motor_pool.spiketrains[neuron_idx]

# Extract data
spike_times__s = spiketrain.magnitude  # NumPy array
n_spikes = len(spiketrain)
duration = spiketrain.t_stop
sampling_rate = spiketrain.sampling_rate

Example: Calculate firing rate

motor_pool = spike_train__Block.segments[0]
firing_rates = []

for spiketrain in motor_pool.spiketrains:
    if len(spiketrain) > 0:
        rate = (len(spiketrain) / (spiketrain.t_stop - spiketrain.t_start)).rescale("Hz")
        firing_rates.append(rate.magnitude)

print(f"Mean firing rate: {np.mean(firing_rates):.2f} Hz")

SURFACE_MUAP__Block

Structure: Block → Groups (electrode arrays) → Segments (MUAP indices) → AnalogSignals (3D)

Stores Motor Unit Action Potential (MUAP) templates for surface electrode arrays. Each group represents an electrode array, each segment represents a MUAP, and signals are 3D arrays (time × rows × columns).

# Access MUAPs
electrode_array = muaps__Block.groups[array_idx]
muap_segment = electrode_array.segments[muap_idx]
muap_signal = muap_segment.analogsignals[0]

# Extract data
muap_array = muap_signal.magnitude  # Shape: (time, rows, cols)
time__s = muap_signal.times.magnitude
electrode_muap = muap_signal[:, row, col]  # Specific electrode

Example: Analyze MUAP amplitude

# Get MUAP from motor unit 5, electrode at row 2, column 3
muap_signal = muaps__Block.groups[0].segments[5].analogsignals[0]

row, col = 2, 3
electrode_muap = muap_signal[:, row, col]

print(f"Peak-to-peak: {np.ptp(electrode_muap.magnitude):.2f} {electrode_muap.units}")
print(f"Max amplitude: {np.max(np.abs(electrode_muap.magnitude)):.2f} {electrode_muap.units}")

SURFACE_EMG__Block

Structure: Block → Groups (electrode arrays) → Segments (motor pools) → AnalogSignals (3D)

Stores synthesized surface EMG signals. Similar structure to SURFACE_MUAP__Block, but contains the full EMG signal (longer duration) rather than templates.

# Access surface EMG
electrode_array = surface_emg__Block.groups[array_idx]
motor_pool = electrode_array.segments[pool_idx]
emg_signal = motor_pool.analogsignals[0]

# Extract data
emg_array = emg_signal.magnitude  # Shape: (time, rows, cols)
electrode_emg = emg_signal[:, row, col]  # Specific electrode

Example: Calculate RMS amplitude

emg_signal = surface_emg__Block.groups[0].segments[0].analogsignals[0]
electrode_emg = emg_signal[:, 2, 2].magnitude

rms = np.sqrt(np.mean(electrode_emg ** 2))
print(f"RMS amplitude: {rms:.2f} {emg_signal.units}")

INTRAMUSCULAR_MUAP__Block

Structure: Block → Segments (MUAP indices) → AnalogSignals (2D)

Stores MUAP templates for intramuscular electrodes. Each segment represents a MUAP, and signals are 2D arrays (time × electrodes).

# Access intramuscular MUAPs
muap_segment = im_muaps__Block.segments[muap_idx]
muap_signal = muap_segment.analogsignals[0]

# Extract data
muap_array = muap_signal.magnitude  # Shape: (time, electrodes)
electrode_muap = muap_signal[:, electrode_idx]

INTRAMUSCULAR_EMG__Block

Structure: Block → Segments (motor pools) → AnalogSignals (2D)

Stores synthesized intramuscular EMG signals. Each segment represents a motor pool, and signals are 2D arrays (time × electrodes).

# Access intramuscular EMG
motor_pool = im_emg__Block.segments[pool_idx]
emg_signal = motor_pool.analogsignals[0]

# Extract data
emg_array = emg_signal.magnitude  # Shape: (time, electrodes)
electrode_emg = emg_signal[:, electrode_idx]

Common Operations

Accessing Metadata

All Neo signals have metadata properties:

signal.magnitude          # NumPy array of values
signal.times.magnitude    # Time axis (in seconds)
signal.sampling_rate      # Sampling frequency
signal.sampling_period    # Time between samples
signal.t_start           # Start time
signal.t_stop            # Stop time
signal.duration          # Total duration
signal.units             # Physical units (mV, uV, etc.)
signal.shape             # Array dimensions

Extracting Time Windows

# For spike trains
windowed_train = spiketrain.time_slice(t_start, t_stop)

# For analog signals
start_idx = int(t_start * emg_signal.sampling_rate.magnitude)
end_idx = int(t_stop * emg_signal.sampling_rate.magnitude)
windowed_signal = emg_signal[start_idx:end_idx]

Converting Units

import quantities as pq

# Check current units
print(signal.units)  # e.g., mV

# Convert to different units
signal_uV = signal.rescale(pq.uV)

Saving and Loading

import joblib

# Save blocks
joblib.dump(spike_train__Block, "spike_trains.pkl")
joblib.dump(surface_emg__Block, "surface_emg.pkl")

# Load blocks
spike_trains = joblib.load("spike_trains.pkl")
surface_emg = joblib.load("surface_emg.pkl")

Shape Reference

Block Type Dimensionality Shape Format
SPIKE_TRAIN 1D (n_spikes,)
SURFACE_MUAP 3D (time, rows, cols)
SURFACE_EMG 3D (time, rows, cols)
INTRAMUSCULAR_MUAP 2D (time, electrodes)
INTRAMUSCULAR_EMG 2D (time, electrodes)

Data Flow Pipeline

The simulation pipeline follows this flow:

  1. Generate Spike TrainsSPIKE_TRAIN__Block

  2. When neurons fire

  3. Generate MUAPsSURFACE_MUAP__Block or INTRAMUSCULAR_MUAP__Block

  4. What individual motor units look like

  5. Convolve MUAPs with Spike TrainsSURFACE_EMG__Block or INTRAMUSCULAR_EMG__Block

  6. Final synthesized EMG signals

Complete Example

import joblib
import numpy as np
import matplotlib.pyplot as plt
from myogen.utils.types import SPIKE_TRAIN__Block, SURFACE_EMG__Block

# 1. Load data
spike_trains = joblib.load("spike_trains.pkl")
surface_emg = joblib.load("surface_emg.pkl")

# 2. Extract spike times
motor_pool = spike_trains.segments[0]
neuron_5 = motor_pool.spiketrains[5]
spike_times = neuron_5.magnitude

# 3. Extract EMG from center electrode
emg_signal = surface_emg.groups[0].segments[0].analogsignals[0]
center_row = emg_signal.shape[1] // 2
center_col = emg_signal.shape[2] // 2
electrode_emg = emg_signal[:, center_row, center_col]

# 4. Plot
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)

# Spike raster
ax1.scatter(spike_times, [0]*len(spike_times), s=10, c='black')
ax1.set_ylabel('Neuron 5')
ax1.set_ylim(-0.5, 0.5)

# EMG signal
time = emg_signal.times.magnitude
ax2.plot(time, electrode_emg.magnitude)
ax2.set_xlabel('Time (s)')
ax2.set_ylabel(f'EMG ({electrode_emg.units})')

plt.tight_layout()
plt.show()

Troubleshooting

Missing 'groups' attribute

Issue: AttributeError: 'Block' object has no attribute 'groups'

Solution: This is likely a spike train or intramuscular block. Those use segments not groups.

IndexError when accessing electrodes

Issue: IndexError when accessing electrodes

Solution: Check the shape first:

print(signal.shape)  # e.g., (150000, 13, 5)
# Then access within bounds: signal[:, 0:13, 0:5]

Wrong units

Issue: Values seem wrong (very large or very small)

Solution: Check the units:

print(signal.units)  # e.g., mV or µV
signal.rescale(pq.mV)  # Convert to desired unit

Additional Resources