osl_dynamics.data.sign_flipping#

Sign flipping.

Source reconstruction leaves the sign of each channel (parcel) arbitrary, so the same parcel can have opposite polarity across sessions. Before pooling sessions we align these signs: for each session we search for the +1/-1 per-channel vector whose (time-delay embedded, standardized) covariance best matches a template.

For the common case of sign flipping a single parcellated fif file (or mne.io.Raw) to match a saved template covariance, see sign_flip_mne_raw().

Functions#

calc_cov(array, n_embeddings[, standardize])

Covariance of time-delay embedded (optionally standardized) data.

calc_corr(M1, M2, n_embeddings[, mode])

Correlation between the upper triangles of two covariances.

apply_flips_to_cov(cov, flips, n_embeddings)

Apply a ±1 per-channel flip vector to an embedded covariance.

find_flips(cov, template_cov, n_channels, n_embeddings)

Find the ±1 per-channel flip vector aligning cov to a template.

sign_flip(parcel_data, template_cov[, n_embeddings, ...])

Sign flip parcel time courses to match a template covariance.

sign_flip_mne_raw(fif, template_cov[, output_file, ...])

Sign flip a parcellated fif file to match a template covariance.

Module Contents#

osl_dynamics.data.sign_flipping.calc_cov(array, n_embeddings, standardize=True)[source]#

Covariance of time-delay embedded (optionally standardized) data.

Parameters:
  • array (np.ndarray) – Data, shape (n_samples, n_channels).

  • n_embeddings (int) – Number of time-delay embeddings.

  • standardize (bool, optional) – Standardize the embedded data before computing the covariance.

Returns:

cov – Covariance, shape (n_channels * n_embeddings, n_channels * n_embeddings).

Return type:

np.ndarray

osl_dynamics.data.sign_flipping.calc_corr(M1, M2, n_embeddings, mode=None)[source]#

Correlation between the upper triangles of two covariances.

The first n_embeddings diagonals are skipped (k=n_embeddings in numpy.triu_indices()) so within-channel, near-lag terms — which do not depend on the sign — are excluded from the comparison.

Parameters:
  • M1 (np.ndarray) – Covariance matrices of the same shape.

  • M2 (np.ndarray) – Covariance matrices of the same shape.

  • n_embeddings (int) – Number of time-delay embeddings (sets the skipped diagonal offset).

  • mode (str, optional) – If "abs", compare the absolute values (sign-invariant), used when picking a median template session.

Returns:

corr – Pearson correlation between the selected entries.

Return type:

float

osl_dynamics.data.sign_flipping.apply_flips_to_cov(cov, flips, n_embeddings)[source]#

Apply a ±1 per-channel flip vector to an embedded covariance.

Parameters:
  • cov (numpy.ndarray)

  • flips (numpy.ndarray)

  • n_embeddings (int)

Return type:

numpy.ndarray

osl_dynamics.data.sign_flipping.find_flips(cov, template_cov, n_channels, n_embeddings, n_random_starts=3, n_spectral=2)[source]#

Find the ±1 per-channel flip vector aligning cov to a template.

The objective is reduced to a small n_channels × n_channels problem that is exactly the correlation calc_corr() maximizes. In that form aligning the signs is a Z2 (Ising / MAX-CUT) synchronization problem, solved by:

  1. warm starts: the sign of the leading eigenvector(s) of the reduced matrix (the spectral relaxation), plus the all-+1 start and n_random_starts random restarts;

  2. exact greedy coordinate ascent on the true correlation from each start, keeping the best local optimum.

Parameters:
  • cov (np.ndarray) – This session’s embedded covariance (from calc_cov()).

  • template_cov (np.ndarray) – Template embedded covariance to align to (same shape as cov).

  • n_channels (int) – Number of channels (parcels).

  • n_embeddings (int) – Number of time-delay embeddings used to build cov.

  • n_random_starts (int, optional) – Number of random restarts (in addition to the spectral and all-+1 starts). The restarts use a fixed internal seed, so the whole search is deterministic and reproducible.

  • n_spectral (int, optional) – Number of leading eigenvectors of the reduced matrix used as warm starts.

