Report Generation Utilities for EEG Preprocessing Pipeline.
This module provides helper functions for generating HTML reports with
interactive visualizations and detailed preprocessing information.
Functions
collect_bad_channels_from_steps(preprocessing_steps)
Collects all unique bad channels from preprocessing step records.
Aggregates channels marked as bad across multiple detection steps.
create_bad_channels_topoplot(info, bad_channels, figsize)
Creates a topographical plot showing bad channels marked with red crosses.
Uses the montage from the info object for electrode positions.
create_preprocessing_steps_table(preprocessing_steps)
Creates an interactive HTML table with collapsible sections for each
preprocessing step. Displays step parameters in a formatted, readable way.
Usage
These functions are typically called by the generate_html_report step:
from report import (
collect_bad_channels_from_steps,
create_bad_channels_topoplot,
create_preprocessing_steps_table
)
# Collect bad channels
bad_channels = collect_bad_channels_from_steps(preprocessing_steps)
# Create topoplot
fig = create_bad_channels_topoplot(info, bad_channels)
# Create steps table
html_table = create_preprocessing_steps_table(preprocessing_steps)
The HTML reports include:
- Bad channels visualization (topoplot with red crosses)
- Preprocessing steps summary (collapsible table with parameters)
- Raw data plots, ICA components, epochs, and evoked responses (via MNE Report)
Report Structure
Generated HTML reports contain:
1. Bad Channels section (if any bad channels detected)
- Topoplot showing channel positions
- List of bad channel names
2. Preprocessing Steps section
- Collapsible table with each step's parameters
- Parameter values formatted based on type (dict, list, number, etc.)
3. Data Visualizations (via MNE Report)
- ICA components (if ICA was applied)
- Raw data traces
- Events timeline (if events exist)
- Epochs and evoked responses (if epochs exist)
See Also
mne.Report : MNE's report generation class
collect_bad_channels_from_steps
collect_bad_channels_from_steps(preprocessing_steps)
Collect all bad channels from preprocessing steps.
Parameters
preprocessing_steps : list of dict
List of preprocessing step dictionaries, each potentially containing
a 'bad_channels' key.
Returns
bad_channels : list of str
Unique list of all bad channels found in preprocessing steps.
Source code in src/meegflow/report.py
| def collect_bad_channels_from_steps(preprocessing_steps: List[Dict[str, Any]]) -> List[str]:
"""
Collect all bad channels from preprocessing steps.
Parameters
----------
preprocessing_steps : list of dict
List of preprocessing step dictionaries, each potentially containing
a 'bad_channels' key.
Returns
-------
bad_channels : list of str
Unique list of all bad channels found in preprocessing steps.
"""
bad_channels = []
for step in preprocessing_steps:
if 'bad_channels' in step and step['bad_channels']:
# Add bad channels from this step
step_bad_channels = step['bad_channels']
if isinstance(step_bad_channels, list):
bad_channels.extend(step_bad_channels)
elif isinstance(step_bad_channels, str):
bad_channels.append(step_bad_channels)
# TODO: replace by return list(set(bad_channels))
# Return unique channels preserving order
seen = set()
unique_bad_channels = []
for ch in bad_channels:
if ch not in seen:
seen.add(ch)
unique_bad_channels.append(ch)
return unique_bad_channels
|
create_bad_channels_topoplot
create_bad_channels_topoplot(info, bad_channels, outlines=None, figsize=(8, 6))
Create a topoplot showing bad channels marked with red crosses.
Uses the montage from the info object to determine the appropriate
head shape and electrode positions.
Parameters
info : mne.Info
MNE Info object containing channel information and montage.
bad_channels : list of str
List of bad channel names to mark on the topoplot.
outlines : dict or None, optional
Dictionary defining head shape outlines. Default is None.
figsize : tuple, optional
Figure size (width, height) in inches. Default is (8, 6).
Returns
fig : matplotlib.figure.Figure or None
Figure containing the topoplot, or None if creation failed.
Source code in src/meegflow/report.py
| def create_bad_channels_topoplot(
info: mne.Info,
bad_channels: List[str],
outlines: Optional[Dict[str, List[int]]] = None,
figsize: tuple = (8, 6)
) -> Optional[plt.Figure]:
"""
Create a topoplot showing bad channels marked with red crosses.
Uses the montage from the info object to determine the appropriate
head shape and electrode positions.
Parameters
----------
info : mne.Info
MNE Info object containing channel information and montage.
bad_channels : list of str
List of bad channel names to mark on the topoplot.
outlines : dict or None, optional
Dictionary defining head shape outlines. Default is None.
figsize : tuple, optional
Figure size (width, height) in inches. Default is (8, 6).
Returns
-------
fig : matplotlib.figure.Figure or None
Figure containing the topoplot, or None if creation failed.
"""
if not bad_channels:
logger.info("No bad channels to plot")
return None
# Create figure
fig, ax = plt.subplots(figsize=figsize)
# Get all EEG channel positions
eeg_picks = mne.pick_types(info, eeg=True, exclude=[])
if len(eeg_picks) == 0:
logger.warning("No EEG channels found for topoplot")
plt.close(fig)
return None
# Get channel names
ch_names = [info['ch_names'][i] for i in eeg_picks]
# Create data array (all zeros for white background)
data_to_plot = np.zeros(len(eeg_picks))
# Create mask for bad channels
mask = np.array([ch in bad_channels for ch in ch_names])
if not np.any(mask):
logger.warning("None of the bad channels are in the EEG channels")
plt.close(fig)
return None
# Plot topomap with white background
# The montage from info will be used automatically by plot_topomap
from mne.viz import plot_topomap
im, cn = plot_topomap(
data_to_plot,
info,
axes=ax,
show=False,
cmap='Greys',
vlim=(0, 0.1),
outlines=outlines,
mask=mask,
mask_params=dict(
marker='x',
markerfacecolor='red',
markeredgecolor='red',
linewidth=0,
markersize=15
),
sensors=True,
contours=0
)
ax.set_title(f'Bad Channels (n={len(bad_channels)})', fontsize=14, fontweight='bold')
# Add text listing bad channels
bad_channels_text = ', '.join(bad_channels)
fig.text(0.5, 0.05, f'Bad channels: {bad_channels_text}',
ha='center', fontsize=10, wrap=True)
plt.tight_layout()
return fig
|
create_preprocessing_steps_table
create_preprocessing_steps_table(preprocessing_steps)
Create an HTML table with collapsible rows for preprocessing steps.
Uses MNE Report table styling with bootstrap-table classes.
Each step's parameters are displayed in a two-column table:
- First column: parameter keys
- Second column: parameter values
- Numbers displayed as numbers
- Lists displayed as bullet points
- Dicts displayed as prettified JSON with indent 4
Parameters
preprocessing_steps : list of dict
List of preprocessing step dictionaries containing step information.
Returns
html_content : str
HTML string containing the styled, collapsible table.
Source code in src/meegflow/report.py
| def create_preprocessing_steps_table(preprocessing_steps: List[Dict[str, Any]]) -> str:
"""
Create an HTML table with collapsible rows for preprocessing steps.
Uses MNE Report table styling with bootstrap-table classes.
Each step's parameters are displayed in a two-column table:
- First column: parameter keys
- Second column: parameter values
- Numbers displayed as numbers
- Lists displayed as bullet points
- Dicts displayed as prettified JSON with indent 4
Parameters
----------
preprocessing_steps : list of dict
List of preprocessing step dictionaries containing step information.
Returns
-------
html_content : str
HTML string containing the styled, collapsible table.
"""
if not preprocessing_steps:
return ""
def format_value(value):
"""Format a value based on its type."""
if isinstance(value, dict):
# Format dicts as prettified JSON with indent 4
return f'<pre style="margin: 0; background-color: #f8f9fa; padding: 8px; border-radius: 4px;">{json.dumps(value, indent=4, cls=NpEncoder)}</pre>'
elif isinstance(value, list):
# Format lists as bullet points
if not value:
return '<em>empty list</em>'
bullet_points = ''.join([f'<li>{item}</li>' for item in value])
return f'<ul style="margin: 0; padding-left: 20px;">{bullet_points}</ul>'
elif isinstance(value, (int, float)):
# Format numbers as numbers (not strings)
return str(value)
elif value is None:
return '<em>None</em>'
else:
# Format everything else as string
return str(value)
# Create HTML with collapsible sections for each step
html_content = """
<style>
.step-container {
margin-bottom: 20px;
}
.step-header {
cursor: pointer;
padding: 12px;
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
font-weight: bold;
user-select: none;
transition: background-color 0.2s;
}
.step-header:hover {
background-color: #e9ecef;
}
.step-header .toggle-icon {
float: right;
font-weight: bold;
}
.step-details {
display: none;
margin-top: 10px;
}
.step-details.active {
display: block;
}
.params-table {
width: 100%;
margin-top: 10px;
}
.params-table td {
padding: 8px;
border: 1px solid #dee2e6;
vertical-align: top;
}
.params-table td:first-child {
background-color: #f8f9fa;
font-weight: 500;
width: 30%;
}
</style>
<script>
function toggleStep(stepId) {
var details = document.getElementById('details-' + stepId);
var icon = document.getElementById('icon-' + stepId);
if (details.classList.contains('active')) {
details.classList.remove('active');
icon.textContent = '▼';
} else {
details.classList.add('active');
icon.textContent = '▲';
}
}
</script>
"""
for idx, step in enumerate(preprocessing_steps, 1):
step_name = step.get('step', 'Unknown')
step_id = f"step-{idx}"
# Create collapsible section for this step
html_content += f"""
<div class="step-container">
<div class="step-header" onclick="toggleStep('{step_id}')">
<span>Step {idx}: {step_name}</span>
<span class="toggle-icon" id="icon-{step_id}">▼</span>
</div>
<div class="step-details" id="details-{step_id}">
<table class="params-table table table-hover">
<tbody>
"""
# Add each parameter as a row in the table
for key, value in step.items():
if key != 'step':
formatted_value = format_value(value)
html_content += f"""
<tr>
<td>{key}</td>
<td>{formatted_value}</td>
</tr>
"""
html_content += """
</tbody>
</table>
</div>
</div>
"""
return html_content
|