@yibeichan/claude-skills 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/cli.js +272 -0
- package/install.py +240 -0
- package/package.json +44 -0
- package/skills/bidsapp-nidm-standards/SKILL.md +202 -0
- package/skills/bidsapp-nidm-standards/references/babs_config.md +20 -0
- package/skills/bidsapp-nidm-standards/references/cli_arguments.md +76 -0
- package/skills/bidsapp-nidm-standards/references/container_patterns.md +53 -0
- package/skills/bidsapp-nidm-standards/references/nidm_integration.md +403 -0
- package/skills/bidsapp-nidm-standards/references/repo_structure.md +121 -0
- package/skills/bidsapp-nidm-standards/references/testing_patterns.md +82 -0
- package/skills/dicom2fmriprep/SKILL.md +377 -0
- package/skills/dicom2fmriprep/evals/evals.json +26 -0
- package/skills/dicom2fmriprep/references/babs-details.md +407 -0
- package/skills/dicom2fmriprep/references/fmriprep-details.md +250 -0
- package/skills/dicom2fmriprep/references/heudiconv-details.md +243 -0
- package/skills/fmri-ssm/SKILL.md +317 -0
- package/skills/fmri-ssm/references/code_templates.md +1570 -0
- package/skills/fmri-ssm/references/downstream_analysis.md +680 -0
- package/skills/fmri-ssm/references/group_inference.md +608 -0
- package/skills/fmri-ssm/references/hrf_modeling.md +447 -0
- package/skills/fmri-ssm/references/model_catalog.md +436 -0
- package/skills/fmri-ssm/references/paradigm_guide.md +406 -0
- package/skills/fmri-ssm/references/preprocessing.md +614 -0
- package/skills/fmri-ssm.zip +0 -0
- package/skills/neuroimaging-qc/SKILL.md +203 -0
- package/skills/neuroimaging-qc/references/eeg_qc.md +400 -0
- package/skills/neuroimaging-qc/references/fmri_qc.md +343 -0
- package/skills/neuroimaging-qc/references/fnirs_qc.md +430 -0
- package/skills/neuroimaging-qc/references/structural_qc.md +454 -0
- package/skills/neuroimaging-qc/scripts/parse_fmriprep_confounds.py +153 -0
- package/skills/neuroimaging-qc/scripts/parse_mriqc.py +114 -0
- package/skills/neuroimaging-qc/scripts/qc_report.py +295 -0
- package/skills/scientific-writer/SKILL.md +202 -0
- package/skills/scientific-writer/references/citation_styles.md +163 -0
- package/skills/scientific-writer/references/field_conventions.md +245 -0
- package/skills/scientific-writer/references/figures_tables.md +225 -0
- package/skills/scientific-writer/references/reporting_guidelines.md +225 -0
- package/skills.json +54 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: neuroimaging-qc
|
|
3
|
+
description: Evidence-based QC decision-making for neuroimaging data. Interpret QC metrics from any pipeline (fMRIPrep, MRIQC, FreeSurfer, MNE-Python, Homer3, custom outputs) to make justified inclusion/exclusion decisions. Covers fMRI, EEG, fNIRS, and structural MRI across populations (adults, infants, adolescents, clinical) and paradigms (resting-state, task, naturalistic, sleep). Use when filtering subjects based on QC outputs, setting exclusion thresholds, justifying QC criteria for methods sections, or parsing QC files programmatically with Python.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Neuroimaging QC Decision-Making
|
|
7
|
+
|
|
8
|
+
Evidence-based guidance for interpreting QC metrics and making principled inclusion/exclusion decisions.
|
|
9
|
+
|
|
10
|
+
## Core Principles
|
|
11
|
+
|
|
12
|
+
### 1. No Universal Thresholds
|
|
13
|
+
QC thresholds are study-specific. Factors affecting appropriate cutoffs:
|
|
14
|
+
- **Population**: Infants tolerate higher motion than adults
|
|
15
|
+
- **Paradigm**: Task fMRI has different constraints than resting-state
|
|
16
|
+
- **Analysis**: Connectivity analyses are more motion-sensitive than activation
|
|
17
|
+
- **Sample size**: Stricter thresholds with larger N; lenient with small N
|
|
18
|
+
|
|
19
|
+
### 2. Distribution-Based Decisions
|
|
20
|
+
Always examine your sample's QC distribution before applying thresholds:
|
|
21
|
+
1. Plot histograms of key metrics
|
|
22
|
+
2. Identify natural breakpoints/outliers (>2-3 SD from mean)
|
|
23
|
+
3. Apply literature-based thresholds as starting points, adjust based on distribution
|
|
24
|
+
4. Report both threshold AND resulting exclusion rate
|
|
25
|
+
|
|
26
|
+
### 3. Multi-Metric Assessment
|
|
27
|
+
Never exclude based on single metric. Combine:
|
|
28
|
+
- Motion metrics (FD, DVARS)
|
|
29
|
+
- Signal quality metrics (tSNR, SNR)
|
|
30
|
+
- Artifact indicators (outlier volumes, registration quality)
|
|
31
|
+
- Visual inspection for edge cases
|
|
32
|
+
|
|
33
|
+
## Decision Workflow
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
1. IDENTIFY your QC source
|
|
37
|
+
├── Known pipeline (fMRIPrep, MRIQC, etc.) → See modality references
|
|
38
|
+
└── Custom/unknown output → Parse available metrics, map to known categories
|
|
39
|
+
|
|
40
|
+
2. CHARACTERIZE your study
|
|
41
|
+
├── Population: adult / pediatric / infant / clinical
|
|
42
|
+
├── Paradigm: rest / task / naturalistic / sleep
|
|
43
|
+
└── Analysis: activation / connectivity / other
|
|
44
|
+
|
|
45
|
+
3. ESTABLISH thresholds
|
|
46
|
+
├── Start with literature recommendations (see references)
|
|
47
|
+
├── Examine your sample distribution
|
|
48
|
+
└── Adjust based on trade-off: data quality vs. statistical power
|
|
49
|
+
|
|
50
|
+
4. APPLY and DOCUMENT
|
|
51
|
+
├── Generate exclusion summary
|
|
52
|
+
├── Report thresholds with citations
|
|
53
|
+
└── Conduct sensitivity analysis with stricter/lenient thresholds
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick Reference: Common Thresholds
|
|
57
|
+
|
|
58
|
+
### fMRI Motion (FD)
|
|
59
|
+
|
|
60
|
+
| Population | Conservative | Standard | Lenient | Citation |
|
|
61
|
+
|------------|-------------|----------|---------|----------|
|
|
62
|
+
| Adults (rest) | 0.2 mm | 0.3 mm | 0.5 mm | Power et al., 2012, 2014 |
|
|
63
|
+
| Adults (task) | 0.5 mm | 0.9 mm | 1.0 mm | Siegel et al., 2014 |
|
|
64
|
+
| Children (6-12y) | 0.3 mm | 0.4 mm | 0.5 mm | Fair et al., 2012 |
|
|
65
|
+
| Infants | 0.3 mm | 0.5 mm | — | Population-dependent |
|
|
66
|
+
| Neonates | 0.2 mm | 0.5 mm | — | Smyser et al., 2010 |
|
|
67
|
+
|
|
68
|
+
**Additional motion criteria:**
|
|
69
|
+
- fd_perc (% volumes > threshold): typically exclude if >20-50%
|
|
70
|
+
- Maximum FD spike: consider >3-5 mm as problematic
|
|
71
|
+
- Minimum usable data: ≥5 min for resting-state, task-dependent for task fMRI
|
|
72
|
+
|
|
73
|
+
### EEG Amplitude (Peak-to-Peak)
|
|
74
|
+
|
|
75
|
+
| Channel Type | Reject Threshold | Flat Threshold | Notes |
|
|
76
|
+
|--------------|-----------------|----------------|-------|
|
|
77
|
+
| EEG | 100-200 µV | 1 µV | Hardware-dependent |
|
|
78
|
+
| EOG | 200-250 µV | — | Blink detection |
|
|
79
|
+
| MEG (mag) | 3000-4000 fT | 1 fT | Magnetometers |
|
|
80
|
+
| MEG (grad) | 3000-4000 fT/cm | 1 fT/cm | Gradiometers |
|
|
81
|
+
|
|
82
|
+
**Additional EEG criteria:**
|
|
83
|
+
- Channel rejection: >20-30% bad epochs → mark as bad channel
|
|
84
|
+
- Epoch rejection: typically accept 10-30% epoch loss; >50% problematic
|
|
85
|
+
- Interpolation limit: ≤10% of channels can be interpolated
|
|
86
|
+
|
|
87
|
+
### Structural MRI
|
|
88
|
+
|
|
89
|
+
| Metric | Direction | Concern Level | Notes |
|
|
90
|
+
|--------|-----------|---------------|-------|
|
|
91
|
+
| CNR (GM/WM) | Higher better | <2.5 | Tissue contrast |
|
|
92
|
+
| SNR | Higher better | Site-dependent | Compare within-site |
|
|
93
|
+
| QI1 | Lower better | >0.1 | Artifact detection |
|
|
94
|
+
| EFC | Lower better | Outlier in distribution | Ghosting indicator |
|
|
95
|
+
|
|
96
|
+
## Modality-Specific References
|
|
97
|
+
|
|
98
|
+
For detailed metrics, thresholds, and Python code:
|
|
99
|
+
|
|
100
|
+
- **fMRI (fMRIPrep/MRIQC)**: See [references/fmri_qc.md](references/fmri_qc.md)
|
|
101
|
+
- **EEG/MEG (MNE-Python)**: See [references/eeg_qc.md](references/eeg_qc.md)
|
|
102
|
+
- **fNIRS (Homer3/MNE-NIRS)**: See [references/fnirs_qc.md](references/fnirs_qc.md)
|
|
103
|
+
- **Structural MRI**: See [references/structural_qc.md](references/structural_qc.md)
|
|
104
|
+
|
|
105
|
+
## Python Utilities
|
|
106
|
+
|
|
107
|
+
Scripts for parsing QC outputs and applying thresholds:
|
|
108
|
+
|
|
109
|
+
- `scripts/parse_mriqc.py`: Parse MRIQC group TSV, flag subjects
|
|
110
|
+
- `scripts/parse_fmriprep_confounds.py`: Summarize fMRIPrep confounds
|
|
111
|
+
- `scripts/qc_report.py`: Generate QC summary reports
|
|
112
|
+
|
|
113
|
+
## Methods Section Templates
|
|
114
|
+
|
|
115
|
+
### fMRI QC Methods
|
|
116
|
+
```
|
|
117
|
+
Quality control was performed using [MRIQC/fMRIPrep] outputs. Subjects were
|
|
118
|
+
excluded based on the following criteria: (1) mean framewise displacement
|
|
119
|
+
(FD) > X mm [cite Power et al., 2012], (2) >Y% of volumes exceeding FD
|
|
120
|
+
threshold of Z mm, or (3) visual inspection revealing [registration
|
|
121
|
+
failures/artifacts]. This resulted in N subjects excluded (X% of sample),
|
|
122
|
+
yielding a final sample of M participants.
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### EEG QC Methods
|
|
126
|
+
```
|
|
127
|
+
Continuous EEG data underwent artifact rejection using MNE-Python. Epochs
|
|
128
|
+
containing peak-to-peak amplitudes exceeding X µV were rejected. Channels
|
|
129
|
+
with >Y% rejected epochs were marked as bad and interpolated using spherical
|
|
130
|
+
spline interpolation. Participants with >Z% rejected epochs or >N bad
|
|
131
|
+
channels were excluded from analysis.
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Handling Unknown QC Outputs
|
|
135
|
+
|
|
136
|
+
When encountering unfamiliar QC metrics:
|
|
137
|
+
|
|
138
|
+
1. **Identify metric category**:
|
|
139
|
+
- Motion/movement: Look for displacement, rotation, translation terms
|
|
140
|
+
- Signal quality: SNR, tSNR, CNR, variance-related
|
|
141
|
+
- Artifacts: Outlier counts, spike detection, artifact indices
|
|
142
|
+
|
|
143
|
+
2. **Determine directionality**:
|
|
144
|
+
- Higher-is-better: SNR, tSNR, CNR
|
|
145
|
+
- Lower-is-better: FD, DVARS, artifact indices, outlier counts
|
|
146
|
+
|
|
147
|
+
3. **Establish thresholds**:
|
|
148
|
+
- Plot distribution, identify outliers
|
|
149
|
+
- If metric has known analog, use those thresholds
|
|
150
|
+
- Otherwise: use ±2-3 SD from mean as starting point
|
|
151
|
+
|
|
152
|
+
4. **Validate**:
|
|
153
|
+
- Cross-reference with visual inspection
|
|
154
|
+
- Check correlation with known metrics
|
|
155
|
+
- Verify excluded subjects are actually problematic
|
|
156
|
+
|
|
157
|
+
## Population-Specific Considerations
|
|
158
|
+
|
|
159
|
+
### Infants (0-24 months)
|
|
160
|
+
- Higher baseline motion expected; adjust FD thresholds upward
|
|
161
|
+
- Shorter usable data segments acceptable
|
|
162
|
+
- Age-appropriate templates critical for registration QC
|
|
163
|
+
- Sleep state affects data quality (deep sleep preferred)
|
|
164
|
+
|
|
165
|
+
### Pediatric (3-12 years)
|
|
166
|
+
- Motion decreases with age; consider age as covariate
|
|
167
|
+
- Task compliance affects data quality
|
|
168
|
+
- Mock scanner training reduces motion
|
|
169
|
+
- Consider breaks during long protocols
|
|
170
|
+
|
|
171
|
+
### Adolescents
|
|
172
|
+
- Motion intermediate between children and adults
|
|
173
|
+
- Developmental stage affects hemodynamics
|
|
174
|
+
- Consider puberty stage as potential confound
|
|
175
|
+
|
|
176
|
+
### Clinical Populations
|
|
177
|
+
- Disease-specific considerations (lesions, atrophy)
|
|
178
|
+
- Medication effects on signal
|
|
179
|
+
- May need population-specific templates
|
|
180
|
+
- Balance data quality vs. already-reduced sample sizes
|
|
181
|
+
|
|
182
|
+
## Paradigm-Specific Considerations
|
|
183
|
+
|
|
184
|
+
### Resting-State
|
|
185
|
+
- Scrubbing viable (can remove timepoints)
|
|
186
|
+
- Need minimum continuous/total duration (≥5 min recommended)
|
|
187
|
+
- Strict motion thresholds (FD < 0.2-0.3 mm)
|
|
188
|
+
|
|
189
|
+
### Task fMRI
|
|
190
|
+
- Cannot arbitrarily remove timepoints
|
|
191
|
+
- Consider motion relative to task timing
|
|
192
|
+
- More lenient thresholds acceptable (FD < 0.5-0.9 mm)
|
|
193
|
+
- Ensure sufficient trials survive exclusion
|
|
194
|
+
|
|
195
|
+
### Naturalistic (movies, stories)
|
|
196
|
+
- Long durations increase motion likelihood
|
|
197
|
+
- Consider segment-wise QC
|
|
198
|
+
- Drift artifacts more relevant
|
|
199
|
+
|
|
200
|
+
### Sleep Studies
|
|
201
|
+
- State-dependent QC (arousal events)
|
|
202
|
+
- EEG quality for sleep staging
|
|
203
|
+
- Movement during state transitions
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# EEG/MEG QC Reference
|
|
2
|
+
|
|
3
|
+
Comprehensive guide for QC of EEG and MEG data, primarily using MNE-Python.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
1. [Channel-Level QC](#channel-level-qc)
|
|
7
|
+
2. [Epoch-Level QC](#epoch-level-qc)
|
|
8
|
+
3. [Artifact Detection](#artifact-detection)
|
|
9
|
+
4. [Subject-Level Exclusion](#subject-level-exclusion)
|
|
10
|
+
5. [Population Considerations](#population-considerations)
|
|
11
|
+
6. [Python Examples](#python-examples)
|
|
12
|
+
|
|
13
|
+
## Channel-Level QC
|
|
14
|
+
|
|
15
|
+
### Bad Channel Detection
|
|
16
|
+
|
|
17
|
+
**Indicators of bad channels:**
|
|
18
|
+
- Flat/dead channels (no signal variance)
|
|
19
|
+
- Noisy channels (excessive high-frequency activity)
|
|
20
|
+
- Channels with persistent artifacts
|
|
21
|
+
- Disconnected/bridged electrodes
|
|
22
|
+
|
|
23
|
+
**Detection strategies:**
|
|
24
|
+
|
|
25
|
+
1. **Amplitude-based**: Channels with abnormal variance
|
|
26
|
+
2. **Correlation-based**: Channels poorly correlated with neighbors
|
|
27
|
+
3. **Spectral-based**: Abnormal power spectrum shape
|
|
28
|
+
4. **Visual inspection**: Always recommended as final check
|
|
29
|
+
|
|
30
|
+
**MNE-Python thresholds (peak-to-peak):**
|
|
31
|
+
|
|
32
|
+
| Channel Type | Reject (Max PTP) | Flat (Min PTP) | Notes |
|
|
33
|
+
|--------------|-----------------|----------------|-------|
|
|
34
|
+
| EEG | 100-200 µV | 1 µV | Hardware-dependent |
|
|
35
|
+
| EOG | 200-250 µV | — | Blink detection |
|
|
36
|
+
| ECG | 5 mV | — | Heartbeat detection |
|
|
37
|
+
| MEG (magnetometer) | 3000-4000 fT | 1 fT | |
|
|
38
|
+
| MEG (gradiometer) | 3000-4000 fT/cm | 1 fT/cm | |
|
|
39
|
+
|
|
40
|
+
### Interpolation Guidelines
|
|
41
|
+
|
|
42
|
+
- **Maximum interpolatable channels**: 10% of total (e.g., ≤6 for 64-channel system)
|
|
43
|
+
- **Avoid interpolating**: Reference electrode neighbors, critical ROI channels
|
|
44
|
+
- **Method**: Spherical spline interpolation (MNE default)
|
|
45
|
+
- **Document**: Always report number and location of interpolated channels
|
|
46
|
+
|
|
47
|
+
## Epoch-Level QC
|
|
48
|
+
|
|
49
|
+
### Rejection Thresholds
|
|
50
|
+
|
|
51
|
+
**Standard EEG thresholds:**
|
|
52
|
+
```python
|
|
53
|
+
reject = dict(
|
|
54
|
+
eeg=100e-6, # 100 µV - conservative
|
|
55
|
+
# eeg=150e-6, # 150 µV - standard
|
|
56
|
+
# eeg=200e-6, # 200 µV - lenient
|
|
57
|
+
eog=200e-6 # 200 µV for EOG
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
flat = dict(
|
|
61
|
+
eeg=1e-6 # 1 µV minimum
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**MEG thresholds:**
|
|
66
|
+
```python
|
|
67
|
+
reject = dict(
|
|
68
|
+
mag=4000e-15, # 4000 fT
|
|
69
|
+
grad=4000e-13, # 4000 fT/cm
|
|
70
|
+
eog=200e-6
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
flat = dict(
|
|
74
|
+
mag=1e-15, # 1 fT
|
|
75
|
+
grad=1e-13 # 1 fT/cm
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Acceptable Epoch Loss
|
|
80
|
+
|
|
81
|
+
| Context | Acceptable Loss | Concern Level |
|
|
82
|
+
|---------|-----------------|---------------|
|
|
83
|
+
| High-trial designs (>200 per condition) | <30% | >40% |
|
|
84
|
+
| Standard designs (50-100 per condition) | <20% | >30% |
|
|
85
|
+
| Low-trial designs (<50 per condition) | <10% | >20% |
|
|
86
|
+
|
|
87
|
+
**Minimum trials for reliable ERPs:**
|
|
88
|
+
- Simple components (P1, N1): ~30-40 trials
|
|
89
|
+
- Later components (P300, N400): ~20-30 trials
|
|
90
|
+
- Complex conditions: varies by effect size
|
|
91
|
+
|
|
92
|
+
## Artifact Detection
|
|
93
|
+
|
|
94
|
+
### EOG (Eye Movement) Artifacts
|
|
95
|
+
|
|
96
|
+
**Types:**
|
|
97
|
+
- Blinks: Large amplitude, frontal distribution
|
|
98
|
+
- Saccades: Horizontal eye movements
|
|
99
|
+
|
|
100
|
+
**Detection (MNE):**
|
|
101
|
+
```python
|
|
102
|
+
# Find EOG events
|
|
103
|
+
eog_events = mne.preprocessing.find_eog_events(raw)
|
|
104
|
+
|
|
105
|
+
# Create EOG epochs for inspection/ICA
|
|
106
|
+
eog_epochs = mne.preprocessing.create_eog_epochs(raw)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Handling strategies:**
|
|
110
|
+
1. **Rejection**: Remove contaminated epochs (simple, loses data)
|
|
111
|
+
2. **ICA**: Remove EOG components (preserves more data)
|
|
112
|
+
3. **Regression**: Regress out EOG signal (SSP projections)
|
|
113
|
+
|
|
114
|
+
### ECG (Cardiac) Artifacts
|
|
115
|
+
|
|
116
|
+
**More prominent in MEG than EEG.**
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
ecg_events = mne.preprocessing.find_ecg_events(raw)
|
|
120
|
+
ecg_epochs = mne.preprocessing.create_ecg_epochs(raw)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Muscle Artifacts
|
|
124
|
+
|
|
125
|
+
**Characteristics:**
|
|
126
|
+
- High frequency (>20 Hz)
|
|
127
|
+
- Often temporal/neck electrodes
|
|
128
|
+
- Common during jaw clenching, swallowing
|
|
129
|
+
|
|
130
|
+
**Detection:**
|
|
131
|
+
- High-frequency power increase
|
|
132
|
+
- ICA component inspection
|
|
133
|
+
|
|
134
|
+
### Line Noise (50/60 Hz)
|
|
135
|
+
|
|
136
|
+
**Handling:**
|
|
137
|
+
```python
|
|
138
|
+
# Notch filter
|
|
139
|
+
raw_filtered = raw.notch_filter(freqs=[60, 120, 180]) # 60 Hz and harmonics
|
|
140
|
+
|
|
141
|
+
# Or use spectral interpolation for narrow-band removal
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Subject-Level Exclusion
|
|
145
|
+
|
|
146
|
+
### Exclusion Criteria
|
|
147
|
+
|
|
148
|
+
**Quantitative:**
|
|
149
|
+
- >50% epochs rejected
|
|
150
|
+
- >10% channels interpolated
|
|
151
|
+
- Insufficient trials per condition (<minimum for analysis)
|
|
152
|
+
- Technical failures (disconnected electrodes, recording errors)
|
|
153
|
+
|
|
154
|
+
**Qualitative (requires visual inspection):**
|
|
155
|
+
- Persistent artifacts not removable by ICA
|
|
156
|
+
- Unusual waveform morphology suggesting hardware issues
|
|
157
|
+
- Excessive drowsiness/sleep (for wake studies)
|
|
158
|
+
|
|
159
|
+
### QC Checklist
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
□ Raw data visual inspection (scrolling)
|
|
163
|
+
□ Power spectrum check (line noise, muscle)
|
|
164
|
+
□ Bad channel identification and interpolation
|
|
165
|
+
□ Artifact detection (EOG, ECG)
|
|
166
|
+
□ ICA component inspection (if used)
|
|
167
|
+
□ Epoch rejection summary
|
|
168
|
+
□ Final epoch count per condition
|
|
169
|
+
□ ERP/ERF waveform check
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Population Considerations
|
|
173
|
+
|
|
174
|
+
### Infants
|
|
175
|
+
|
|
176
|
+
**Unique challenges:**
|
|
177
|
+
- More movement artifacts
|
|
178
|
+
- Different reference electrode considerations
|
|
179
|
+
- Shorter attention spans → fewer trials
|
|
180
|
+
- Different frequency characteristics
|
|
181
|
+
|
|
182
|
+
**Adjusted thresholds:**
|
|
183
|
+
```python
|
|
184
|
+
# More lenient for infant EEG
|
|
185
|
+
reject_infant = dict(
|
|
186
|
+
eeg=200e-6, # 200 µV (vs 100-150 for adults)
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Minimum trials (infant ERPs):**
|
|
191
|
+
- May need only 10-20 good trials for robust components
|
|
192
|
+
- Consider trial-by-trial analysis approaches
|
|
193
|
+
|
|
194
|
+
### Clinical Populations
|
|
195
|
+
|
|
196
|
+
**Considerations:**
|
|
197
|
+
- Pathological activity may look like artifacts
|
|
198
|
+
- Medication effects on EEG
|
|
199
|
+
- May have reduced compliance / more movement
|
|
200
|
+
- Adjust thresholds based on population characteristics
|
|
201
|
+
|
|
202
|
+
### High-Density EEG (64+ channels)
|
|
203
|
+
|
|
204
|
+
- Can be more aggressive with channel interpolation
|
|
205
|
+
- ICA more effective with more channels
|
|
206
|
+
- Consider spatial filtering approaches
|
|
207
|
+
|
|
208
|
+
## Python Examples
|
|
209
|
+
|
|
210
|
+
### Complete QC Pipeline
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
import mne
|
|
214
|
+
import numpy as np
|
|
215
|
+
|
|
216
|
+
def run_eeg_qc(raw_path, events_path=None, event_id=None):
|
|
217
|
+
"""
|
|
218
|
+
Complete EEG QC pipeline.
|
|
219
|
+
|
|
220
|
+
Returns dict with QC metrics and processed data.
|
|
221
|
+
"""
|
|
222
|
+
# Load data
|
|
223
|
+
raw = mne.io.read_raw_fif(raw_path, preload=True)
|
|
224
|
+
|
|
225
|
+
# 1. Filter
|
|
226
|
+
raw.filter(l_freq=0.1, h_freq=40)
|
|
227
|
+
raw.notch_filter(freqs=[60])
|
|
228
|
+
|
|
229
|
+
# 2. Find bad channels by variance
|
|
230
|
+
data = raw.get_data(picks='eeg')
|
|
231
|
+
variance = np.var(data, axis=1)
|
|
232
|
+
z_var = (variance - np.mean(variance)) / np.std(variance)
|
|
233
|
+
bad_by_var = np.where(np.abs(z_var) > 3)[0]
|
|
234
|
+
|
|
235
|
+
# 3. Mark bad channels
|
|
236
|
+
eeg_names = raw.ch_names
|
|
237
|
+
bad_channels = [eeg_names[i] for i in bad_by_var if eeg_names[i].startswith('EEG')]
|
|
238
|
+
raw.info['bads'].extend(bad_channels)
|
|
239
|
+
|
|
240
|
+
# 4. Interpolate bad channels
|
|
241
|
+
if len(raw.info['bads']) > 0:
|
|
242
|
+
raw.interpolate_bads(reset_bads=True)
|
|
243
|
+
|
|
244
|
+
# 5. Re-reference (average reference)
|
|
245
|
+
raw.set_eeg_reference('average')
|
|
246
|
+
|
|
247
|
+
# 6. Create epochs (if events provided)
|
|
248
|
+
qc_results = {
|
|
249
|
+
'n_bad_channels': len(bad_channels),
|
|
250
|
+
'bad_channels': bad_channels,
|
|
251
|
+
'n_total_channels': len([ch for ch in raw.ch_names if ch.startswith('EEG')]),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if events_path and event_id:
|
|
255
|
+
events = mne.read_events(events_path)
|
|
256
|
+
|
|
257
|
+
reject = dict(eeg=150e-6)
|
|
258
|
+
flat = dict(eeg=1e-6)
|
|
259
|
+
|
|
260
|
+
epochs = mne.Epochs(
|
|
261
|
+
raw, events, event_id,
|
|
262
|
+
tmin=-0.2, tmax=0.8,
|
|
263
|
+
reject=reject, flat=flat,
|
|
264
|
+
preload=True
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Calculate epoch stats
|
|
268
|
+
n_total = len(events)
|
|
269
|
+
n_dropped = n_total - len(epochs)
|
|
270
|
+
|
|
271
|
+
qc_results.update({
|
|
272
|
+
'n_epochs_total': n_total,
|
|
273
|
+
'n_epochs_dropped': n_dropped,
|
|
274
|
+
'epoch_rejection_rate': 100 * n_dropped / n_total,
|
|
275
|
+
'epochs_per_condition': {k: len(epochs[k]) for k in event_id.keys()},
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
return qc_results, raw, epochs
|
|
279
|
+
|
|
280
|
+
return qc_results, raw, None
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def check_subject_inclusion(qc_results,
|
|
284
|
+
max_bad_channels_pct=10,
|
|
285
|
+
max_epoch_rejection_pct=30,
|
|
286
|
+
min_epochs_per_cond=30):
|
|
287
|
+
"""
|
|
288
|
+
Check if subject meets inclusion criteria.
|
|
289
|
+
"""
|
|
290
|
+
include = True
|
|
291
|
+
reasons = []
|
|
292
|
+
|
|
293
|
+
# Check bad channels
|
|
294
|
+
bad_pct = 100 * qc_results['n_bad_channels'] / qc_results['n_total_channels']
|
|
295
|
+
if bad_pct > max_bad_channels_pct:
|
|
296
|
+
include = False
|
|
297
|
+
reasons.append(f"Bad channels: {bad_pct:.1f}% > {max_bad_channels_pct}%")
|
|
298
|
+
|
|
299
|
+
# Check epoch rejection
|
|
300
|
+
if 'epoch_rejection_rate' in qc_results:
|
|
301
|
+
if qc_results['epoch_rejection_rate'] > max_epoch_rejection_pct:
|
|
302
|
+
include = False
|
|
303
|
+
reasons.append(f"Epoch rejection: {qc_results['epoch_rejection_rate']:.1f}% > {max_epoch_rejection_pct}%")
|
|
304
|
+
|
|
305
|
+
# Check epochs per condition
|
|
306
|
+
for cond, n in qc_results['epochs_per_condition'].items():
|
|
307
|
+
if n < min_epochs_per_cond:
|
|
308
|
+
include = False
|
|
309
|
+
reasons.append(f"Insufficient epochs for {cond}: {n} < {min_epochs_per_cond}")
|
|
310
|
+
|
|
311
|
+
return include, reasons
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### ICA-Based Artifact Removal
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
def run_ica_artifact_removal(raw, n_components=15):
|
|
318
|
+
"""
|
|
319
|
+
ICA-based artifact removal for EOG and ECG.
|
|
320
|
+
"""
|
|
321
|
+
from mne.preprocessing import ICA
|
|
322
|
+
|
|
323
|
+
# Fit ICA
|
|
324
|
+
ica = ICA(n_components=n_components, random_state=42)
|
|
325
|
+
ica.fit(raw)
|
|
326
|
+
|
|
327
|
+
# Find EOG-related components
|
|
328
|
+
eog_indices, eog_scores = ica.find_bads_eog(raw)
|
|
329
|
+
|
|
330
|
+
# Find ECG-related components (if ECG channel available)
|
|
331
|
+
try:
|
|
332
|
+
ecg_indices, ecg_scores = ica.find_bads_ecg(raw)
|
|
333
|
+
except:
|
|
334
|
+
ecg_indices = []
|
|
335
|
+
|
|
336
|
+
# Mark components for exclusion
|
|
337
|
+
ica.exclude = list(set(eog_indices + ecg_indices))
|
|
338
|
+
|
|
339
|
+
# Apply ICA
|
|
340
|
+
raw_clean = ica.apply(raw.copy())
|
|
341
|
+
|
|
342
|
+
return raw_clean, ica, {
|
|
343
|
+
'n_eog_components': len(eog_indices),
|
|
344
|
+
'n_ecg_components': len(ecg_indices),
|
|
345
|
+
'eog_indices': eog_indices,
|
|
346
|
+
'ecg_indices': ecg_indices,
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Generate QC Report
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
def generate_eeg_qc_report(qc_results, output_path):
|
|
354
|
+
"""Generate text QC report."""
|
|
355
|
+
|
|
356
|
+
report = f"""
|
|
357
|
+
EEG Quality Control Report
|
|
358
|
+
==========================
|
|
359
|
+
|
|
360
|
+
Channel Quality
|
|
361
|
+
---------------
|
|
362
|
+
Total EEG channels: {qc_results['n_total_channels']}
|
|
363
|
+
Bad channels identified: {qc_results['n_bad_channels']}
|
|
364
|
+
Bad channel rate: {100 * qc_results['n_bad_channels'] / qc_results['n_total_channels']:.1f}%
|
|
365
|
+
Bad channel names: {', '.join(qc_results['bad_channels']) if qc_results['bad_channels'] else 'None'}
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
if 'n_epochs_total' in qc_results:
|
|
369
|
+
report += f"""
|
|
370
|
+
Epoch Quality
|
|
371
|
+
-------------
|
|
372
|
+
Total epochs: {qc_results['n_epochs_total']}
|
|
373
|
+
Rejected epochs: {qc_results['n_epochs_dropped']}
|
|
374
|
+
Rejection rate: {qc_results['epoch_rejection_rate']:.1f}%
|
|
375
|
+
|
|
376
|
+
Epochs per condition:
|
|
377
|
+
"""
|
|
378
|
+
for cond, n in qc_results['epochs_per_condition'].items():
|
|
379
|
+
report += f" {cond}: {n}\n"
|
|
380
|
+
|
|
381
|
+
if 'n_eog_components' in qc_results:
|
|
382
|
+
report += f"""
|
|
383
|
+
ICA Artifact Removal
|
|
384
|
+
--------------------
|
|
385
|
+
EOG components removed: {qc_results['n_eog_components']}
|
|
386
|
+
ECG components removed: {qc_results['n_ecg_components']}
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
with open(output_path, 'w') as f:
|
|
390
|
+
f.write(report)
|
|
391
|
+
|
|
392
|
+
return report
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Key References
|
|
396
|
+
|
|
397
|
+
- Jas M et al. (2017). Autoreject: Automated artifact rejection for MEG and EEG data. NeuroImage 159:417-429.
|
|
398
|
+
- Gramfort A et al. (2013). MEG and EEG data analysis with MNE-Python. Frontiers in Neuroscience 7:267.
|
|
399
|
+
- Delorme A & Makeig S. (2004). EEGLAB: an open source toolbox for analysis of single-trial EEG dynamics. Journal of Neuroscience Methods 134(1):9-21.
|
|
400
|
+
- Luck SJ. (2014). An Introduction to the Event-Related Potential Technique. MIT Press.
|