Returns:

  • flips (np.ndarray) – Per-channel flips in {+1, -1}, shape (n_channels,), dtype int.

  • metric (float) – The achieved correlation with template_cov.

Return type:

Tuple[numpy.ndarray, float]

osl_dynamics.data.sign_flipping.sign_flip(parcel_data, template_cov, n_embeddings=15, standardize=True)[source]#

Sign flip parcel time courses to match a template covariance.

Finds the per-parcel +1/-1 signs whose (time-delay embedded, standardized) covariance best matches template_cov and applies them. This is the array-level core shared by sign_flip_mne_raw() and by scripts that already hold the parcel data in memory.

Parameters:
  • parcel_data (np.ndarray) – Parcel time courses, shape (n_parcels, n_samples).

  • template_cov (str or np.ndarray) – Template covariance to align to, or the path to a .npy file containing it. This must be the covariance of the time-delay embedded data (see calc_cov()), built with the same n_embeddings and standardize settings passed here.

  • n_embeddings (int, optional) – Number of time-delay embeddings used to build the covariances.

  • standardize (bool, optional) – Standardize the embedded data before computing the covariance.

Returns:

  • flipped_data (np.ndarray) – parcel_data with the per-parcel signs applied, same shape as the input.

  • flips (np.ndarray) – The per-parcel +1/-1 signs that were applied, shape (n_parcels,).

  • corr (float) – Correlation between the flipped covariance and template_cov (the metric maximised by the flip search).

Return type:

Tuple[numpy.ndarray, numpy.ndarray, float]

osl_dynamics.data.sign_flipping.sign_flip_mne_raw(fif, template_cov, output_file=None, n_embeddings=15, standardize=True, picks='misc', extra_chans='stim')[source]#

Sign flip a parcellated fif file to match a template covariance.

Convenience wrapper that loads parcel time courses from a fif file (or mne.io.Raw), finds the per-parcel signs that best align its (time-delay embedded) covariance to template_cov, and applies them. The result is either saved to a new fif file or returned as an mne.io.Raw object (see output_file).

Parameters:
  • fif (str or mne.io.Raw) – Path to a parcellated fif file, or a loaded mne.io.Raw object. The parcel time courses are read from the picks channels.

  • template_cov (str or np.ndarray) – Template covariance to align to, or the path to a .npy file containing it. This must be the covariance of the time-delay embedded data (see calc_cov()), built with the same n_embeddings and standardize settings passed here.

  • output_file (str, optional) – Path to save the sign-flipped fif file to. If None (the default), nothing is written to disk and the sign-flipped mne.io.Raw object is returned instead (see Returns).

  • n_embeddings (int, optional) – Number of time-delay embeddings used to build the covariances.

  • standardize (bool, optional) – Standardize the embedded data before computing the covariance.

  • picks (str, optional) – Channel type holding the parcel time courses. Parcellated fif files written by osl-dynamics store parcels as "misc" channels.

  • extra_chans (str or list of str, optional) – Extra channels (e.g. "stim") to copy from the input to the output. Passed to osl_dynamics.meeg.parcellation.save_as_fif().

Return type:

Union[Tuple[numpy.ndarray, float], mne.io.Raw]

Examples

Save the sign-flipped data to a new fif file:

from osl_dynamics.data import sign_flipping

sign_flipping.sign_flip_mne_raw(
    "lcmv-parc-raw.fif",
    "template_cov.npy",
    "sflip-lcmv-parc-raw.fif",
)

Or get the sign-flipped data back as an mne.io.Raw without saving:

raw = sign_flipping.sign_flip_mne_raw("lcmv-parc-raw.fif", "template_cov.npy")