Calculations/Data-Analyzer/+Plotter/plotFitParameterPDF.m

169 lines
5.8 KiB
Matlab
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

function plotFitParameterPDF(fitResults, scanValues, paramName, varargin)
%% plotFitParameterPDF
% Author: Karthik
% Date: 2025-10-06
% Version: 1.2
%
% Description:
% Plots 2D PDF (heatmap) of any parameter from two-Gaussian fit results
% for multiple scan parameters, with optional overlay of mean ± SEM.
%
% Inputs:
% fitResults - struct array from fitTwoGaussianCurves
% scanValues - vector of scan parameter values
% paramName - string specifying the parameter to plot:
% 'A1', 'mu1', 'sigma1', 'A2', 'mu2', 'sigma2'
%
% Optional name-value pairs (same as before, plus OverlayMeanSEM):
% 'OverlayMeanSEM' - logical, overlay mean ± SEM (default: true)
% --- Parse optional inputs ---
p = inputParser;
addParameter(p, 'Title', '', @(x) ischar(x) || isstring(x));
addParameter(p, 'XLabel', '', @(x) ischar(x) || isstring(x));
addParameter(p, 'YLabel', '', @(x) ischar(x) || isstring(x));
addParameter(p, 'FigNum', 1, @(x) isscalar(x));
addParameter(p, 'FontName', 'Arial', @ischar);
addParameter(p, 'FontSize', 14, @isnumeric);
addParameter(p, 'SkipSaveFigures', false, @islogical);
addParameter(p, 'SaveFileName', 'FitParameterPDF.fig', @ischar);
addParameter(p, 'SaveDirectory', pwd, @ischar);
addParameter(p, 'NumPoints', 200, @(x) isscalar(x));
addParameter(p, 'DataRange', [], @(x) isempty(x) || numel(x)==2);
addParameter(p, 'XLim', [], @(x) isempty(x) || numel(x)==2);
addParameter(p, 'YLim', [], @(x) isempty(x) || numel(x)==2);
addParameter(p, 'Colormap', @jet);
addParameter(p, 'PlotType', 'histogram', @(x) any(validatestring(x,{'kde','histogram'})));
addParameter(p, 'NumberOfBins', 50, @isscalar);
addParameter(p, 'NormalizeHistogram', true, @islogical);
addParameter(p, 'OverlayMeanSEM', true, @islogical);
parse(p, varargin{:});
opts = p.Results;
% --- Map paramName to index ---
paramMap = struct('A1',1,'mu1',2,'sigma1',3,'A2',4,'mu2',5,'sigma2',6);
if ~isfield(paramMap,paramName)
error('Invalid paramName. Must be one of: A1, mu1, sigma1, A2, mu2, sigma2');
end
paramIdx = paramMap.(paramName);
% --- Determine repetitions and scan parameters ---
N_params = numel(scanValues);
N_total = numel(fitResults);
N_reps = N_total / N_params;
% --- Extract chosen parameter values ---
paramValues = nan(N_reps, N_params);
for k = 1:N_total
paramIdxScan = mod(k-1, N_params) + 1;
repIdx = floor((k-1)/N_params) + 1;
paramValues(repIdx, paramIdxScan) = fitResults(k).pFit(paramIdx);
end
% --- Create mask of true zeros (from fillZeroStruct) ---
trueZeroMask = false(size(paramValues));
for i = 1:N_total
paramIdxScan = mod(i-1, N_params) + 1;
repIdx = floor((i-1)/N_params) + 1;
if ~fitResults(i).isValid && all(fitResults(i).pFit == 0)
trueZeroMask(repIdx, paramIdxScan) = true;
end
end
% --- Prepare data per scan parameter ---
dataCell = cell(N_params,1);
for i = 1:N_params
dataCell{i} = paramValues(:,i);
end
% --- Determine y-range ---
if isempty(opts.DataRange)
allData = cell2mat(dataCell(:));
y_min = min(allData);
y_max = max(allData);
else
y_min = opts.DataRange(1);
y_max = opts.DataRange(2);
end
% --- Prepare PDF grid/matrix ---
if strcmpi(opts.PlotType,'kde')
y_grid = linspace(y_min, y_max, opts.NumPoints);
pdf_matrix = zeros(numel(y_grid), N_params);
else
edges = linspace(y_min, y_max, opts.NumberOfBins+1);
binCenters = (edges(1:end-1) + edges(2:end))/2;
pdf_matrix = zeros(numel(binCenters), N_params);
end
% --- Compute PDFs ---
for i = 1:N_params
data = dataCell{i};
data = data(~isnan(data));
if isempty(data), continue; end
if strcmpi(opts.PlotType,'kde')
f = ksdensity(data, y_grid);
pdf_matrix(:,i) = f;
else
counts = histcounts(data, edges);
if opts.NormalizeHistogram
binWidth = edges(2) - edges(1);
counts = counts / (sum(counts) * binWidth);
end
pdf_matrix(:,i) = counts(:);
end
end
% --- Mask out non-data regions (make them white) ---
dataMask = ~isnan(paramValues); % where valid fits exist
maskPerScan = any(dataMask,1); % scans that have any valid data
pdf_matrix(:, ~maskPerScan) = NaN; % make empty scans white
% --- Plot heatmap ---
fig = figure(opts.FigNum); clf(fig);
set(fig, 'Color', 'w', 'Position', [100 100 950 750]);
if strcmpi(opts.PlotType,'kde')
h = imagesc(scanValues, y_grid, pdf_matrix);
set(h, 'AlphaData', ~isnan(pdf_matrix)); % make NaNs transparent
else
h = imagesc(scanValues, binCenters, pdf_matrix);
set(h, 'AlphaData', ~isnan(pdf_matrix)); % make NaNs transparent
end
set(gca, 'YDir', 'normal', 'FontName', opts.FontName, 'FontSize', opts.FontSize);
xlabel(opts.XLabel, 'FontSize', opts.FontSize, 'FontName', opts.FontName);
ylabel(opts.YLabel, 'FontSize', opts.FontSize, 'FontName', opts.FontName);
title(opts.Title, 'FontSize', opts.FontSize+2, 'FontWeight', 'bold');
% --- Colormap and colorbar ---
cmap = feval(opts.Colormap);
colormap([1 1 1; cmap]); % prepend white for NaN regions
c = colorbar;
if strcmpi(opts.PlotType,'kde') || opts.NormalizeHistogram
ylabel(c, 'Probability Density', 'Rotation', -90, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
else
ylabel(c, 'Counts', 'Rotation', -90, 'FontName', opts.FontName, 'FontSize', opts.FontSize);
end
if ~isempty(opts.XLim)
xlim(opts.XLim);
end
if ~isempty(opts.YLim)
ylim(opts.YLim);
end
% --- Overlay mean ± SEM if requested ---
if opts.OverlayMeanSEM
meanParam = nanmean(paramValues,1);
semParam = nanstd(paramValues,0,1) ./ sqrt(sum(~isnan(paramValues),1));
hold on;
xVec = reshape(scanValues, 1, []); % ensures 1 × N_params
fill([xVec, fliplr(xVec)], [meanParam - semParam, fliplr(meanParam + semParam)], ...
[0.2 0.4 0.8], 'FaceAlpha',0.2, 'EdgeColor','none');
plot(scanValues, meanParam, 'k-', 'LineWidth', 2);
end
end