osl_dynamics.meeg.report#

Generate a QC summary HTML report from pipeline plots.

Attributes#

Functions#

generate_report(plots_dir, sessions[, output_dir, ...])

Generate a QC summary HTML report.

Module Contents#

osl_dynamics.meeg.report.STEPS[source]#
osl_dynamics.meeg.report.CSS = Multiline-String[source]#
Show Value
"""
body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    margin: 0;
    padding: 20px;
    background: #f5f5f5;
    color: #333;
}
h1 {
    margin: 0 0 20px 0;
}
.tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 20px;
    border-bottom: 2px solid #ddd;
}
.tab-btn {
    flex: 1;
    padding: 10px 20px;
    border: none;
    background: #e0e0e0;
    cursor: pointer;
    font-size: 14px;
    border-radius: 6px 6px 0 0;
    transition: background 0.2s;
    text-align: center;
}
.tab-btn:hover {
    background: #d0d0d0;
}
.tab-btn.active {
    background: #fff;
    font-weight: bold;
    border-bottom: 2px solid #fff;
    margin-bottom: -2px;
}
.tab-content {
    display: none;
    background: #fff;
    padding: 20px;
    border-radius: 0 0 6px 6px;
}
.tab-content.active {
    display: block;
}
.session-nav {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
    padding: 10px;
    background: #f0f0f0;
    border-radius: 6px;
}
.session-nav button {
    padding: 6px 14px;
    border: 1px solid #ccc;
    background: #fff;
    cursor: pointer;
    border-radius: 4px;
    font-size: 18px;
    line-height: 1;
}
.session-nav button:hover {
    background: #e8e8e8;
}
.session-nav input {
    padding: 6px 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 14px;
    font-family: monospace;
    width: 300px;
}
.session-nav .counter {
    font-size: 13px;
    color: #888;
    margin-left: auto;
}
.subpanel-nav {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 16px;
    padding: 8px 10px;
    background: #e8f0fe;
    border-radius: 6px;
}
.subpanel-nav button {
    padding: 4px 12px;
    border: 1px solid #b0c4de;
    background: #fff;
    cursor: pointer;
    border-radius: 4px;
    font-size: 16px;
    line-height: 1;
}
.subpanel-nav button:hover {
    background: #dce8f5;
}
.subpanel-nav .subpanel-label {
    font-size: 14px;
    font-weight: bold;
    color: #333;
}
.subpanel-nav .subpanel-counter {
    font-size: 12px;
    color: #888;
    margin-left: auto;
}
.subpanel-nav .hint {
    font-size: 11px;
    color: #aaa;
}
.session-panel {
    display: none;
}
.session-panel.active {
    display: block;
}
.subpanel {
    display: none;
}
.subpanel.active {
    display: block;
}
.subpanel img {
    max-width: 100%;
    max-height: calc(80vh - 280px);
    display: block;
    margin: 4px auto;
    border: 1px solid #eee;
}
.subpanel iframe {
    width: 100%;
    height: 350px;
    border: 1px solid #ddd;
    border-radius: 4px;
    margin: 4px 0;
}
.image-row {
    display: flex;
    gap: 10px;
    justify-content: center;
    flex-wrap: wrap;
    margin: 12px 0;
}
.image-row-item {
    flex: 1;
    min-width: 0;
    text-align: center;
}
.image-row-item img {
    max-width: 100%;
    max-height: 200px;
    border: 1px solid #eee;
}
.placeholder {
    background: #eee;
    color: #999;
    padding: 40px;
    text-align: center;
    border-radius: 4px;
    margin: 8px auto;
    font-style: italic;
}
.file-label {
    font-size: 12px;
    color: #888;
    margin: 12px 0 2px 0;
    text-align: center;
}
.summary-box {
    background: #f8f8f8;
    border: 1px solid #ddd;
    border-radius: 4px;
    padding: 10px 14px;
    margin-bottom: 12px;
    font-size: 13px;
    line-height: 1.6;
}
.summary-box .label {
    color: #888;
    font-size: 12px;
}
.summary-box .bad {
    color: #c0392b;
    font-weight: bold;
}
.footer {
    margin-top: 30px;
    padding-top: 15px;
    border-top: 1px solid #ddd;
    font-size: 12px;
    color: #999;
}
"""
osl_dynamics.meeg.report.JS = Multiline-String[source]#
Show Value
"""
var sessions = SESSION_LIST;
var currentStep = 1;
var currentIdx = {};       // per-step session index
var currentSubpanel = {};  // per-step subpanel index
var subpanelCounts = SUBPANEL_COUNTS;  // {step: count}

// Initialise each step to session 0, subpanel 0
STEP_NUMS.forEach(function(s) {
    currentIdx[s] = 0;
    currentSubpanel[s] = 0;
});

function switchTab(step) {
    currentStep = step;
    document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
    document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
    document.getElementById('btn-' + step).classList.add('active');
    document.getElementById('tab-' + step).classList.add('active');
    showSession(step, currentIdx[step]);
}

function showSession(step, idx) {
    if (idx < 0) idx = 0;
    if (idx >= sessions.length) idx = sessions.length - 1;
    currentIdx[step] = idx;

    var panels = document.querySelectorAll('#tab-' + step + ' .session-panel');
    panels.forEach(p => p.classList.remove('active'));

    var panel = document.getElementById('step-' + step + '-session-' + idx);
    if (panel) panel.classList.add('active');

    var input = document.getElementById('input-' + step);
    var counter = document.getElementById('counter-' + step);
    input.value = sessions[idx];
    counter.textContent = (idx + 1) + ' / ' + sessions.length;

    showSubpanel(step, currentSubpanel[step]);
}

function showSubpanel(step, spIdx) {
    var count = subpanelCounts[step] || 1;
    if (spIdx < 0) spIdx = 0;
    if (spIdx >= count) spIdx = count - 1;
    currentSubpanel[step] = spIdx;

    // Hide all subpanels in the current session panel
    var sessionIdx = currentIdx[step];
    var panel = document.getElementById('step-' + step + '-session-' + sessionIdx);
    if (!panel) return;
    var subs = panel.querySelectorAll('.subpanel');
    subs.forEach(s => s.classList.remove('active'));
    var target = panel.querySelector('.subpanel[data-sp-idx="' + spIdx + '"]');
    if (target) target.classList.add('active');

    // Update subpanel nav label and counter
    var label = document.getElementById('sp-label-' + step);
    var spCounter = document.getElementById('sp-counter-' + step);
    if (label && target) {
        label.textContent = target.getAttribute('data-sp-name') || '';
    }
    if (spCounter) {
        spCounter.textContent = (spIdx + 1) + ' / ' + count;
    }
}

function prevSubpanel(step) {
    showSubpanel(step, currentSubpanel[step] - 1);
}

function nextSubpanel(step) {
    showSubpanel(step, currentSubpanel[step] + 1);
}

function prevSession(step) {
    showSession(step, currentIdx[step] - 1);
}

function nextSession(step) {
    showSession(step, currentIdx[step] + 1);
}

function jumpToSession(step) {
    var input = document.getElementById('input-' + step);
    var val = input.value.trim().toLowerCase();
    for (var i = 0; i < sessions.length; i++) {
        if (sessions[i].toLowerCase() === val) {
            showSession(step, i);
            return;
        }
    }
    for (var i = 0; i < sessions.length; i++) {
        if (sessions[i].toLowerCase().indexOf(val) !== -1) {
            showSession(step, i);
            return;
        }
    }
}

document.addEventListener('keydown', function(e) {
    if (document.activeElement.tagName === 'INPUT') {
        if (e.key === 'Enter') {
            e.preventDefault();
            jumpToSession(currentStep);
        }
        return;
    }
    if (e.key === 'ArrowLeft') {
        e.preventDefault();
        prevSession(currentStep);
    } else if (e.key === 'ArrowRight') {
        e.preventDefault();
        nextSession(currentStep);
    } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        prevSubpanel(currentStep);
    } else if (e.key === 'ArrowDown') {
        e.preventDefault();
        nextSubpanel(currentStep);
    }
});
"""
osl_dynamics.meeg.report.generate_report(plots_dir, sessions, output_dir=None, output_file='report.html')[source]#

Generate a QC summary HTML report.

Scans the plots directory for existing QC files and builds a self-contained HTML report with tabs for each pipeline step. Sessions are navigated with left/right arrows or a text input field. Within each step, subpanels are navigated with up/down arrows.

Parameters:
  • plots_dir (str or Path) – Path to the plots directory containing per-session subdirectories.

  • sessions (dict) – Dictionary of sessions (same format as the pipeline scripts).

  • output_dir (str or Path, optional) – Path to the derivatives directory. If provided, surface extraction, coregistration, and parcellation plots are copied from here into the plots directory.

  • output_file (str, optional) – Filename for the report. Written to plots_dir/output_file.

Return type:

None