Extracting features from SONATA Network simulations

This notebook shows how to extract features of a group of cells from a SONATA network, specifically focusing on a small portion of non-barrel primary somatosensory cortex circuit from juvenile rats, with the help of BlueCellulLab. For those interested in conducting a more in-depth analysis, the entire circuit dataset is accessible on Zenodo. For more details about the simulation and in-depth insights on the circuit, please refer to the Bluecellulab SONATA Network example and the related paper, respectively.

Note: The compiled mechanisms need to be provided before importing bluecellulab.

!nrnivmodl ./mechanisms
import json
from pathlib import Path

from matplotlib import pyplot as plt

from bluecellulab import CircuitSimulation
import efel

In this example, a small sub-circuit has been extracted from the sscx circuit. This sub-circuit specifically consists of a random selection of cells exhibiting delayed stuttering (dSTUT) etype.

The simulation_config specifies the types of input stimuli to be applied to the cells. In this case, we have selected a ‘relative_linear’ stimulus of 70 ms and set the stimulus current at a level equivalent to 100 percent of the cell’s threshold current.

simulation_config = Path("./") / "simulation_config.json"
with open(simulation_config) as f:
    simulation_config_dict = json.load(f)
print(json.dumps(simulation_config_dict, indent=4))
{
    "manifest": {
        "$OUTPUT_DIR": "."
    },
    "run": {
        "tstop": 100.0,
        "dt": 0.025,
        "random_seed": 1
    },
    "conditions": {
        "v_init": -65
    },
    "target_simulator": "NEURON",
    "network": "./O1/circuit_config.json",
    "node_set": "dSTUT_mini",
    "output": {
        "output_dir": "$OUTPUT_DIR/output_sonata",
        "spikes_file": "out.h5",
        "spikes_sort_order": "by_time"
    },
    "inputs": {
        "continuous_linear": {
            "input_type": "current_clamp",
            "module": "relative_linear",
            "delay": 20.0,
            "duration": 70.0,
            "percent_start": 100,
            "node_set": "dSTUT_mini"
        }
    },
    "reports": {
        "soma": {
            "cells": "dSTUT_mini",
            "variable_name": "v",
            "type": "compartment",
            "dt": 1.0,
            "start_time": 0.0,
            "end_time": 20.0,
            "sections": "soma",
            "compartments": "center"
        }
    }
}

We use BlueCellulab for simulating smaller scale circuits, in contrast to the larger-scale simulations conducted with Neurodamus.

simulation_config = Path("./") / "simulation_config.json"
with open(simulation_config) as f:
    simulation_config_dict = json.load(f)
sim = CircuitSimulation(simulation_config)
from bluepysnap import Simulation as snap_sim
snap_access = snap_sim(simulation_config)
import pandas as pd
from bluepysnap import Simulation as snap_sim
all_nodes = pd.concat([x[1] for x in snap_access.circuit.nodes.get()])
dstut_cells = all_nodes[all_nodes["etype"] == "dSTUT"].index.to_list()
sim.instantiate_gids(dstut_cells, add_stimuli=True)
t_stop = 100.0
sim.run(t_stop)

The plot displays the voltage traces simulated for each cell in our circuit.

plt.figure(figsize=(18, 12))
for cell_id in sim.cells:
    time = sim.get_time_trace()
    voltage = sim.get_voltage_trace(cell_id)
    plt.plot(time, voltage, label=cell_id)
    plt.xlabel("Time (ms)")
    plt.ylabel("Voltage (mV)")
_images/sonata-network_11_0.png

Let’s focus on 3 cells for better visualization

sim.cells = dict(list(sim.cells.items())[:3])
plt.figure(figsize=(18, 12))
for cell_id in sim.cells:
    time = sim.get_time_trace()
    voltage = sim.get_voltage_trace(cell_id)
    plt.plot(time, voltage, label=cell_id)
    plt.xlabel("Time (ms)")
    plt.ylabel("Voltage (mV)")
    plt.legend()
_images/sonata-network_13_0.png

We are now ready to extract features. First, we will build the data structure for eFEL

traces = []
for cell_id in sim.cells:
    voltage = sim.get_voltage_trace(cell_id)
    trace = {}
    trace['T'] = time
    trace['V'] = voltage
    trace['stim_start'] = [20]
    trace['stim_end'] = [90]
    traces.append(trace)

Next, we choose the specific features of interest

features = ['peak_time', 'AHP_time_from_peak', 'AP_height', 'AHP_depth_abs', 'all_ISI_values']

Finally, we perform the feature extraction

traces_results = efel.get_feature_values(traces, features)

The plot below shows action potential (AP) height and depth of those 3 cells

import pylab
for trace, trace_result, cell_id in zip(traces, traces_results, sim.cells):
    time = trace['T']
    voltage = trace['V']
    peak_times = trace_result['peak_time']
    ahp_time = trace_result['AHP_time_from_peak']
    ap_heights = trace_result['AP_height']
    AHP_depth_abss = trace_result['AHP_depth_abs']

    pylab.figure(figsize=(10, 6))
    pylab.title(cell_id)
    pylab.plot(time,voltage)
    pylab.plot(peak_times, ap_heights, 'o')
    pylab.plot(peak_times+ahp_time, AHP_depth_abss, 'o')
    pylab.xlabel('Time (ms)')
    pylab.ylabel('Vm (mV)')
    pylab.show()
_images/sonata-network_21_0.png _images/sonata-network_21_1.png _images/sonata-network_21_2.png

Now, let’s overlay the durations of the inter-spike intervals (ISIs) for a clearer visualization of the timing between spikes

for trace, trace_result, cell_id in zip(traces, traces_results, sim.cells):
    time = trace['T']
    voltage = trace['V']
    peak_times = trace_result['peak_time']
    ap_heights = trace_result['AP_height']

    all_isi_values = trace_result['all_ISI_values']

    pylab.figure(figsize=(10, 6))
    pylab.title(cell_id)
    pylab.plot(time, voltage, label='Voltage Trace')
    pylab.plot(peak_times, ap_heights, 'o', label='Spike Peaks')

    for i in range(len(peak_times) - 1):
        start_spike_time = peak_times[i]
        end_spike_time = peak_times[i + 1]
        duration = round(all_isi_values[i], 2)

        y_position = max(ap_heights[i], ap_heights[i + 1]) + 1

        # Check if it's the first ISI line to be drawn and add a label, otherwise draw without a label
        if i == 0:
            pylab.plot([start_spike_time, end_spike_time], [y_position, y_position], 'r-', lw=2, label='inter-spike-intervals (ISI)')
        else:
            pylab.plot([start_spike_time, end_spike_time], [y_position, y_position], 'r-', lw=2)

        # Adjust text position to be slightly lower
        midpoint = (start_spike_time + end_spike_time) / 2
        pylab.text(midpoint, y_position - 3, f'{duration} ms', verticalalignment='bottom', horizontalalignment='center', color='red')

    pylab.xlabel('Time (ms)')
    pylab.ylabel('Vm (mV)')
    pylab.legend(loc='lower right')
    pylab.show()
_images/sonata-network_23_0.png _images/sonata-network_23_1.png _images/sonata-network_23_2.png

Source

sonata-network.rst