diff --git a/README.md b/README.md index abbbe9e..af94fc5 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ and for measuring field of view size using an EM grid. ## Main functions * `mpqc.record.PSF` to easily acquire PSF stacks with ScanImage. -* `mpqc.record.lens_tissue` to acquire standardised data from lens tissue. +* `mpqc.record.lens_paper` to acquire standardised data from lens tissue. * `mpqc.record.standard_light_source` to data from a standard light source +* `mpqc.record.electrical_noise` to measure the electrical noise of the PMTs. * `measurePSF` to estimate PSF size. For a demo, run `measurePSF('demo')`. * `Grid2MicsPerPixel` measures the number of microns per pixel along x and y by analyzing an image of an EM grid. * `mpqc.tools.meanFrame` plots the mean frame intensity as a function of time whilst you are scanning. diff --git a/code/+mpqc/+longitudinal/long_electrical_noise.m b/code/+mpqc/+longitudinal/long_electrical_noise.m new file mode 100644 index 0000000..18aef6a --- /dev/null +++ b/code/+mpqc/+longitudinal/long_electrical_noise.m @@ -0,0 +1,151 @@ +function varargout = long_electrical_noise(data_dir,varargin) +% Longitudinal electrical noise plots showing FWHM and max values over time +% +% mpqc.longitudinal.long_electrical_noise(maintenace_folder_path, varargin) +% Optional inputs: Starting date- month-year +% +% Purpose +% Plots of the maximum value and FWHM of electrical noise for each channel +% with PMTs off. If there is significant change in electrical noise, the FWHM +% distributions will likely increase in value. +% +% +% Outputs +% out (optional) - structure containing key information and data. +% +% Notes +% Currently plots for 4 PMT channels regardless of actual number of +% PMTs in system +% +% +% Isabell Whiteley, SWC AMF 2025 + +if nargin<1 + data_dir = pwd; +end + + +debugPlots = true; + +maintenanceFiles = dir(fullfile(data_dir,'\**\*.tif')); +n=1; + +for ii=1:length(maintenanceFiles) + tmp = maintenanceFiles(ii); + + if contains(tmp.name,'electrical_noise') + plotting_template(n) = generic_generator_template(tmp); + plotting_template(n).type = 'electrical_noise'; + plotting_template(n).plotting_func = @mpqc.plot.electrical_noise; + plotting_template(n).date = string(datetime(regexp(tmp.name, '(\d{4}-\d{2}-\d{2})_(\d{2}-\d{2}-\d{2})','match'),'InputFormat','yyyy-MM-dd_HH-mm-ss')); + [pathstr,plotting_template(n).name,ext] = fileparts(tmp.name); + n=n+1; + end +end +if ~exist('plotting_template','var') + disp('No electrical noise files found') + varargout{1} = []; + return +end + +% sort plotting_template data by the date/time +date_list = [plotting_template.date]; +[~,order] = sort(datenum(date_list,'dd-mm-yyyy hh:MM:ss'),1,'ascend'); +plotting_template = plotting_template(order); + +if nargin > 1 % Optional variable for selecting starting date + startDate = datetime(varargin{1}); + startIndex = 1; + + while [plotting_template(startIndex).date] < startDate + startIndex = startIndex + 1; + end + + plotting_template = plotting_template(startIndex:end); +end + +for ii = 1:length(plotting_template) + if contains(plotting_template(ii).full_path_to_data, '.tif') + noiseData(:,:,:,ii) = mpqc.tools.scanImage_stackLoad(plotting_template(ii).full_path_to_data); + end +end + +noiseData = single(noiseData); + +for q = 1:size(noiseData,4) % each date + + if debugPlots + fig = mpqc.tools.returnFigureHandleForFile(sprintf('%s_%02d',mfilename,q)); + end + for t = 1:size(noiseData,3) % each PMT + + % Extract data + t_im = noiseData(:,:,t,q); + [n,x] = hist(t_im(:),100); % plots all data as histogram + m = smoothdata(n,'gaussian',5); + detail = interp1(x,m,[1:1000]); + + maxVal(t,q) = max(detail(:)); + halfMaxVal = maxVal(t,q)/2; + leftIndex = find(detail(:) >= halfMaxVal, 1, 'first'); + rightIndex = find(detail(:) >= halfMaxVal, 1, 'last'); + fwhm(t,q) = rightIndex -leftIndex; + + % TODO Remove data where PMT does not exist + + % Optionally plot + if debugPlots + subplot(2,2,t) + a=area(n); + a.EdgeColor=[0,0,0.75]; + a.FaceColor=[0.5,0.5,1]; + a.LineWidth=2; + hold on + b = plot(m); + b.LineWidth = 2; + sgtitle(plotting_template(q).date) + title(['PMT # ',num2str(t)]) + hold off + end + end +end + +for ii = 1:size(noiseData,3) % plotting FWHM and max value over time for each PMT + xlabels = {plotting_template.date}; + fig = mpqc.tools.returnFigureHandleForFile(sprintf('%s_%02d',mfilename,ii)); + subplot(2,1,1) + plot(maxVal(ii,:)) + xticks(1:length(xlabels)) + xticklabels(xlabels) + title('Max value') + + subplot(2,1,2) + plot(fwhm(ii,:)) + xticks(1:length(xlabels)) + xticklabels(xlabels) + title('FWHM') + sgtitle(['PMT # ',num2str(ii)]) +end + + +% Output of the main function +if nargout>0 + out.fileName = {plotting_template(:).name}; + out.noiseData = noiseData; + out.fwhm = fwhm; + out.maxValues = maxVal; + out.date ={plotting_template(:).date}; + varargout{1} = out; +end + +end % close main funtion + + + +function out = generic_generator_template(t_dir) +out.full_path_to_data = fullfile(t_dir.folder,t_dir.name); +out.type = []; +out.plotting_func = []; +out.name = []; +out.date = []; +end \ No newline at end of file diff --git a/code/+mpqc/+longitudinal/long_power.m b/code/+mpqc/+longitudinal/long_power.m new file mode 100644 index 0000000..d037266 --- /dev/null +++ b/code/+mpqc/+longitudinal/long_power.m @@ -0,0 +1,181 @@ +function varargout = long_power(data_dir,varargin) +% Function to track the changes in laser power over time +% +% mpqc.longitudinal.long_power(maintenace_folder_path, varargin) +% +% Optional inputs: +% 'startDate', month-year +% 'wavelength, value +% +% Purpose +% Plots the power at the objective from 0-100% and compares maximum output +% power over time. Used to monitor the health of a laser +% +% +% Outputs +% out (optional) - structure containing key information and data. +% +% +% +% Isabell Whiteley, SWC AMF 2025 + +if nargin<1 + data_dir = pwd; +end + +% For elseif statement of wavelength given in varargin + % % process inputs + % % addpath("+tools"); + % optInputs = parseInputVariable(varargin{:}); + % + % % Extract critical input variable + % selectWavelength = optInputs.wavelength; + % % TODO Add to parseInputVariable startDate + +debugPlots = true; + +maintenanceFiles = dir(fullfile(data_dir,'\**\*.mat')); +n=1; + +for ii=1:length(maintenanceFiles) + tmp = maintenanceFiles(ii); + + if contains(tmp.name,'power') + plotting_template(n) = generic_generator_template(tmp); + plotting_template(n).type = 'power'; + plotting_template(n).plotting_func = @mpqc.plot.power; + plotting_template(n).date = string(datetime(regexp(tmp.name, '(\d{4}-\d{2}-\d{2})_(\d{2}-\d{2}-\d{2})','match'),'InputFormat','yyyy-MM-dd_HH-mm-ss')); + plotting_template(n).wavelength = str2num(cell2mat(regexp(tmp.name,'\d*(?=nm)','match'))); + [pathstr,plotting_template(n).name,ext] = fileparts(tmp.name); + n=n+1; + end +end +if ~exist('plotting_template','var') + disp('No power measurement files found') + varargout{1} = []; + return +end + +% sort plotting_template data by the date/time +date_list = [plotting_template.date]; +[~,order] = sort(datenum(date_list,'dd-mm-yyyy hh:MM:ss'),1,'ascend'); +plotting_template = plotting_template(order); + +if nargin > 1 % Optional variable for selecting starting date + startDate = datetime(varargin{1}); + startIndex = 1; + + while [plotting_template(startIndex).date] < startDate + startIndex = startIndex + 1; + end + + plotting_template = plotting_template(startIndex:end); +end + +if isequal(plotting_template(:).wavelength) % if wavelength is the same + for ii = 1:length(plotting_template) + if contains(plotting_template(ii).full_path_to_data, '.mat') + % If power data is found, load it and find max value + powerData(ii) = load(plotting_template(ii).full_path_to_data); + maxPower(ii) = powerData(ii).powerMeasurements.observedPower(end); + + % Plot the power curves for each date + hold on + subplot(2,1,1) + plot([0:5:100],powerData(ii).powerMeasurements.observedPower,'.') + legend(plotting_template.date,'location', 'Northwest') + title(cell2mat(['Power at ', string(plotting_template(1).wavelength), 'nm'])) + xlabel('Percent power') + ylabel('Power (mW)') + hold off + end + end + % Plot the maximum laser output over time + subplot(2,1,2) + plot(maxPower, '-*') + title('Maximum laser power') + xlabels = {plotting_template.date}; + xticks(1:length(xlabels)) + xticklabels(xlabels) + ylabel('Maximum power (mW)') + +% elseif isequal(plotting_template(:).wavelength,selectWavelength) +% elseif ~isempty(selectWavelength) +% tempStruc = find(plotting_template(:).wavelength == selectWavelength); +% % if wavelength given in varargin, plot only that wavelength and dates + +else + % disp('Different wavelengths found') + % powerData = []; + % maxPower = []; + for i = 1:length(plotting_template) + allWave(i) = plotting_template(i).wavelength; + end + wavelengthVals = unique(allWave); + numWavelength = length(wavelengthVals); +maxPower = cell(zeros(1:length(plotting_template)),zeros(1:length(wavelengthVals))); % Need to be cells because they will have empty values +powerData = cell(zeros(1:length(plotting_template)),zeros(1:length(wavelengthVals))); + for jj = 1:length(numWavelength) + figure; + for ii = 1:length(plotting_template) + + if contains(plotting_template(ii).full_path_to_data, '.mat') && isequal(plotting_template(ii).wavelength,wavelengthVals(jj)) + % If power data is found, load it and find max value + powerData(ii,jj) = load(plotting_template(ii).full_path_to_data); + maxPower(ii,jj) = powerData(ii).powerMeasurements.observedPower(end); + + + hold on + subplot(2,1,1) + plot([0:5:100],powerData(ii,jj).powerMeasurements.observedPower,'.') + legend(plotting_template.date,'location', 'Northwest') + title(cell2mat(['Power at ', string(wavelengthVals(jj)), 'nm'])) + xlabel('Percent power') + ylabel('Power (mW)') + hold off + end + end + end + % for ii = 1:length(numWavelength) + % end + % separate plots for each wavelength + % tried groupcounts + % in R2025a numunique function may work + + % plot wavelengths separately, but keep all dates - so assisgn NaN to + % unused dates. Make temporary structures so values are not permanently + % replaced +end + + + +% identify wavelength - done +% plot curves on same plot - done +% separate plots based on wavelength +% max value to see decay of laser - done + + + + + + +% Output of the main function +if nargout>0 + out.fileName = {plotting_template(:).name}; + out.date ={plotting_template(:).date}; + out.powerData = powerData; + out.maxPower = maxPower; + out.wavelength = {plotting_template(:).wavelength}; + varargout{1} = out; +end + +end + +function out = generic_generator_template(t_dir) +out.full_path_to_data = fullfile(t_dir.folder,t_dir.name); +out.type = []; +out.plotting_func = []; +out.name = []; +out.date = []; +out.wavelength = []; +end \ No newline at end of file diff --git a/code/+mpqc/+longitudinal/long_standard_light_source.m b/code/+mpqc/+longitudinal/long_standard_light_source.m new file mode 100644 index 0000000..dcfa31a --- /dev/null +++ b/code/+mpqc/+longitudinal/long_standard_light_source.m @@ -0,0 +1,38 @@ +function varargout = long_standard_light_source(data_dir,varargin) +% Plots showing photon count of PMTs over time +% +% +% +% +% Isabell Whiteley, SWC AMF 2025 + +if nargin<1 + data_dir = pwd; +end + + +debugPlots = true; + +maintenanceFiles = dir(fullfile(data_dir,'\**\*.tif')); +n=1; + +for ii=1:length(maintenanceFiles) + tmp = maintenanceFiles(ii); + + if contains(tmp.name,'standard_light_source') + plotting_template(n) = generic_generator_template(tmp); + plotting_template(n).type = 'standard_light_source'; + plotting_template(n).plotting_func = @mpqc.plot.electrical_noise; + plotting_template(n).date = string(datetime(regexp(tmp.name, '(\d{4}-\d{2}-\d{2})_(\d{2}-\d{2}-\d{2})','match'),'InputFormat','yyyy-MM-dd_HH-mm-ss')); + [pathstr,plotting_template(n).name,ext] = fileparts(tmp.name); + n=n+1; + end +end +if ~exist('plotting_template','var') + disp('No standard light source files found') + varargout{1} = []; + return +end + + +end \ No newline at end of file diff --git a/code/testscript.m b/code/testscript.m new file mode 100644 index 0000000..a1d6ebb --- /dev/null +++ b/code/testscript.m @@ -0,0 +1,2 @@ +a = 1 +[d,out] = mpqc.longitudinal.long_electrical_noise('C:\Users\Isabell\Desktop\2p-313_diagnostics\') \ No newline at end of file