concord-core

The contract layer — containers and interfaces that every other package depends on

Role in the system concord-core defines the shared language. Every other package imports from it. It never imports from any sibling package. Its only dependency is numpy.

Package Structure

concord-core/
  src/concord_core/
    __init__.py       exports all containers, ABCs, and configure_logging
    containers.py     Recording, MetricResult, ParameterVector, ModelOutput
    abc.py            Metric ABC, Model ABC, MetricRequirements
    logging.py        shared logging setup

Recording

Recording is the primary data container. It wraps a numpy array of EEG signal along with all the metadata needed to interpret it.

dataclassRecording
FieldTypeDescription
datanp.ndarraySignal array, shape (n_channels, n_samples), dtype float64. Values are in Volts (MNE SI convention).
fsfloatSampling rate in Hz.
channel_nameslist[str]Channel labels, length n_channels. Matches axis 0 of data.
montagestr"monopolar" | "bipolar" | "car" — how the signal was referenced.
start_timefloatOffset in seconds from the beginning of the original recording. Default 0.0.
anode_nameslist[str] | NoneFor bipolar only: the positive contact of each channel pair.
cathode_nameslist[str] | NoneFor bipolar only: the negative contact of each channel pair.
eventslist[tuple[float, float, str]]List of (onset_s, duration_s, label) annotation tuples.
channel_metadatadict[str, dict] | NonePer-channel info keyed by channel name. Contains status, status_description, x, y, z from BIDS sidecar files.
subject_metadatadict | NoneRow from participants.tsv for this subject.
metadatadictArbitrary key-value pairs (e.g. source file path, ieeg.json content).

Properties

propertyRecording.n_channels → int

data.shape[0] — number of channels.

propertyRecording.n_samples → int

data.shape[1] — number of time samples.

propertyRecording.duration → float

n_samples / fs — total duration in seconds.

propertyRecording.times → np.ndarray

start_time + np.arange(n_samples) / fs — absolute time stamp for every sample in seconds.

channel_metadata structure

When loaded from a BIDS dataset, channel_metadata is a dict like:

{
  "SEEG1": {
    "status": "good",            # "good" | "bad" | None
    "status_description": "soz", # "soz" | "resected" | None | other
    "x": 12.3,                   # MNI coordinate (float) or None
    "y": -24.1,
    "z": 8.5,
    "type": "SEEG",
    ...                          # any other columns from channels.tsv
  },
  "SEEG2": { ... },
  ...
}
Immutability pattern Recording is a dataclass, not a frozen dataclass — technically mutable — but all functions in the codebase treat it as immutable: they return new Recording objects rather than modifying fields. This is a convention, not enforced by the type system.

MetricResult

MetricResult is the output of any metric computation. It stores the result array alongside enough metadata to fully interpret it.

dataclassMetricResult
FieldTypeDescription
datanp.ndarrayResult values. Shape depends on metric: scalar (), 1D (n_channels,), 2D (n_channels, n_features), or 3D (n_channels, n_windows, n_features).
metric_namestrHuman-readable name of the metric that produced this result.
paramsdictAll parameters used for computation (window_s, overlap, fmin, etc.) — for reproducibility.
channel_labelslist[str]Labels for axis 0 of data. Usually matches Recording.channel_names.
unitsstrPhysical units of data values (e.g. "V", "V^2/Hz", "V^2").
freq_axisnp.ndarray | NoneFrequency values in Hz for spectral results (axis 1).
time_axisnp.ndarray | NoneTime values in seconds for windowed results (axis 1).
metadatadictExtra info (e.g. band names for BandPower).

Shape conventions

Metric typedata.shapeAxes
Global scalar()
Per-channel scalar(n_channels,)axis 0 = channels
Spectral(n_channels, n_freqs)axis 0 = channels, axis 1 = freqs (see freq_axis)
Windowed(n_channels, n_windows)axis 0 = channels, axis 1 = time windows (see time_axis)
Per-channel multi-feature(n_channels, n_features)axis 0 = channels, axis 1 = features (e.g. Hjorth params)
Windowed multi-feature(n_channels, n_windows, n_features)full 3D

Metric ABC

Every metric in the system must subclass Metric and implement three things. See Abstract Base Classes for how this pattern works in Python.

