Skip to content

Decoding

mesoscopy process decode tests whether the activity of a region, or of all regions together, tells which stimulus the animal saw on a trial, or whether it pushed the lever. It works on the per-trial windows written by process peri-event, the same _perievent.csv that process metrics and process connectivity take, and needs the sdt_type trials column of a go/no-go session.

mesoscopy process decode /path/to/recording_smoothed_regions_event-cueonset_perievent.csv

A peri-event file written from a _regions.csv already carries the trials columns. If yours does not, pass the trials CSV with --trials.

Output

The command writes <stem>_label-stim_decoding.csv with one row per decoder, region and trial group. The decoders are a logistic regression and a linear discriminant, each fitted to one region at a time and to all regions together, which appear as the region all.

Column Meaning
decoder logistic or lda.
label What is decoded, stim or response.
region The region, or all for the population decoder.
group The trial group the decoder is fitted within.
accuracy, accuracy_sd The fraction of held-out trials classified correctly, as the mean and SD over the folds.
balanced_accuracy, balanced_accuracy_sd The mean of the two per-class accuracies, so a majority class cannot inflate it.
d_prime The sensitivity index from the hit and false alarm rates of the decoder.
auroc The area under the ROC curve of the decision values over all trials.
f2 The F-beta score with beta 2, weighting recall over precision.
tp, fn, fp, tn The confusion counts over all trials, averaged over repeats.
shuffle_accuracy, shuffle_accuracy_sd The accuracy of the same decoder on shuffled labels, as the mean and SD over the shuffles.
shuffle_balanced_accuracy, shuffle_balanced_accuracy_sd The balanced accuracy on shuffled labels.
shuffle_balanced_accuracy_95 The 95th percentile of the shuffled balanced accuracy, the level a real score has to beat.
p_value The fraction of shuffles whose balanced accuracy reaches the observed one, with one added to both counts.
n_trials, n_class_a, n_class_b The trials used, and how many of them are in the positive and the negative class.

The positive class is the go stimulus or the lever push. A positive d_prime means the decoder picks it out and a positive population weight means higher activity favours it. The weights of the population decoder go to <stem>_label-stim_decoding-weights.csv, one row per decoder, group and region, with the mean and SD over the fits.

Groups where either class has fewer than --min-trials trials (default 10) keep their counts and are otherwise empty. With --shuffles 0 the shuffle columns are left out.

Labels and groups

--label stim, the default, decodes the go against the no-go stimulus. --label response decodes the lever push against no push. Both come from sdt_type.

Each decoder is fitted over all trials and within the groups that hold the other variable constant. For the stimulus these are all, resp-push (hits against false alarms) and resp-nopush (misses against correct rejections), so a decoder that still separates the stimuli among pushed trials is not reading the movement. For the response they are all, stim-go (hits against misses) and stim-nogo (false alarms against correct rejections).

Epoch

The feature of each trial is each region's mean ∆F/F over the epoch. The epoch runs from the start of the response window to the median response time of the trials that responded, so every trial has the same epoch. --response START END bounds it (default all post-event samples), --response-pad extends it past the response by a fixed time (default 0 s), and trials with a response time below --min-rt (default 0.2 s) are left out. Pass --no-mask-response to use the whole response window instead.

--per-trial-end ends each trial's epoch at its own response, as the connectivity epoch does. Trials that did not respond then end at the median while the others spread around it, which lets the response label be decoded from the epoch length alone, so leave it off unless you need the per-trial epoch.

Validation

Every decoder is scored by stratified k-fold cross-validation with --folds folds (default 5), repeated --repeats times (default 10) with a fresh split each time. Features are standardised within each training fold. The logistic regression weights the classes to balance them, and the linear discriminant uses equal priors with automatic shrinkage. Every region is scored on the same splits, so the regions compare on the same held-out trials.

Chance level

The chance level comes from fitting the same decoders to --shuffles (default 200) permutations of the labels, each under one k-fold. Raw accuracy on shuffled labels sits at the share of the majority class, balanced accuracy near 0.5, so the two together show whether a decoder beats chance or only the class imbalance. p_value is one-sided on balanced accuracy and cannot fall below one over the number of shuffles plus one. Pass --shuffles 0 to skip the shuffles and leave the columns out.

Rolling

Pass --with-rolling to follow decoding through the trial. The command then also writes <stem>_label-stim_decoding-rolling.csv, with the same scores taken in windows that slide over the whole peri-event window.

mesoscopy process decode /path/to/recording_smoothed_regions_event-cueonset_perievent.csv --with-rolling

Each window's feature is the region mean over its samples, so the epoch options do not apply. --rolling-window (default 0.5 s) sets the window length and --rolling-step (default 0.1 s) the time between windows, both rounded to whole samples. At 25 Hz the defaults give windows of 0.48 s every 0.08 s. The table has one row per decoder, region, group and window, and time is the window centre relative to the event.

The chance level is taken once per decoder, region and group on the --baseline window (default every sample before the event), since shuffled labels give the same chance level in every window. The shuffle columns therefore repeat along a time course and p_value is read against them in every window. The rolling table is the slowest part of the command, since the population decoder is fitted in every window.

Reading the table

balanced_accuracy is the headline score. A region whose score sits above shuffle_balanced_accuracy_95 carries information about the label. auroc ranks the decision values rather than thresholding them, so it is unmoved by the class imbalance and by the choice of threshold.

The population decoder usually beats every single region, since regions carry partly independent information. Its weights show which regions it leans on, in units of a standard deviation of each region's feature.

A region that decodes the stimulus before the cue in the rolling table reads the structure of the session rather than the stimulus. Correction trials repeat the stimulus after an error, so the coming stimulus can be predicted from the last trial.

As a library

import pandas as pd
from mesoscopy.process import decoding

perievent = pd.read_csv("recording_smoothed_regions_event-cueonset_perievent.csv")
tables = decoding.decoding_table(perievent, label="stim")
tables.table.query("decoder == 'lda' and group == 'all'").nlargest(5, "balanced_accuracy")

decoding.decoding_table returns a DecodingTables with the scores in table and the population weights in weights. decoding.rolling_table returns the rolling table. decoding.region_decisions fits the one-feature decoders of every region, fold and shuffle at once from a (n_trials, n_features) array and the fold of each trial, and decoding.fit_scores fits one scikit-learn decoder per fold.