function runInteractiveASDTwoGaussianFitGUI(spectral_analysis_results) %% runInteractiveASDTwoGaussianFitGUI % Author: Karthik % Date: 2025-10-16 % Version: 1.0 % % Description: % Interactive viewer for raw angular spectral distributions. % Lets user browse through individual curves and refit two-Gaussian models % live as threshold parameters are adjusted. % % Controls: % - Slider or ←/→ keys to browse curves % - Editable boxes for thresholds (Residual, Position, Amplitude, MaxTheta) % - "Re-fit" button triggers recomputation % % Dependencies: % Requires Analyzer.fitTwoGaussianCurvesToAngularSpectralDistribution.m %% --- Initialization --- rawCurves.x = spectral_analysis_results.S_theta_norm_all; rawCurves.theta = spectral_analysis_results.theta_vals; % Handle input organization if iscell(rawCurves.x) Ncurves = numel(rawCurves.x); else Ncurves = size(rawCurves.x, 1); tmp = arrayfun(@(i) struct('x', rawCurves.x(i, :), 'theta', rawCurves.theta(:)'), 1:Ncurves); rawCurves = tmp(:); end % Default fit parameters params.MaxTheta = pi/2; params.ResidualThreshold = 0.15; params.PositionThreshold = pi/15; params.AmplitudeThreshold = 0.15; currentIdx = 1; %% --- Create figure --- hFig = findobj('Type','figure','Tag','InteractiveTwoGaussianFit'); if isempty(hFig) hFig = figure('Name','Angular Spectra Fit Viewer',... 'NumberTitle','off',... 'Position',[100 100 1250 800],... 'Color','w',... 'KeyPressFcn',@keyPressCallback,... 'Tag','InteractiveTwoGaussianFit'); else figure(hFig); clf(hFig); end %% --- Axes for plot --- hAx = axes('Parent',hFig,'Position',[0.08 0.1 0.55 0.8]); grid(hAx,'on'); box(hAx,'on'); xlabel(hAx,'\theta (rad)','FontSize',12); ylabel(hAx,'Normalized amplitude','FontSize',12); %% --- Slider for curve index --- hSlider = uicontrol('Style','slider','Min',1,'Max',Ncurves,'Value',1,... 'SliderStep',[1/(Ncurves-1), 10/(Ncurves-1)],... 'Units','normalized','Position',[0.1 0.02 0.5 0.03],... 'Callback',@(~,~) updatePlot()); %% --- Parameter definitions --- paramNames = {'MaxTheta','ResidualThreshold','PositionThreshold','AmplitudeThreshold'}; paramLabels = {'Max Theta (rad)', 'Residual Threshold', 'Position Threshold (rad)', 'Amplitude Threshold'}; paramDescs = {'Upper limit for fitting range',... 'Maximum mean residual for valid fits',... 'Minimum peak separation required',... 'Minimum amplitude ratio threshold'}; nParams = numel(paramNames); hEdit = gobjects(nParams,1); baseY = 0.88; %% --- Create parameter controls --- for i = 1:nParams yPos = baseY - 0.12*(i-1); % Label uicontrol('Style','text','Units','normalized',... 'Position',[0.68 yPos 0.25 0.04],... 'String',paramLabels{i},... 'HorizontalAlignment','left',... 'FontSize',10,'FontWeight','bold',... 'BackgroundColor','w'); % Edit box hEdit(i) = uicontrol('Style','edit','Units','normalized',... 'Position',[0.9 yPos 0.07 0.045],... 'String',num2str(params.(paramNames{i}), '%.4f'),... 'Callback',@(src,~) applyParams()); % Description uicontrol('Style','text','Units','normalized',... 'Position',[0.68 yPos - 0.04 0.35 0.03],... 'String',paramDescs{i},... 'HorizontalAlignment','left',... 'FontSize',8,'ForegroundColor',[0.4 0.4 0.4],... 'BackgroundColor','w'); end %% --- Apply button --- uicontrol('Style','pushbutton','Units','normalized',... 'Position',[0.70 0.35 0.25 0.06],... 'String','Re-fit All Curves',... 'FontSize',14,'FontWeight','bold',... 'BackgroundColor',[0.2 0.6 1],... 'ForegroundColor','w',... 'Callback',@(~,~) refitAll()); %% --- Fit info text boxes --- infoLabels = {'Fit validity:', 'Peak positions (rad):', 'Amplitudes:'}; hInfoText = gobjects(numel(infoLabels),1); for i = 1:numel(infoLabels) yText = 0.22 - 0.07*(i-1); uicontrol('Style','text','Units','normalized',... 'Position',[0.68 yText 0.25 0.05],... 'String',infoLabels{i},... 'FontSize',10,'FontWeight','bold',... 'HorizontalAlignment','left',... 'BackgroundColor','w'); hInfoText(i) = uicontrol('Style','text','Units','normalized',... 'Position',[0.88 yText 0.1 0.05],... 'String','-','FontSize',10,... 'HorizontalAlignment','left',... 'BackgroundColor','w'); end %% --- Compute initial fits --- [fitResults, rawData] = Analyzer.fitTwoGaussianCurvesToAngularSpectralDistribution(... spectral_analysis_results.S_theta_norm_all,... spectral_analysis_results.theta_vals,... 'MaxTheta', params.MaxTheta,... 'ResidualThreshold', params.ResidualThreshold,... 'PositionThreshold', params.PositionThreshold,... 'AmplitudeThreshold', params.AmplitudeThreshold); %% --- Initial plot --- updatePlot(); %% --- Nested functions --- function applyParams() % Update parameters from GUI fields for j = 1:nParams val = str2double(hEdit(j).String); if ~isnan(val) params.(paramNames{j}) = val; end end refitAll(); end function refitAll() % Recompute all fits drawnow; [fitResults, rawData] = Analyzer.fitTwoGaussianCurvesToAngularSpectralDistribution(... spectral_analysis_results.S_theta_norm_all,... spectral_analysis_results.theta_vals,... 'MaxTheta', params.MaxTheta,... 'ResidualThreshold', params.ResidualThreshold,... 'PositionThreshold', params.PositionThreshold,... 'AmplitudeThreshold', params.AmplitudeThreshold); updatePlot(); end function updatePlot() idx = round(get(hSlider,'Value')); currentIdx = idx; cla(hAx); hold(hAx,'on'); raw = rawData(idx); plot(hAx, raw.theta, raw.x, 'k.-', 'LineWidth', 1, 'DisplayName', 'Raw Data'); if idx <= numel(fitResults) fit = fitResults(idx); if isfield(fit,'isValid') && ~isempty(fit.isValid) && fit.isValid plot(hAx, fit.thetaFine, fit.yFit, 'r-', 'LineWidth', 1.5, 'DisplayName', 'Two-Gaussian Fit'); % Extract parameters from pFit vector if available if isfield(fit,'pFit') && numel(fit.pFit) >= 6 A1 = fit.pFit(1); mu1 = fit.pFit(2); A2 = fit.pFit(4); mu2 = fit.pFit(5); hInfoText(1).String = 'Valid'; hInfoText(1).ForegroundColor = [0 0.5 0]; hInfoText(2).String = sprintf('[%.3f, %.3f]', mu1, mu2); hInfoText(3).String = sprintf('[%.2f, %.2f]', A1, A2); else hInfoText(1).String = 'Valid'; hInfoText(1).ForegroundColor = [0 0.5 0]; hInfoText(2).String = '-'; hInfoText(3).String = '-'; end else hInfoText(1).String = 'Invalid'; hInfoText(1).ForegroundColor = [0.7 0 0]; hInfoText(2).String = '-'; hInfoText(3).String = '-'; end end title(hAx,sprintf('Curve %d / %d', idx, Ncurves),'FontSize',16); legend(hAx,'Location','northeast','FontSize',14); hold(hAx,'off'); drawnow; end function keyPressCallback(event) switch event.Key case 'rightarrow' if currentIdx < Ncurves currentIdx = currentIdx + 1; set(hSlider,'Value',currentIdx); updatePlot(); end case 'leftarrow' if currentIdx > 1 currentIdx = currentIdx - 1; set(hSlider,'Value',currentIdx); updatePlot(); end end end end