-
Notifications
You must be signed in to change notification settings - Fork 0
Visualization
processFNIRS2 ships a plotting stack that spans single-subject timeseries and
topographic maps, publication-quality 3D cortical renders, statistical
projections, connectivity/synchrony diagrams, and group-level plots from
exploreFNIRS. This page is a practical catalog of the main entry points —
for group-analysis plot types (plotTemporal, plotBar, plotLME, ...) see
Group Analysis.
pf2.data.plot.oxy plots hemoglobin concentration time series, with channel
selection, biomarker choice, baseline correction, and marker overlay:
data = pf2.import.sampleData.fNIR2000();
processed = processFNIRS2(data);
pf2.data.plot.oxy(processed); % all channels, arranged by probe geometry
pf2.data.plot.oxy(processed, 5); % single channel
pf2.data.plot.oxy(processed, 1:4); % specific channels
pf2.data.plot.oxy(processed, 5, 'biomarkers', 'all'); % HbO, HbR, HbDiff, HbTotal, CBSI
pf2.data.plot.oxy(processed, 'baseline', 10); % subtract mean of first 10 s
pf2.data.plot.oxy(processed, 1:4, 'ylim', [-5 5]); % fixed y-axis
pf2.data.plot.oxy(processed, 1:4, 'markers', 10); % show only marker code 10
% Headless save (2D plots): built-in savePath option
fig = pf2.data.plot.oxy(processed, 1:4, ...
'savePath', 'oxy_channels.png', 'saveWidth', 800, 'saveHeight', 400, 'saveDPI', 150);
close(fig);The raw-intensity counterpart is pf2.data.plot.raw(data, ...) — same channel
selection and savePath options, plus 'wavelengths', 730 to view a single
wavelength before any processing.
pf2.probe.plot.topo is the shortest path from processed data to a
topographic activation map. It pulls a per-channel value for the requested
biomarker (at a time point, over a window, or time-averaged) and renders it
as a 2D probe heatmap, a 3D cortical projection, or a time-animated movie:
pf2.probe.plot.topo(processed, 'HbO'); % 2D heatmap, time-averaged
pf2.probe.plot.topo(processed, 'HbO', 'Time', 30); % snapshot at t = 30 s
pf2.probe.plot.topo(processed, 'HbO', 'Time', [20 40]); % window mean, 20-40 s
pf2.probe.plot.topo(processed, 'HbO', 'View', '3d'); % cortical surface
pf2.probe.plot.topo(processed, 'HbO', 'View', 'movie', ...
'Time', [0 40], 'savePath', 'topo_movie.mp4'); % shortcut to pf2.probe.plot.movie
pf2.probe.plot.topo(processed, 'HbO', 'Time', 30, 'savePath', 'topo.png');View |
Renders via |
|---|---|
'2d' (default) |
pf2.probe.plot.imageValues — flat probe heatmap |
'3d' |
pf2.probe.project.biomarker — cortical surface (needs MNI coords) |
'movie' |
pf2.probe.plot.movie — animates over Time/TimeRange (pass savePath, FPS, NFrames) |
Underlying primitives (data2plot is a [1 x C] vector, not a biomarker
name):
meanHbO = mean(processed.HbO, 1);
pf2.probe.plot.imageValues(meanHbO, processed, [], [], 'Mean HbO', '\muM'); % 2D
pf2.probe.plot.interpolateValues3D(meanHbO, processed, [], [], 'Mean HbO', '\muM'); % 3DThe 3D surface renderer (pf2.probe.plot.interpolateValues3D, and everything
built on it — topo 'View','3d', pf2.probe.project.*) supports a 'Style'
option controlled by pf2_base.plot.RenderStyle:
| Style | Look |
|---|---|
'showcase' (default for the 3D renderer) |
Procedural matcap shading, neutral-gray cortex so activation pops, sulcal ambient occlusion, an elevated 3/4 "hero" camera view, 2x supersampled export. MRIcroGL/Surfice-inspired. |
'publication' |
Smooth Gouraud matte cortex, gentle ambient occlusion, a conservative data-facing view. (Note: RenderStyle.get() itself defaults to 'publication' when called with no argument — pass the name explicitly to avoid ambiguity.) |
data = pf2.import.sampleData.fNIR2000();
proc = processFNIRS2(data);
vals = mean(proc.HbO, 1, 'omitnan');
pf2.probe.plot.interpolateValues3D(vals, proc, 'Style', 'showcase'); % default look
pf2.probe.plot.interpolateValues3D(vals, proc, 'Style', 'publication'); % conservative look
% Override individual fields on a resolved style struct
sty = pf2_base.plot.RenderStyle.get('showcase');
sty.matcapMaterial = 'porcelain'; % clay | porcelain | matte | glossy | pewter | jade
sty.aoStrength = 0.5; % 0..0.8, sulcal ambient-occlusion strength
pf2.probe.plot.interpolateValues3D(vals, proc, 'Style', sty);Colormap names resolve through pf2_base.plot.brainColormap: MRIcroGL/Surfice
LUTs ('actc', 'warm', 'cool', 'blue2red', 'bone') plus
perceptually-uniform, colour-blind-safe defaults ('rdbu' diverging,
'viridis', 'cividis', or any MATLAB built-in name):
pf2.probe.plot.interpolateValues3D(vals, proc, 'cmap', 'rdbu');
pf2.probe.plot.interpolateValues3D(vals, proc, 'cmap', 'actc');For interactive tuning, pf2.probe.plot.Explore3D(proc) opens a live preview
with controls for style, matcap material, ambient-occlusion strength, camera
view, colormap, interpolation, biomarker, and time point — every change
re-renders immediately and the panel prints the equivalent command for
copy-paste into a script.
pf2.probe.plot.Explore3D(proc); % interactive; skip under -batchpf2.probe.plot.movie sweeps a biomarker over time and writes an MP4, AVI,
or GIF, with a single shared color scale across frames and an optional
time/marker stamp per frame:
pf2.probe.plot.movie(processed, 'HbO', 'savePath', 'hbo.mp4'); % 3D cortex (default)
pf2.probe.plot.movie(processed, 'HbO', 'View', '2d', 'savePath', 'hbo.gif'); % flat 2D heatmap
pf2.probe.plot.movie(processed, 'HbO', ...
'TimeRange', [0 40], 'NFrames', 80, 'FPS', 20, 'savePath', 'hbo.mp4');The file extension on savePath selects the writer (.mp4 -> MPEG-4, .avi
-> Motion JPEG AVI, .gif -> animated GIF, paletted/lower-fidelity — prefer
MP4 for smooth gradients). Key options: TimeRange, NFrames (target frame
count, default 60) or FrameStep (explicit stride), Window (per-frame
averaging), FPS, Range (fixed color limits). pf2.probe.plot.topo(..., 'View', 'movie', ...) is a shortcut that delegates here.
Each wrapper projects a per-channel statistic onto the 3D cortical surface,
handling colormap, range, significance thresholding, and transparency of
non-significant channels for you. All accept savePath for headless saving
and forward camera/style options (initCamPosition, Style,
ForceLightMode, ...) to interpolateValues3D.
| Function | Projects | Notes |
|---|---|---|
pf2.probe.project.pvalues(pvals, data) |
Per-channel p-values, as -log10(p)
|
'pThreshold', 'FDR', 'PTicks'; non-significant channels render transparent |
pf2.probe.project.fstats(Fvals, data) |
F-statistics (positive-only) | Threshold via companion 'pvalues'/'pThreshold' or an explicit 'Fcritical'
|
pf2.probe.project.correlation(rho, data) |
Signed correlation in [-1, 1] | Two-colorbar diverging treatment; 'pvalues', 'DeadZone'
|
pf2.probe.project.biomarker(vals, data) |
Signed biomarker values (HbO, HbR, a contrast) | Diverging two-colorbar; 'Range', 'DeadZone', 'pvalues'; 'interpolateType','sensitivity' for a Gaussian optical-sensitivity kernel instead of nearest/IDW |
pf2.probe.project.counts(Nvals, data) |
Per-channel sample counts (N) | Sequential, positive-only colorbar — coverage after rejection |
pf2.probe.project.regions(regionValues, data) |
Per-Brodmann-region values, flat-filled | Needs data.canonical from pf2.probe.canonicalize; accepts a biomarker name (takes the time-mean) or an explicit [1 x R] vector |
pf2.probe.project.parcels(data) / parcels(channelValues, data)
|
Per-channel Voronoi "parcel" cells (nearest-optode) | A channel-assignment cartoon, NOT DOT/image reconstruction; 'Highlight' prints a subset of optode numbers |
pf2.probe.project.pmdf(vals, data) |
Channel values through the physical PMDF sensitivity footprint | The "honest banana" — masked to real depth-dependent coverage, vs. biomarker's Gaussian lateral kernel |
pf2.probe.project.tomography(recon) |
A DOT image reconstruction (pf2.probe.dot.reconstruct output) |
Masked to sensitivity coverage; see Diffuse Optical Tomography |
raw = pf2.import.sampleData.fNIR2000();
processed = processFNIRS2(raw);
K = size(processed.HbO, 2);
pvals = rand(1, K);
Fvals = abs(randn(1, K)) * 2 + 1;
rho = 2 * rand(1, K) - 1;
pf2.probe.project.pvalues(pvals, processed, 'pThreshold', 0.05, 'FDR', true);
pf2.probe.project.fstats(Fvals, processed, 'pvalues', pvals, 'pThreshold', 0.05);
pf2.probe.project.correlation(rho, processed, 'pvalues', pvals, 'pThreshold', 0.05);
pf2.probe.project.biomarker(processed.HbO(100, :), processed, 'Range', [-1 1]);
% Anatomical parcels need canonicalized channels
procC = pf2.probe.canonicalize(processed, 'MaxDistance', 25);
pf2.probe.project.regions('HbO', procC); % time-mean per Brodmann region
% Headless save (all project.* functions)
pf2.probe.project.fstats(Fvals, processed, 'Fcritical', 3.84, 'savePath', 'fstats.png');pf2.probe.plot.connectome draws a connectivity matrix as edges anchored at
real channel/ROI positions on the cortex (3D) or flat probe layout (2D) —
spatial structure (front-back, long- vs short-range) is directly visible:
conn = exploreFNIRS.connectivity.computeMatrix(processed, 'Method', 'pearson');
pf2.probe.plot.connectome(conn, processed, 'View', '3d', 'TopN', 30);
pf2.probe.plot.connectome(conn, processed, 'View', '2d', 'Threshold', 0.5);exploreFNIRS.connectivity.plotChord draws the same kind of result as a
circular chord diagram (nodes on a ring, Bezier arcs for coupled pairs) —
better for a compact all-pairs overview than for spatial layout. Nodes can be
colored by a per-node statistic with their own colorbar:
exploreFNIRS.connectivity.plotChord(conn); % flat nodes, sign-colored arcs
exploreFNIRS.connectivity.plotChord(conn, 'NodeValues', 'auto', ... % signed node strength + colorbar
'GroupLabels', 'auto');exploreFNIRS.hyperscanning.plotDualBrain places two subjects' probes side
by side at their real anatomical layout with cross-brain synchrony edges
between them, optionally with a linked wavelet-coherence panel:
A = processed; B = processFNIRS2(pf2.import.sampleData.fNIR2000());
dyad = exploreFNIRS.hyperscanning.computeDyad(A, B, 'ChannelPairing', 'all');
wc = exploreFNIRS.coupling.wcoherence(A.HbO(:,1), B.HbO(:,1), A.fs);
exploreFNIRS.hyperscanning.plotDualBrain(dyad, A, B, 'TopN', 30, 'Wcoherence', wc, ...
'BrainLabels', {'Child', 'Parent'}, 'SavePath', 'dualbrain.png');See Group Analysis for exploreFNIRS.connectivity.computeMatrix
and exploreFNIRS.hyperscanning.computeDyad themselves.
pf2.probe.plot.showProbe3D overlays source/detector optodes on a 3D brain
surface — the quickest way to sanity-check probe placement:
pf2.probe.plot.showProbe3D(processed);
pf2.probe.plot.showProbe3D(processed, 'initCamPosition', 'top'); % also: front, top-left, front-left, ...pf2.probe.plot.showHead3D renders the Colin27 head model in real MNI
millimetres: a translucent scalp shell over a brain surface, with the probe
overlaid. This is a qualitative visualization/QC aid (not a forward/DOT
model):
pf2.probe.plot.showHead3D(processed); % scalp + brain + sources/detectors
pf2.probe.plot.showHead3D(processed, 'ShowScalp', false); % brain only
pf2.probe.plot.showHead3D(processed, 'Markers', 'channels'); % channel midpoints instead of optodes
pf2.probe.plot.showHead3D(processed, 'savePath', 'head.png');Multi-subject plots (plotTemporal, plotBar, plotScatter, plotHeatmap,
plotTopo, plotLME/plotTopoLME, plotComposite) live on the
exploreFNIRS.core.Experiment class and share a common Colors/
ColorScheme and SavePath/SaveWidth/SaveHeight/SaveDPI convention.
See Group Analysis for the full option catalog; in brief:
[ex, allData] = pf2.import.sampleData.group();
ex.groupby({'Group'}); ex.aggregate();
ex.plotTemporal('Biomarkers', {'HbO'}, 'ErrorType', 'SEM');
ex.plotBar('Biomarker', 'HbO', 'ShowIndividual', true);
ex.plotHeatmap('Biomarker', 'HbO', 'Colormap', 'RdBu');
[fig, results] = ex.plotLME('Biomarkers', {'HbO'}, 'SigType', 'q'); % FDR-corrected F-statsColor customization applies to every plot type — an [N x 3] RGB matrix, a
named palette ('Set1', 'tab10', 'viridis'), or an
exploreFNIRS.core.ColorScheme for hierarchical per-factor colors:
cs = exploreFNIRS.core.ColorScheme();
cs = cs.set('Group', 'Young', [0.2, 0.6, 0.9]);
cs = cs.set('Condition', 'Task', 'darken', 0.15);
ex.colorScheme = cs; % auto-applies to all subsequent plots
cs.preview(); % preview resolved colors before plotting2D plots (pf2.data.plot.oxy/raw, imageValues, topo with the
default '2d' view, group-analysis plots): either pattern works —
fig = figure('Visible', 'off');
pf2.data.plot.oxy(processed);
saveas(fig, 'output.png');or (preferred) the built-in 'savePath' option, which every plotting
function above accepts.
3D renders (showProbe3D, showHead3D, interpolateValues3D,
topo 'View','3d', movie, and every pf2.probe.project.* function) must
use the built-in savePath option, or capture imgOut and imwrite it. The
generic off-screen figure('Visible','off') + saveas/exportgraphics
pattern is unreliable for 3D — its failure mode is platform-dependent: a
blank image on some headless setups (Linux/SSH, software GL), or a
dark-background image plus an exit-time crash under macOS -nodisplay.
pf2.probe.project.fstats(Fvals, processed, 'savePath', 'fstats.png'); % correct, stable
[~, imgOut] = pf2.probe.plot.interpolateValues3D(vals, processed);
imwrite(imgOut, 'brain.png'); % also correctAll plots automatically adapt to MATLAB's dark mode theme on screen, but saved figures always export with a white background regardless of theme. A persistent preference can force light mode everywhere:
pf2_base.plot.PlotStyle.setForceLightMode(true); % disable dark mode for pf2
pf2_base.plot.PlotStyle.setForceLightMode(false); % re-enable theme detection3D renders also accept a per-call 'ForceLightMode', true override (white
background, dark axes/labels) without touching the persistent preference —
useful for headless batch scripts that render into figures created with
'Color', 'w'.
-
Group Analysis —
Experimentclass plots, LME topo maps, connectivity/hyperscanning computation -
Diffuse Optical Tomography — PMDF sensitivity,
dot.reconstruct,project.tomography - Quality Control — QC dashboards and PSD/SCI plots
- Block Averaging and Epoching — building the segments these plots visualize
processFNIRS2
Getting Started
Core Workflow
Group Analysis
Visualization & Export
Reference