concord-core
The contract layer — containers and interfaces that every other package depends on
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.
| Field | Type | Description |
|---|---|---|
| data | np.ndarray | Signal array, shape (n_channels, n_samples), dtype float64. Values are in Volts (MNE SI convention). |
| fs | float | Sampling rate in Hz. |
| channel_names | list[str] | Channel labels, length n_channels. Matches axis 0 of data. |
| montage | str | "monopolar" | "bipolar" | "car" — how the signal was referenced. |
| start_time | float | Offset in seconds from the beginning of the original recording. Default 0.0. |
| anode_names | list[str] | None | For bipolar only: the positive contact of each channel pair. |
| cathode_names | list[str] | None | For bipolar only: the negative contact of each channel pair. |
| events | list[tuple[float, float, str]] | List of (onset_s, duration_s, label) annotation tuples. |
| channel_metadata | dict[str, dict] | None | Per-channel info keyed by channel name. Contains status, status_description, x, y, z from BIDS sidecar files. |
| subject_metadata | dict | None | Row from participants.tsv for this subject. |
| metadata | dict | Arbitrary key-value pairs (e.g. source file path, ieeg.json content). |
Properties
data.shape[0] — number of channels.
data.shape[1] — number of time samples.
n_samples / fs — total duration in seconds.
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": { ... },
...
}
MetricResult
MetricResult is the output of any metric computation.
It stores the result array alongside enough metadata to fully interpret it.
| Field | Type | Description |
|---|---|---|
| data | np.ndarray | Result values. Shape depends on metric: scalar (), 1D (n_channels,), 2D (n_channels, n_features), or 3D (n_channels, n_windows, n_features). |
| metric_name | str | Human-readable name of the metric that produced this result. |
| params | dict | All parameters used for computation (window_s, overlap, fmin, etc.) — for reproducibility. |
| channel_labels | list[str] | Labels for axis 0 of data. Usually matches Recording.channel_names. |
| units | str | Physical units of data values (e.g. "V", "V^2/Hz", "V^2"). |
| freq_axis | np.ndarray | None | Frequency values in Hz for spectral results (axis 1). |
| time_axis | np.ndarray | None | Time values in seconds for windowed results (axis 1). |
| metadata | dict | Extra info (e.g. band names for BandPower). |
Shape conventions
| Metric type | data.shape | Axes |
|---|---|---|
| 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.
Abstract base class. Cannot be instantiated directly — only concrete subclasses can.
| Member | Type | Description |
|---|---|---|
| name | @property str | Human-readable metric name, e.g. "line_length". |
| requires | @property MetricRequirements | Declares what the metric needs from the Recording before compute() is called. |
| compute(recording) | @abstractmethod | Takes a Recording, returns a MetricResult. Must be stateless. |
| Field | Default | Description |
|---|---|---|
| min_channels | 1 | Minimum number of channels required. |
| min_duration_s | 0.0 | Minimum recording duration in seconds. |
| min_fs_hz | 0.0 | Minimum sampling rate in Hz. |
| allowed_montages | None | If 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.
| Field | Type | Description |
|---|---|---|
| names | list[str] | Parameter names, e.g. ["A", "B", "a", "b", "C", ...] |
| values | np.ndarray | 1D array of current parameter values. |
| lower | np.ndarray | 1D array of lower bounds. |
| upper | np.ndarray | 1D array of upper bounds. |
| units | list[str] | None | Optional units per parameter (e.g. "mV", "1/s"). |
| metadata | dict | Arbitrary 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.
| Field | Type | Description |
|---|---|---|
| data | np.ndarray | Simulated signal, shape (n_nodes, n_samples). For single-node models, n_nodes = 1. |
| fs | float | Output sampling rate in Hz. |
| state_variables | dict[str, np.ndarray] | All internal state traces, keyed by name. Each value has shape (n_nodes, n_samples). |
| parameters | ParameterVector | The exact parameters used for this simulation — for reproducibility. |
| node_labels | list[str] | Labels for each node (axis 0 of data). |
| time_axis | np.ndarray | None | Time stamps in seconds, shape (n_samples,). |
| metadata | dict | Arbitrary 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.
Abstract base class for neural mass models. Cannot be instantiated directly.
| Member | Type | Description |
|---|---|---|
| name | @property str | Model identifier, e.g. "jansen_rit". |
| default_parameters() | @abstractmethod | Returns a fresh ParameterVector with standard values and bounds. |
| simulate(parameters, duration_s, fs, seed) | @abstractmethod | Runs 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.