ABCMetric

Abstract base class. Cannot be instantiated directly — only concrete subclasses can.

MemberTypeDescription
name@property strHuman-readable metric name, e.g. "line_length".
requires@property MetricRequirementsDeclares what the metric needs from the Recording before compute() is called.
compute(recording)@abstractmethodTakes a Recording, returns a MetricResult. Must be stateless.
dataclassMetricRequirements
FieldDefaultDescription
min_channels1Minimum number of channels required.
min_duration_s0.0Minimum recording duration in seconds.
min_fs_hz0.0Minimum sampling rate in Hz.
allowed_montagesNoneIf None, any montage is accepted. Otherwise, a list of allowed montage strings.

Implementing a Metric

from concord_core import Recording, MetricResult
from concord_core.abc import Metric, MetricRequirements
import numpy as np

class MyMetric(Metric):
    def __init__(self, window_s: float = 1.0):
        self.window_s = window_s      # only config, no data

    @property
    def name(self) -> str:
        return "my_metric"

    @property
    def requires(self) -> MetricRequirements:
        return MetricRequirements(
            min_duration_s=self.window_s,
            min_fs_hz=10.0,
        )

    def compute(self, recording: Recording) -> MetricResult:
        result = np.mean(recording.data, axis=1)   # shape (n_channels,)
        return MetricResult(
            data=result,
            metric_name=self.name,
            params={"window_s": self.window_s},
            channel_labels=list(recording.channel_names),
            units="V",
        )

ParameterVector

ParameterVector carries model parameter values alongside their names and bounds. It is the input to Model.simulate() and the search space for optimization in concord-fit.

dataclassParameterVector
FieldTypeDescription
nameslist[str]Parameter names, e.g. ["A", "B", "a", "b", "C", ...]
valuesnp.ndarray1D array of current parameter values.
lowernp.ndarray1D array of lower bounds.
uppernp.ndarray1D array of upper bounds.
unitslist[str] | NoneOptional units per parameter (e.g. "mV", "1/s").
metadatadictArbitrary extra info.

ModelOutput

ModelOutput is the output of a model simulation — the simulation-side analog of MetricResult. It contains the simulated signal plus all internal state variables.

dataclassModelOutput
FieldTypeDescription
datanp.ndarraySimulated signal, shape (n_nodes, n_samples). For single-node models, n_nodes = 1.
fsfloatOutput sampling rate in Hz.
state_variablesdict[str, np.ndarray]All internal state traces, keyed by name. Each value has shape (n_nodes, n_samples).
parametersParameterVectorThe exact parameters used for this simulation — for reproducibility.
node_labelslist[str]Labels for each node (axis 0 of data).
time_axisnp.ndarray | NoneTime stamps in seconds, shape (n_samples,).
metadatadictArbitrary extra info.

Model ABC

Every neural mass model must subclass Model and implement three things. This mirrors the Metric ABC pattern — see Abstract Base Classes for the general concept and Neural Mass Models for the domain background.

ABCModel

Abstract base class for neural mass models. Cannot be instantiated directly.

MemberTypeDescription
name@property strModel identifier, e.g. "jansen_rit".
default_parameters()@abstractmethodReturns a fresh ParameterVector with standard values and bounds.
simulate(parameters, duration_s, fs, seed)@abstractmethodRuns the simulation, returns ModelOutput.

Implementing a Model

from concord_core import Model, ModelOutput, ParameterVector
import numpy as np

class MyModel(Model):
    @property
    def name(self) -> str:
        return "my_model"

    def default_parameters(self) -> ParameterVector:
        return ParameterVector(
            names=["param_a", "param_b"],
            values=np.array([1.0, 2.0]),
            lower=np.array([0.0, 0.0]),
            upper=np.array([10.0, 10.0]),
        )

    def simulate(self, parameters, duration_s, fs, seed=None):
        # ... integrate ODEs, produce signal ...
        return ModelOutput(
            data=signal, fs=fs,
            state_variables={...},
            parameters=parameters,
            node_labels=["node_0"],
        )

Logging

logging.py configures the root logger named "Montage Concord". All packages use logging.getLogger("Montage Concord.{package_name}") to emit log messages that inherit this configuration.