ffTRF: frequency-domain temporal response function and deconvolution tools.
Project description
ffTRF
ffTRF is a Python toolbox for fitting temporal response functions (TRFs) and
related linear deconvolution models in the frequency domain. It is designed for
users that work with continuous stimulus-response data, for example speech
features and MEG/EEG, and want a workflow that feels familiar if they have used e.g.
mTRFpy or other lag-matrix TRF tools.
The main public API is centered on fftrf.TRF. It supports forward encoding
models, backward decoding models, cross-validated ridge regularization,
multi-trial data, optional segmented or multi-taper spectral estimation,
prediction and scoring, bootstrap intervals, permutation tests, diagnostics,
and plotting helpers.
Full documentation: weigla.github.io/ffTRF
The whole Toolbox and its API is designed to work similar to mTRFpy:
Bialas et al., (2023). mTRFpy: A Python package for temporal response function analysis. Journal of Open Source Software, 8(89), 5657, https://doi.org/10.21105/joss.05657
The workflow behind it is loosely based on previous work from the Maddox-Lab:
Tong Shan, Ross K. Maddox; Comparing methods for deriving the auditory brainstem response to continuous speech in human listeners. Imaging Neuroscience 2025; 3 IMAG.a.19. doi: https://doi.org/10.1162/IMAG.a.19
Ross K. Maddox, Adrian K. C. Lee; Auditory Brainstem Responses to Continuous Natural Speech in Human Listeners. eNeuro 31 January 2018, 5 (1) ENEURO.0441-17.2018; DOI: https://doi.org/10.1523/ENEURO.0441-17.2018
Why ffTRF?
Traditional time-domain TRF estimators build an explicit lagged design matrix: each predictor is copied once per requested lag. That representation is direct and interpretable, but it can become large when recordings are long, sampling rates are high, lag windows are wide, or the predictor side has many channels.
ffTRF estimates the same class of linear stimulus–response models as conventional time-domain mTRF approaches, but does so from frequency-domain sufficient statistics. Instead of forming an explicit lagged design matrix, the model is fitted from auto- and cross-spectral estimates of the stimulus and response.
The main point here is that different spectral estimators emphasize different goals. If you are looking for the closest frequency-domain analogue of a standard finite-lag mTRF fit, whole-trial spectra are the way to go. For "real world data" (and especially in backward-models), segmented or windowed spectra are often preferable: by averaging spectral statistics across shorter segments, they can improve robustness to noise and nonstationarity, and in practice may produce kernels with higher predictive accuracy than conventional time-domain TRF estimates.
Installation
For a released package:
pip install fftrf
or if you use Pixi:
pixi add --pypi fftrf
alternatively you can point directly to this repo in your pixi.tomlfile:
[pypi-dependencies]
ffTRF = { git = "https://github.com/weigla/ffTRF.git"}
Then run pixi install. If you want to pin a specific revision, add
rev = "<commit>" to the dependency entry.
Quick Start
ffTRF uses time as the first axis. A single trial is a NumPy array with shape
(n_samples, n_features) or (n_samples, n_outputs). Multiple trials are
passed as lists of arrays.
import numpy as np
from fftrf import TRF
# Example shapes:
# stimulus_train: list of (n_samples, n_features) arrays
# response_train: list of (n_samples, n_channels) arrays
# stimulus_test: list of held-out stimulus trials
# response_test: list of held-out response trials
model = TRF(direction=1, metric="pearsonr")
cv_scores = model.train(
stimulus=stimulus_train,
response=response_train,
fs=128,
tmin=0.0,
tmax=0.4,
regularization=np.logspace(-4, 4, 17),
k=5,
seed=7,
)
predicted_response, heldout_r = model.predict(
stimulus=stimulus_test,
response=response_test,
average=False,
)
fig, ax = model.plot(input_index=0, output_index=0)
For a backward decoder, use TRF(direction=-1). As in mTRF, backward fitting
reverses the requested lag samples: a user-facing request such as
tmin=0.0, tmax=0.4 stores physical decoder lags from -0.4 ending at zero in
model.times.
Under the Hood
Instead of constructing an explicit lag matrix, ffTRF:
- estimates predictor auto-spectra and predictor-target cross-spectra,
- solves a ridge-regularized transfer function at each frequency, and
- converts the transfer function into a lag-domain impulse response over the
requested
tmin, tmax)interval.
By default, each trial is treated as one FFT segment. That is the closest setting to a standard mTRF-style finite-lag comparison:
model.train(..., segment_length=None, window=None)
For noisy continuous data (real world data), it is often useful to estimate spectra from shorter overlapping segments:
model.train(
...,
segment_duration=2.0,
overlap=0.5,
window="hann",
)
With multiple regularization candidates, ffTRF caches per-trial spectra so
cross-validation can reuse the FFT work across folds and lambda values. Direct
single-lambda fits use a lower-memory aggregate spectral path.
Core Conventions
- Time is always axis 0.
- A single trial can be 1D or 2D.
- Multiple trials are represented as a list of arrays.
TRF(direction=1)fits stimulus -> response.TRF(direction=-1)fits response -> stimulus.- Stored lag-domain weights have shape
(n_inputs, n_lags, n_outputs). - The lag interval is sample based and half-open:
[tmin, tmax).
When ffTRF Saves Computation
ffTRF avoids explicitly constructing the time-lagged predictor matrix. This can substantially reduce peak memory and cross-validation time when the predictor-lag dimension or regularization grid is large. It is not a universal speed win: a small fixed-ridge model can still be faster in a time-domain solver.
The benchmark ratios below are mTRF / ffTRF. Values above 1 favor ffTRF;
values below 1 favor mTRF. Fit time excludes imports, data generation/loading,
prediction, and plotting. Each method runs in a fresh process with one native
BLAS/OpenMP thread.
Synthetic Crossover Scenarios
| Workload | Shape and fit | Runtime ratio (mTRF / ffTRF) | Peak RSS ratio (mTRF / ffTRF) | Correctness check |
|---|---|---|---|---|
| Moderate length | 1->1, fixed | 0.36× | 1.31× | held-out r 0.9990 / 0.9990 |
| Longer lag window | 1->1, fixed | 3.13× | 1.00× | held-out r 0.9989 / 0.9989 |
| Cross-validated ridge | 1->1, cv-8 (k=4) | 9.65× | 1.00× | held-out r 0.9989 / 0.9990 |
| 102-channel backward decoder | 102->1, fixed | 20.44× | 1.80× | held-out r 0.9711 / 0.8695 |
Ratios above 1 favor ffTRF; ratios below 1 favor mTRF. The small fixed-ridge row is included deliberately: ffTRF is not universally faster. Savings emerge as lag count, CV work, or predictor dimension makes explicit lag-matrix construction expensive.
The full synthetic benchmark report includes ten workloads, repeated-run ranges, total and additional peak RSS, held-out prediction, kernel agreement, raw dimensions, and complete environment metadata.
Real Speech-EEG Case Study
The real-data benchmark uses seven training/CV and three held-out segments from the pinned public mTRF speech-EEG sample. It reports both the closest whole-trial solver comparison and a practical ffTRF workflow using 2-second Hann windows with 50% overlap.
| Comparison | Direction | Runtime ratio (mTRF / ffTRF) | Peak RSS ratio (mTRF / ffTRF) | Held-out r (ffTRF / mTRF) |
|---|---|---|---|---|
| Matched whole-trial | Forward | 1.03× | 0.42× | 0.0296 / 0.0200 |
| Matched whole-trial | Backward | 63.75× | 1.17× | 0.0469 / 0.1109 |
| Practical 2 s Hann | Forward | 2.16× | 1.23× | 0.0367 / 0.0200 |
| Practical 2 s Hann | Backward | 207.69× | 5.81× | 0.1954 / 0.1109 |
Ratios above 1 favor ffTRF. Matched rows compare the closest available solver settings. Practical rows use 2-second Hann-windowed spectra in ffTRF and therefore compare workflows rather than identical estimators.
Held-out correlation is included as a prediction check, not as evidence of ground-truth kernel accuracy. The practical rows change ffTRF's spectral estimator and must not be interpreted as strict solver-equivalence results. See the full real EEG benchmark report for the protocol and repeated measurements.
Reproduce both reports and synchronize these generated README tables:
pixi run -e compare benchmark-demo
pixi run -e compare real-eeg-benchmark
The Markdown reports are accompanied by raw JSON measurements under
artifacts/. Runtime depends on hardware and system load; the reports record
the source revision, package versions, platform, CPU, thread limit, and—for the
real-data benchmark—the pinned dataset commit and SHA-256.
License
ffTRF is distributed under the BSD 3-Clause License.
AI usage disclosure
This project uses AI-assisted development tools, including OpenAI Codex and OpenAI language models from version 4 onward through GPT-5.5. These tools were used to assist with generating code, composing tests, improving documentation, and reviewing code.
All AI-generated or AI-assisted contributions were reviewed, validated, and - if necessary - edited by the project author before inclusion. The author remains responsible for the correctness, design decisions, and maintenance of the codebase.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fftrf-0.1.0.tar.gz.
File metadata
- Download URL: fftrf-0.1.0.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79021d3cabb7037a3f80b0978fd914689da6dd4f9d063a4f4da1bfc407970df2
|
|
| MD5 |
c90182ba9d682f1d8037e89f0764017f
|
|
| BLAKE2b-256 |
de5f7e18421b5004396f82d3500b985e02f3581cabe195ad9cfb6ddcb469c17b
|
Provenance
The following attestation bundles were made for fftrf-0.1.0.tar.gz:
Publisher:
publish-pypi.yml on weigla/ffTRF
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fftrf-0.1.0.tar.gz -
Subject digest:
79021d3cabb7037a3f80b0978fd914689da6dd4f9d063a4f4da1bfc407970df2 - Sigstore transparency entry: 2280844539
- Sigstore integration time:
-
Permalink:
weigla/ffTRF@cce3fd9d1ed4d1ba29b7576f82c39e392413be34 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/weigla
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@cce3fd9d1ed4d1ba29b7576f82c39e392413be34 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fftrf-0.1.0-py3-none-any.whl.
File metadata
- Download URL: fftrf-0.1.0-py3-none-any.whl
- Upload date:
- Size: 56.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96a95603fe4bf99b7f34dee5dc36209aa0d380bef33dcb9f31c7c5baf4955498
|
|
| MD5 |
c88da4625bd3f682f2131b16d6c9e574
|
|
| BLAKE2b-256 |
d7e8d423cc9d3e86d7012b91a12533cf4cd93eca6c42aced60aba68532e487cd
|
Provenance
The following attestation bundles were made for fftrf-0.1.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on weigla/ffTRF
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fftrf-0.1.0-py3-none-any.whl -
Subject digest:
96a95603fe4bf99b7f34dee5dc36209aa0d380bef33dcb9f31c7c5baf4955498 - Sigstore transparency entry: 2280844562
- Sigstore integration time:
-
Permalink:
weigla/ffTRF@cce3fd9d1ed4d1ba29b7576f82c39e392413be34 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/weigla
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@cce3fd9d1ed4d1ba29b7576f82c39e392413be34 -
Trigger Event:
release
-
Statement type: