Skip to content

Add group-level sign flipping and two custom ICA rejection steps - #22

Open
harrisonritz wants to merge 2 commits into
devfrom
claude/osl-ephys-pipeline-design-gm9eiw
Open

Add group-level sign flipping and two custom ICA rejection steps#22
harrisonritz wants to merge 2 commits into
devfrom
claude/osl-ephys-pipeline-design-gm9eiw

Conversation

@harrisonritz

Copy link
Copy Markdown
Owner

No description provided.

claude added 2 commits August 7, 2026 06:06
Adds `custom.osl`, an osl-ephys route over the same BIDS data the
mne-bids-pipeline route reads, running one subject per invocation so it
can be the body of a SLURM array job. Stages: preproc, source, all,
collate, validate, driven by a single YAML config per analysis.

Two source backends, since osl-ephys' LCMV path is written against RHINO
and therefore needs FSL (make_lcmv reads the forward from the RHINO file
tree, transform_recon_timeseries needs RHINO's transforms and calls
flirt, and resample_parcellation calls flirt too). Its
surface_extraction_method='freesurfer' path avoids FSL but only reaches
minimum-norm estimates:

  rhino       osl-ephys' native path; requires FSL.
  freesurfer  custom.osl.fs_bridge, reusing the existing recon-all output
              and -trans.fif, beamforming with mne.beamformer.make_lcmv
              and morphing to MNI via FreeSurfer's talairach.xfm. Parcel
              time courses use osl-ephys' own maths, so output is
              comparable with the RHINO backend. No FSL needed.

format_bids converts triggers to annotations and drops the stim channels,
so osl-ephys' find_events cannot be used; events_from_annotations rebuilds
the events array from raw.annotations instead, leaving format_bids
untouched. Codes come from the config's meta.event_codes so numbering is
stable across subjects, and descriptions with no annotations are dropped
because mne.Epochs rejects an event_id entry matching no event.

Group HTML reports are deferred to the collate stage. osl-ephys rebuilds
the shared subject_report.html at the end of every chain from inside its
own try/except, which in an array means every task writing one file and a
rendering hiccup failing an otherwise-successful subject.

The validate stage checks a config without touching data. It replicates
run_src_chain's argument check, which is stricter than it looks: it
rejects any wrapper declared (*args, **kwargs), so the
extract_fiducials_from_fif alias used by the osl-ephys tutorials cannot
be used in a config at all (use extract_polhemus_from_info).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013fJPzRr6DvTdWL92Ffz2a5
custom.osl.sign_flip + custom.osl.group add a `group` stage that runs after
the per-subject array: pick a template subject, sign-flip every subject's
parcel time courses against it, then stack the flipped epochs into
(subjects x parcels x times) arrays per condition and form the configured
contrasts. A beamformer resolves each dipole's orientation only up to a sign,
independently per subject, so averaging without fixing that cancels the signal.

The flip search itself is osl-ephys'; the file handling around it is not,
because osl-ephys' own is broken for epoched data:
sign_flipping.apply_flips reads {outdir}/{subject}/parc/parc-epo.fif in its
epoched branch, while find_template_subject and fix_sign_ambiguity both write
and look for {source_method}-parc-epo.fif. The continuous branch gets the
prefix right; the epoched one does not, so fix_sign_ambiguity(epoched=True)
fails on a file that never existed. sign_flip.apply_flips is the corrected
equivalent, and writes separate *_sflip_*.fif files so re-runs never compound.

Two new preprocessing steps in custom.osl.extra_funcs:

  ica_autoreject_safe   osl-ephys' ica_autoreject with skip_if_absent.
                        find_bads_eog raises when there is no EOG channel,
                        which fails the whole chain; here it is skipped with a
                        warning, so a subject recorded without eye-tracking
                        does not need its own config. ECG is not guarded --
                        find_bads_ecg synthesises one from the magnetometers.

  ica_kurtosisreject    Marks components whose time course has excessive
                        kurtosis. Adapted from the osl-ephys
                        preprocessing_automatic tutorial, with two deliberate
                        departures: component time courses come from
                        ica.get_sources() (the unmixing matrix) rather than the
                        tutorial's get_components().T @ data (the mixing
                        matrix), which is not the component time course; and
                        BAD_* spans are excluded by default, since bad segments
                        are themselves the high-kurtosis spans.

validate now also checks the group section's contrasts, and the flip search is
parallelised across a local Dask cluster inside the one job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013fJPzRr6DvTdWL92Ffz2a5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants