Stage 3: Head shape and sensor alignment plots

Author: Hadi Zaatiti <hadi.zaatiti@nyu.edu>

What & why

Stage 3 of the KIT general pipeline is the quality check of stage 2. For every .fif that stage 2 lists as a current, verified output, it renders the laser head shape, the digitised points and the MEG helmet and sensors in the same 3-D scene and saves the picture. The scene is drawn with the device-to-head transform (info['dev_head_t']) that was stored in the FIF by the KIT-to-FIFF GUI, so what you see is exactly the alignment the operator accepted when pressing Save FIFF…, not a new fit.

Why a separate stage: in the GUI the alignment is judged on screen and then the window is closed. Stage 3 keeps a picture of it next to the data, writes one row per FIF in a summary table, and puts every picture in the report, so a bad head-shape fit (head shape floating above the helmet, rotated, or mirrored) is caught before bad channels, ICA and filtering are spent on the recording.

The stage does not modify any FIF and makes no decision. A recording with an odd picture is a recording to redo in stage 2 (--overwrite).

What you need to provide

  • The stage-2 outputs: <bids_root>/derivatives/2-kit_con_to_fif/ with kit2fiff_summary.csv and the *_desc-rawkit_raw.fif files. Only the FIFs listed in the summary with a success or skipped status are plotted; split parts (*_raw-1.fif) are read through their base file.

  • A 3-D backend: pyvista, pyvistaqt and vtk are in the neurowaves recipe. Without a display the stage renders off-screen (the default); --show needs a desktop session with OpenGL.

Configuration file

Stage 3 has no block of its own. It reads:

  • project and subjects to resolve the BIDS root and the subject selection (subjects.include / subjects.exclude);

  • bids_selection to restrict the recordings (tasks, runs, acquisitions, processings);

  • the stage-2 folder name through the pipeline utilities, so a dataset whose stage-2 outputs still sit in a pre-renumbering folder (2-kit_con_to_fif_manual or kit2fiff) is found as well.

How to run

conda activate neurowaves
python -m pipeline.mne_pipelines.kit_general_pipelines.3-kit_fif_plot_headshape_sensor_alignment \
    -c pipeline/mne_pipelines/kit_general_pipelines/pipeline_config_files/<project>_config.yml

Options:

  • --show: open the interactive 3-D window for every recording instead of only saving the PNG. Rotate the scene to look at the fit from the side and from above; closing the window continues with the next recording.

The VS Code launch entries are named <project> 3 Plot headshape / sensor alignment; the pipeline console runs the stage from its Run a stage panel.

What the script does

Per FIF, in this order:

  1. Read kit2fiff_summary.csv and keep the FIFs whose row is current (fif_out exists and status is not an error). FIFs that are on disk but absent from the summary are ignored, like in every later stage.

  2. Open the FIF without loading the data and read info['dev_head_t']. An identity transform means no coregistration was applied; the stage records an error for that recording instead of drawing a meaningless picture. The norm of the translation is printed (typically 30 to 150 mm for a KIT subject) as a quick sanity check.

  3. Check that the FIF carries digitisation points (info['dig']): the fiducials, the HPI coils and the head-shape points attached by stage 2.

  4. Call mne.viz.plot_alignment with meg=["helmet", "sensors"], dig=True, coord_frame='meg' and no surfaces. Rendering in the device frame means the sensors stay where they physically are and the head shape is moved into the helmet with the inverse of dev_head_t: any gap between the two is the residual of the accepted fit.

  5. Render the scene from the front, the left and the top into one image, a temporary *.pending.png renamed to the final name only when the file was written; a crash never leaves a half-written image behind. MNE’s plotter returns a black screenshot when it renders hidden on the lab workstation, so its actors (helmet, sensors, digitised points, axes) are moved into a plain off-screen PyVista plotter (brain_render.screenshot_mne_3d); an image that comes out blank is recorded as an error.

  6. Write a .json sidecar with the MNE version, the plot version and the identity (path, size, modification time) of the FIF that was plotted, so a later stage-2 rerun that replaces the FIF (or a change of the drawing) invalidates the picture even when its name did not change.

Outputs

Under <bids_root>/derivatives/3-kit_fif_plot_headshape_sensor_alignment/:

alignment_summary.csv
configs/config_<timestamp>.yml
sub-<id>/[ses-<id>/]
  sub-<id>..._desc-rawkit_raw.png          # the 3-D scene
  sub-<id>..._desc-rawkit_raw.png.json     # which FIF was plotted, MNE version

alignment_summary.csv has one row per FIF: timestamp, input_fif, output_png, status (success | up_to_date | error), error, mne_version, source_size and source_mtime_ns. A picture whose sidecar still matches the FIF (same path, size, modification time and MNE version) is not redrawn and its row says up_to_date. Rows of recordings that were not part of a run (another subjects.include) are kept, so the table always describes the whole cohort.

Report

The report shows every picture in the stage-3 section, grouped by subject and session, with the summary row under it. A recording whose status is error appears with the error text instead of a picture.

Edge cases & safeguards

  • Identity transform: reported as an error; redo the conversion of that recording in stage 2 (the GUI must be saved with Save FIFF…, closing it does not write a file).

  • No digitisation: the head-shape files were not found by stage 2; check the acq-head and acq-points files of the subject or session.

  • 3-D backend unavailable: every selected FIF gets the same error text and nothing is drawn; see Troubleshooting.

  • Stage 2 rerun: a FIF replaced with --overwrite changes its size or modification time, so the next stage-3 run redraws its picture instead of reporting it up_to_date. There is no --overwrite for stage 3: the sidecar decides.

Troubleshooting

  • ``3D backend unavailable``: pyvistaqt or vtk cannot initialise. Run on a machine with a desktop session and OpenGL, with the neurowaves interpreter selected; on a remote Linux box set up a virtual display (xvfb-run) for the off-screen render.

  • Head shape outside the helmet: the fit was accepted with the wrong marker set or the wrong head-shape file. Check mrk_paths and hsp_path of that row in kit2fiff_summary.csv and redo stage 2.

  • Empty summary: no FIF of the selection is listed in the stage-2 summary; run stage 2 first, or widen subjects.include / bids_selection.