diff --git a/+ciapkg/+api/getMovieFileType.m b/+ciapkg/+api/getMovieFileType.m index f5f6a70..25658e1 100644 --- a/+ciapkg/+api/getMovieFileType.m +++ b/+ciapkg/+api/getMovieFileType.m @@ -1,4 +1,4 @@ -function [movieType, supported, movieType2] = getMovieFileType(thisMoviePath) +function [movieType, supported, movieType2] = getMovieFileType(thisMoviePath,varargin) % Determine how to load movie, don't assume every movie in list is of the same type % Biafra Ahanonu % started: 2020.09.01 [‏‎14:16:57] diff --git a/+ciapkg/+api/getSeriesNoFromName.m b/+ciapkg/+api/getSeriesNoFromName.m new file mode 100644 index 0000000..8f59f82 --- /dev/null +++ b/+ciapkg/+api/getSeriesNoFromName.m @@ -0,0 +1,32 @@ +function [bfSeriesNo] = getSeriesNoFromName(inputMoviePath,bfSeriesName,varargin) + % [bfSeriesNo] = getSeriesNoFromName(inputFilePath,bfSeriesName,varargin) + % + % Returns series number for Bio-Formats series with name given by user. + % + % Biafra Ahanonu + % started: 2022.07.15 [08:10:16] + % + % Inputs + % inputFilePath - Str: path to Bio-Formats compatible file. + % bfSeriesName - Str: name of series within the file. Can be a regular expression. + % + % Outputs + % bfSeriesNo - Int: series number (1-based indexing) matching the input series name. NaN is output if no series is found. + % + % Options (input as Name-Value with Name = options.(Name)) + % % File ID connection from calling bfGetReader(inputFilePath), this is to save time on larger files by avoiding opening the connection again. + % options.fileIdOpen = ''; + + % Changelog + % + % TODO + % + + try + bfSeriesNo = ciapkg.bf.getSeriesNoFromName(inputMoviePath,bfSeriesName,'passArgs',varargin); + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end +end \ No newline at end of file diff --git a/+ciapkg/+api/loadSignalExtractionSorting.m b/+ciapkg/+api/loadSignalExtractionSorting.m new file mode 100644 index 0000000..3d4c589 --- /dev/null +++ b/+ciapkg/+api/loadSignalExtractionSorting.m @@ -0,0 +1,27 @@ +function [valid, algorithmStr, infoStruct] = loadSignalExtractionSorting(inputFilePath,varargin) + % [valid,algorithmStr,infoStruct] = loadSignalExtractionSorting(inputFilePath,varargin) + % + % Loads manual or automated (e.g. CLEAN) sorting.. + % + % Biafra Ahanonu + % started: 2022.05.31 [20:14:54] (branched from modelVarsFromFiles) + % + % Inputs + % inputFilePath - Str: path to signal extraction output. + % + % Outputs + % valid - logical vector: indicating which signals are valid and should be kept. + % algorithmStr - Str: algorithm name. + % infoStruct - Struct: contains information about the file, e.g. the 'description' property that can contain information about the algorithm. + % + % Options (input as Name-Value with Name = options.(Name)) + % % DESCRIPTION + % options.exampleOption = ''; + + % Changelog + % + % TODO + % + + [valid, algorithmStr, infoStruct] = ciapkg.io.loadSignalExtractionSorting(inputFilePath,'passArgs', varargin); +end \ No newline at end of file diff --git a/+ciapkg/+api/manageParallelWorkers.m b/+ciapkg/+api/manageParallelWorkers.m index be48a98..960e221 100644 --- a/+ciapkg/+api/manageParallelWorkers.m +++ b/+ciapkg/+api/manageParallelWorkers.m @@ -3,5 +3,12 @@ % Biafra Ahanonu % started: 2015.12.01 - [success] = ciapkg.io.manageParallelWorkers('passArgs', varargin); + % changelog + % 2022.02.28 [18:36:15] - Added ability to input just the number of workers to open as 1st single input argument that aliases for the "setNumCores" Name-Value input, still support other input arguments as well. + + if length(varargin)==1 + [success] = ciapkg.io.manageParallelWorkers(varargin{1}); + else + [success] = ciapkg.io.manageParallelWorkers('passArgs', varargin); + end end \ No newline at end of file diff --git a/+ciapkg/+api/mergeStructs.m b/+ciapkg/+api/mergeStructs.m new file mode 100644 index 0000000..302a780 --- /dev/null +++ b/+ciapkg/+api/mergeStructs.m @@ -0,0 +1,17 @@ +function [pullStruct] = mergeStructs(toStruct,fromStruct,varargin) + % [toStruct] = mergeStructs(fromStruct,toStruct,overwritePullFields) + % + % Copies fields in fromStruct into toStruct, if there is an overlap in field names, fromStruct overwrites toStruct unless specified otherwise. + % + % Biafra Ahanonu + % started: 2014.02.12 + % + % inputs + % toStruct - Structure that is to be updated with values from fromStruct. + % fromStruct - structure to use to overwrite toStruct. + % overwritePullFields - 1 = overwrite toStruct fields with fromStruct, 0 = don't overwrite. + % outputs + % toStruct - structure with fromStructs values added. + + [pullStruct] = ciapkg.io.mergeStructs(toStruct,fromStruct,'passArgs', varargin); +end \ No newline at end of file diff --git a/+ciapkg/+api/normalizeSignalExtractionActivityTraces.m b/+ciapkg/+api/normalizeSignalExtractionActivityTraces.m index e908146..e4b94dc 100644 --- a/+ciapkg/+api/normalizeSignalExtractionActivityTraces.m +++ b/+ciapkg/+api/normalizeSignalExtractionActivityTraces.m @@ -1,5 +1,8 @@ function inputSignals = normalizeSignalExtractionActivityTraces(inputSignals,inputImages, varargin) + % [inputSignals] = ciapkg.signal_extraction.normalizeSignalExtractionActivityTraces(inputSignals,inputImages,'passArgs', varargin); + % % Normalizes cell activity traces by the max value in the associated image, normally to produce dF/F equivalent activity traces + % % started: 2019.08.25 [18:19:34] % Biafra Ahanonu % Branched from normalizeCELLMaxTraces (Lacey Kitch and Biafra Ahanonu) diff --git a/+ciapkg/+bf/dispFileSeries.m b/+ciapkg/+bf/dispFileSeries.m new file mode 100644 index 0000000..b42b37d --- /dev/null +++ b/+ciapkg/+bf/dispFileSeries.m @@ -0,0 +1,76 @@ +function [seriesNameArray] = dispFileSeries(inputFilePath,varargin) + % [bfSeriesNo] = getSeriesNoFromName(inputFilePath,bfSeriesName,varargin) + % + % Returns series number for Bio-Formats series with name given by user. + % + % Biafra Ahanonu + % started: 2022.07.15 [08:10:16] + % + % Inputs + % inputFilePath - Str: path to Bio-Formats compatible file. + % + % Outputs + % bfSeriesNo - Int: series number (1-based indexing) matching the input series name. NaN is output if no series is found. + % + % Options (input as Name-Value with Name = options.(Name)) + % % File ID connection from calling bfGetReader(inputFilePath), this is to save time on larger files by avoiding opening the connection again. + % options.fileIdOpen = ''; + + % Changelog + % + % TODO + % + + % ======================== + % File ID connection from calling bfGetReader(inputFilePath), this is to save time on larger files by avoiding opening the connection again. + options.fileIdOpen = []; + % get options + options = ciapkg.io.getOptions(options,varargin); + % disp(options) + % unpack options into current workspace + % fn=fieldnames(options); + % for i=1:length(fn) + % eval([fn{i} '=options.' fn{i} ';']); + % end + % ======================== + + try + % By default output NaN if no series found. + seriesNameArray = {}; + + if isempty(options.fileIdOpen) + disp(['Opening connection to Bio-Formats file:' inputFilePath]) + startReadTime = tic; + fileIdOpen = bfGetReader(inputFilePath); + toc(startReadTime) + else + fileIdOpen = options.fileIdOpen; + end + omeMeta = fileIdOpen.getMetadataStore(); + + nSeries = fileIdOpen.getSeriesCount(); + seriesNameArray = cell([1 nSeries]); + disp('===') + disp(['Series in file: ' inputFilePath]) + for seriesNo = 1:nSeries + % Convert 1-based to 0-based indexing. + thisStr = char(omeMeta.getImageName(seriesNo-1)); + seriesNameArray{seriesNo} = thisStr; + disp([num2str(seriesNo) ': "' thisStr '"']) + end + disp('===') + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end + + function [outputs] = nestedfxn_exampleFxn(arg) + % Always start nested functions with "nestedfxn_" prefix. + % outputs = ; + end +end +function [outputs] = localfxn_exampleFxn(arg) + % Always start local functions with "localfxn_" prefix. + % outputs = ; +end \ No newline at end of file diff --git a/+ciapkg/+bf/getSeriesNoFromName.m b/+ciapkg/+bf/getSeriesNoFromName.m new file mode 100644 index 0000000..bc412da --- /dev/null +++ b/+ciapkg/+bf/getSeriesNoFromName.m @@ -0,0 +1,88 @@ +function [bfSeriesNo] = getSeriesNoFromName(inputFilePath,bfSeriesName,varargin) + % [bfSeriesNo] = getSeriesNoFromName(inputFilePath,bfSeriesName,varargin) + % + % Returns series number for Bio-Formats series with name given by user. + % + % Biafra Ahanonu + % started: 2022.07.15 [08:10:16] + % + % Inputs + % inputFilePath - Str: path to Bio-Formats compatible file. + % bfSeriesName - Str: name of series within the file. Can be a regular expression. + % + % Outputs + % bfSeriesNo - Int: series number (1-based indexing) matching the input series name. NaN is output if no series is found. + % + % Options (input as Name-Value with Name = options.(Name)) + % % File ID connection from calling bfGetReader(inputFilePath), this is to save time on larger files by avoiding opening the connection again. + % options.fileIdOpen = ''; + + % Changelog + % + % TODO + % + + % ======================== + % File ID connection from calling bfGetReader(inputFilePath), this is to save time on larger files by avoiding opening the connection again. + options.fileIdOpen = []; + % get options + options = ciapkg.io.getOptions(options,varargin); + % disp(options) + % unpack options into current workspace + % fn=fieldnames(options); + % for i=1:length(fn) + % eval([fn{i} '=options.' fn{i} ';']); + % end + % ======================== + + try + % By default output NaN if no series found. + bfSeriesNo = NaN; + + if isempty(options.fileIdOpen) + disp(['Opening connection to Bio-Formats file:' inputFilePath]) + startReadTime = tic; + fileIdOpen = bfGetReader(inputFilePath); + toc(startReadTime) + else + fileIdOpen = options.fileIdOpen; + end + omeMeta = fileIdOpen.getMetadataStore(); + + if isempty(bfSeriesName) + disp('Please provide a series name for Bio-Formats') + else + disp(['Searching for series: "' bfSeriesName '"']) + nSeries = fileIdOpen.getSeriesCount(); + seriesNameArray = cell([1 nSeries]); + disp('===') + disp(['Series in file: ' inputFilePath]) + for seriesNo = 1:nSeries + % Convert 1-based to 0-based indexing. + thisStr = char(omeMeta.getImageName(seriesNo-1)); + seriesNameArray{seriesNo} = thisStr; + disp([num2str(seriesNo) ': "' thisStr '"']) + end + disp('===') + matchIdx = ~cellfun(@isempty,regexp(seriesNameArray,bfSeriesName)); + if any(matchIdx) + bfSeriesNo = find(matchIdx); + bfSeriesNo = bfSeriesNo(1); + disp(['Series found: ' num2str(bfSeriesNo) ' - "' seriesNameArray{bfSeriesNo} '"']) + end + end + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end + + function [outputs] = nestedfxn_exampleFxn(arg) + % Always start nested functions with "nestedfxn_" prefix. + % outputs = ; + end +end +function [outputs] = localfxn_exampleFxn(arg) + % Always start local functions with "localfxn_" prefix. + % outputs = ; +end \ No newline at end of file diff --git a/+ciapkg/+classification/signalSorter.m b/+ciapkg/+classification/signalSorter.m index b4917bc..52511b9 100644 --- a/+ciapkg/+classification/signalSorter.m +++ b/+ciapkg/+classification/signalSorter.m @@ -1,20 +1,31 @@ function [inputImages, inputSignals, choices] = signalSorter(inputImages,inputSignals,varargin) + % [inputImages, inputSignals, choices] = signalSorter(inputImages,inputSignals,varargin) + % % Displays a GUI for sorting images (e.g. cells) and their associated signals (e.g. fluorescence activity traces). Also does preliminary sorting based on image/signal properties if requested by user. - % See following URL for details of GUI and tips on manual sorting: https://github.com/bahanonu/calciumImagingAnalysis/wiki/Manual-cell-sorting-of-cell-extraction-outputs. + % + % See following URL for details of GUI and tips on manual sorting: https://bahanonu.github.io/ciatah/help_manual_cell_sorting/. + % % Biafra Ahanonu % started: 2013.10.08 + % % Dependent code - % getOptions.m, createObjMap.m, removeSmallICs.m, identifySpikes.m, etc., see repository - % inputs - % inputImages - [x y N] matrix where N = number of images, x/y are dimensions. Use permute(inputImages,[2 3 1]) if you use [N x y] for matrix indexing. - % Alternatively, make inputImages = give path to NWB file and inputSignals = [] for signalSorter to automatically load NWB files. - % inputSignals - [N time] matrix where N = number of signals (traces) and time = frames. - % inputID - obsolete, kept for compatibility, just input empty [] - % nSignals - obsolete, kept for compatibility - % outputs - % inputImages - [N x y] matrix where N = number of images, x/y are dimensions with only manual choices kept. - % inputSignals - % choices + % getOptions.m, createObjMap.m, removeSmallICs.m, identifySpikes.m, etc., see CIAtah repository. + % + % Inputs + % inputImages + % 1) [x y N] matrix where N = number of images, x/y are dimensions. Use permute(inputImages,[2 3 1]) if you use [N x y] for matrix indexing. + % 2) Path to NWB file and inputSignals = [] for signalSorter to automatically load NWB files. + % inputSignals - [N time] matrix where N = number of signals (traces) and time = frames. + % inputID - obsolete, kept for compatibility, just input empty [] + % nSignals - obsolete, kept for compatibility + % + % Outputs + % inputImages - Matrix: filtered with only good outputs. [N x y] matrix where N = number of images, x/y are dimensions with only manual choices kept. + % inputSignals - Matrix: filtered with only good outputs. [N time] matrix where N = number of signals (traces) and time = frames. + % choices - [1 N] vector using the below key: + % 1 = yes + % 0 = no + % 2 = not designated by user % changelog % 2013.10.xx changed to ginput and altered UI to show more relevant information, now shows a objMap overlayed with the current filter, etc. @@ -78,6 +89,7 @@ % 2021.12.22 [08:19:19] - Updated suptitle to ciapkg.overloaded.suptitle. Also limit number of GUI re-runs to prevent infinite looping. % 2022.01.19 [12:35:42] - Fixed issue with `gca.XRuler.Axle.LineStyle` being an empty object when axes created, leading to failure to assign and error loops. % 2022.02.06 [20:06:19] - Make all openFigure to ciapkg.api.openFigure. + % 2022.03.14 [01:10:52] - Comments update. % TODO % DONE: New GUI interface to allow users to scroll through video and see cell activity at that point % DONE: allow option to mark rest as bad signals @@ -171,8 +183,9 @@ options.randomizeOrder = 0; % show ROI traces in addition to input traces options.showROITrace = 0; - % pre-compute signal peaks + % Matrix: save time if already computed peaks. [nSignals frame] matrix. Binary matrix with 1 = peaks, 0 = non-peaks. options.signalPeaks = []; + % Cell array: save time if already computed peaks. {1 nSignals} cell array. Each cell contains [1 nPeaks] vector that stores the frame locations of each peak. options.signalPeaksArray = []; % ROI for peak signal plotting options.peakROI = -20:20; @@ -1550,6 +1563,9 @@ function plotSignalStatistics(inputSignals,inputImageSizes,inputStr,pointColor, set(zoomHandle,'ActionPostCallback',@sliderZoomCallback); end + % Create progress bar + [axValid, axValidAll] = subfxn_progressBarCreate(axValid, axValidAll); + set(objMapPlotLocHandle,'tag','objMapPlotLocHandle') set(objMapZoomPlotLocHandle,'tag','objMapZoomPlotLocHandle') % if strcmp(get(zoom(1),'Enable'),'off') @@ -1557,10 +1573,8 @@ function plotSignalStatistics(inputSignals,inputImageSizes,inputStr,pointColor, % end set(objMapPlotLocHandle,'ButtonDownFcn',@subfxnSelectCellOnCellmap) set(objMapZoomPlotLocHandle,'ButtonDownFcn',@subfxnSelectCellOnCellmap) - set(mainFig, 'KeyPressFcn', @(source,eventdata) figure(mainFig)); - % Create progress bar - [axValid, axValidAll] = subfxn_progressBarCreate(axValid, axValidAll); + set(mainFig, 'KeyPressFcn', @(source,eventdata) figure(mainFig)); while strcmp(keyIn,'3') frameNoTotal = frameNoTotal+1; @@ -1583,6 +1597,8 @@ function plotSignalStatistics(inputSignals,inputImageSizes,inputStr,pointColor, end set(objMapPlotLocHandle,'tag','objMapPlotLocHandle') set(objMapZoomPlotLocHandle,'tag','objMapZoomPlotLocHandle') + set(objMapPlotLocHandle,'ButtonDownFcn',@subfxnSelectCellOnCellmap) + set(objMapZoomPlotLocHandle,'ButtonDownFcn',@subfxnSelectCellOnCellmap) reply = double(keyIn); set(gcf,'currentch','3'); @@ -2109,6 +2125,7 @@ function mouseWheelChange(hObject, callbackdata, handles) set(gcf,'uicontextmenu',conMenu); end function subfxnSelectCellOnCellmap(source,eventdata) + disp('Click screen') if showCrossHairs==1 [xUser,yUser,~] = ciapkg.overloaded.ginputCustom(1); else @@ -2118,6 +2135,7 @@ function subfxnSelectCellOnCellmap(source,eventdata) end mapTags = {'objMapPlotLocHandle','objMapZoomPlotLocHandle'}; thisPlotTag = find(ismember(mapTags, get(gca,'tag'))); + thisPlotTag if isempty(thisPlotTag) % zoom; elseif thisPlotTag<0|thisPlotTag>length(mapTags) @@ -2551,7 +2569,7 @@ function subfxnLostFocusMainFig(jAxis, jEventData, hFig) croppedPeakImages = cat(3,meanTransientImage,croppedPeakImages); croppedPeakImages222 = compareSignalToMovie(inputMovie, inputImage, thisTrace,'getOnlyPeakImages',1,'waitbarOn',0,'extendedCrosshairs',2,'crossHairVal',maxValMovie*options.crossHairPercent,'outlines',0,'signalPeakArray',signalPeakArray,'cropSize',cropSizeLength,'crosshairs',0,'addPadding',1,'xCoords',xCoords,'yCoords',yCoords,'outlineVal',NaN,'inputDatasetName',options.inputDatasetName,'inputMovieDims',options.inputMovieDims,'hdf5Fid',options.hdf5Fid,'keepFileOpen',options.keepFileOpen); - [thresholdedImages, boundaryIndices] = thresholdImages(croppedPeakImages222(:,:,1),'binary',1,'getBoundaryIndex',1,'threshold',options.thresholdOutline,'removeUnconnectedBinary',0); + [thresholdedImages, boundaryIndices] = thresholdImages(croppedPeakImages222(:,:,1),'binary',1,'getBoundaryIndex',1,'threshold',options.thresholdOutline,'removeUnconnectedBinary',0,'waitbarOn',0); for imageNo = 1:size(croppedPeakImages,3) tmpImg = croppedPeakImages(:,:,imageNo); tmpImg(boundaryIndices{1}) = NaN; diff --git a/+ciapkg/+download/downloadCnmfGithubRepositories.m b/+ciapkg/+download/downloadCnmfGithubRepositories.m index e3c5650..fa6d2d7 100644 --- a/+ciapkg/+download/downloadCnmfGithubRepositories.m +++ b/+ciapkg/+download/downloadCnmfGithubRepositories.m @@ -10,6 +10,7 @@ % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2021.12.01 [20:09:10] - Update display of information if CVX is not automatically found. % 2022.04.08 [15:37:36] - Download CVX from a custom URL since the main URL sometimes has a long response time, leading to timeouts. + % 2022.06.27 [14:30:32] - Update URLs to include a backup URL for cvx since would often timeout. import ciapkg.api.* % import CIAtah functions in ciapkg package API. @@ -35,9 +36,8 @@ gitRepos = {... 'https://github.com/bahanonu/CNMF_E/archive/master.zip'; 'https://github.com/flatironinstitute/CaImAn-MATLAB/archive/master.zip'; - 'http://tiny.ucsf.edu/YR4gfF'; - }; - % 'http://web.cvxr.com/cvx/cvx-rd.zip'; + 'http://tiny.ucsf.edu/YR4gfF'; % 'http://web.cvxr.com/cvx/cvx-rd.zip'; + }; outputDir = {'cnmfe','cnmf_current','cvx_rd'}; gitName = {'CNMF_E-master','CaImAn-MATLAB-master','cvx'}; nRepos = length(outputDir); diff --git a/+ciapkg/+hdf5/appendDataToHdf5.m b/+ciapkg/+hdf5/appendDataToHdf5.m index 9d84b76..0b30f31 100644 --- a/+ciapkg/+hdf5/appendDataToHdf5.m +++ b/+ciapkg/+hdf5/appendDataToHdf5.m @@ -1,15 +1,22 @@ function appendDataToHdf5(filename, datasetName, inputData, varargin) + % appendDataToHdf5(filename, datasetName, inputData, varargin) + % % Appends 3D data to existing dataset in hdf5 file, assumes x,y are the same, only appending z. + % % Biafra Ahanonu % started: 2014.01.07 + % % inputs - % + % filename - Str: path to existing HDF5 file. + % datasetName - Str: name for the existing dataset to append to. + % inputData - 3D matrix: input data to append to the end of the existing HDF5 dataset. % outputs % % changelog % 2014.07.22 % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.03.13 [23:49:04] - Update comments. % TODO % diff --git a/+ciapkg/+hdf5/downsampleHdf5Movie.m b/+ciapkg/+hdf5/downsampleHdf5Movie.m index 4c9ac4e..2c7dc81 100644 --- a/+ciapkg/+hdf5/downsampleHdf5Movie.m +++ b/+ciapkg/+hdf5/downsampleHdf5Movie.m @@ -1,49 +1,77 @@ function [success] = downsampleHdf5Movie(inputFilePath, varargin) - % Downsamples the Hdf5 movie in inputFilePath piece by piece and appends it to already created hdf5 file. + % [success] = downsampleHdf5Movie(inputFilePath, varargin) + % + % Downsamples a movie (HDF5, SLD, etc.) in inputFilePath piece by piece and appends it to already created hdf5 file. Useful for large files that otherwise would not fit into RAM. + % % Biafra Ahanonu % started: 2013.12.19 - % base append code based on work by Dinesh Iyer (http://www.mathworks.com/matlabcentral/newsreader/author/130530) + % + % Part of append code based on work by Dinesh Iyer (http://www.mathworks.com/matlabcentral/newsreader/author/130530) % note: checked between this implementation and imageJ's scale, they are nearly identical (subtracted histogram mean is 1 vs each image mean of ~1860, so assuming precision error). + % % inputs - % inputFilePath + % inputFilePath - Str: path to movie to downsample into HDF5. + % outputs + % success - Binary: 1 = completed successfully, 0 = did not complete successfully. % options - % datasetName = hierarchy where data is stored in HDF5 file + % inputDatasetName = hierarchy where data is stored in HDF5 file % changelog % 2014.01.18 - improved method of obtaining the newFilename % 2014.06.16 - updated output notifications to user % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.07.10 [19:24:29] - Added Bio-Formats support. + % 2022.07.15 [13:34:28] - Switch chunking to automatic by default, generally improved reading from data later than small chunks. % TODO % Use handles to reduce memory load when doing computations. import ciapkg.api.* % import CIAtah functions in ciapkg package API. %======================== - % name of the input hierarchy in the HDF5 files + % Str: name of the input hierarchy in the HDF5 files options.inputDatasetName = '/1'; - % name of the output hierarchy in the HDF5 files + % Str: name of the output hierarchy in the HDF5 files options.outputDatasetName = '/1'; + % Vector: frames to use. options.frameList = 1; + % Int: amount to downsample movie. Value of 1 = no downsample (e.g. just file conversion). options.downsampleFactor = 4; - % max size of a chunk in Mbytes + % Int: max size of a chunk in Mbytes. For HDF5 files. options.maxChunkSize = 20000; + % Int: bytes to MB conversion. DO NOT CHANGE. options.bytesToMB = 1024^2; + % Int: number of frames to use when loading file in chunks. For Bio-Formats files! + options.numFramesSubset = 1000; % interval over which to show waitbar options.waitbarInterval = 1000; - % get the new filename for the downsampled movie - [pathstr,name,ext] = fileparts(inputFilePath); - options.newFilename = [pathstr filesep 'concat_' name '.h5']; - % downsample to different folder + % Str: new filename to save to for the downsampled movie. + options.newFilename = ''; + % Str: downsample to different folder. Leave blank to use same folder as input file. options.saveFolder = []; % second if want to do another downsample without loading another file options.saveFolderTwo = []; + % Int: amount to downsample movie to 2nd folder. Value of 1 = no downsample (e.g. just file conversion). options.downsampleFactorTwo = 2; - [pathstr,name,ext] = fileparts(inputFilePath); - options.newFilenameTwo = [pathstr filesep 'concat_' name '.h5']; + % Str: new filename to save to for the downsampled movie in 2nd folder. + options.newFilenameTwo = ''; % Int: Defines gzip compression level (0-9). 0 = no compression, 9 = most compression. options.deflateLevel = 1; % Int: chunk size in [x y z] of the dataset, leave empty for auto chunking - options.dataDimsChunkCopy = [128 128 1]; + options.dataDimsChunkCopy = []; % [128 128 1] + % Str: movie type: hdf5, bioformats. Leave blank for automatic determination. + options.movieType = ''; + % Int: Bio-Formats series number to load. + options.bfSeriesNo = 1; + % Int: Bio-Formats channel number to load. + options.bfChannelNo = 1; + % Int: Bio-Formats z dimension to load. + options.bfZdimNo = 1; + % Str: Bio-Formats series name. Function will search via regexp for this series within a Bio-Formats file. + options.bfSeriesName = ''; + % Binary: 1 = whether to display info on command line. + options.displayInfo = 1; + % Binary: 1 = waitbar/progress bar is shown, 0 = no progress shown. + options.waitbarOn = 1; % get options options = getOptions(options,varargin); % unpack options into current workspace @@ -53,10 +81,95 @@ % end %======================== + [pathstr,name,ext] = fileparts(inputFilePath); + inputFileEmpty = 0; + if isempty(options.newFilename) + inputFileEmpty = 1; + options.newFilename = [pathstr filesep 'concat_' name '.h5']; + end + if isempty(options.newFilenameTwo) + options.newFilenameTwo = [pathstr filesep 'concat_' name '.h5']; + end + % Check that options.dataDimsChunkCopy + % Check type of movie being read in. + if isempty(options.movieType) + [options.movieType] = ciapkg.io.getMovieFileType(inputFilePath); + end + + startTime = tic; + + fileIdOpen = []; + + switch options.movieType + case 'hdf5' + % movie dimensions and subsets to analyze + [subsets, dataDim] = getSubsetOfDataToAnalyze(inputFilePath, options, varargin); + case 'bioformats' + disp(['Opening connection to Bio-Formats file:' inputFilePath]) + startReadTime = tic; + fileIdOpen = bfGetReader(inputFilePath); + toc(startReadTime) + + % If user inputs a series name, find within the file and overwrite default series name. + if ~isempty(options.bfSeriesName) + [bfSeriesNo] = ciapkg.bf.getSeriesNoFromName(inputFilePath,options.bfSeriesName,'fileIdOpen',fileIdOpen); + if isnan(bfSeriesNo) + disp(['Series "' options.bfSeriesName '" not found in ' inputFilePath]) + disp('Returning...') + return; + else + options.bfSeriesNo = bfSeriesNo; + end + end + + % Convert for 0-base indexing. + bfSeriesNoTrue = options.bfSeriesNo-1; + + fileIdOpen.setSeries(bfSeriesNoTrue); + omeMeta = fileIdOpen.getMetadataStore(); + + dim = struct; + dataDim.x = omeMeta.getPixelsSizeX(bfSeriesNoTrue).getValue(); % image width, pixels + dataDim.y = omeMeta.getPixelsSizeY(bfSeriesNoTrue).getValue(); % image height, pixels + dataDim.z = fileIdOpen.getSizeT; + + thisStr = char(omeMeta.getImageName(bfSeriesNoTrue)); + + disp(['Loading series: ' thisStr]) + display(dataDim) + + % If filename is empty + if inputFileEmpty==1 + options.newFilename = [pathstr filesep 'concat_' thisStr '.h5']; + end + + disp('===') + disp(['Series in file: ' inputFilePath]) + nSeries = fileIdOpen.getSeriesCount(); + for seriesNo = 1:nSeries + thisStr = char(omeMeta.getImageName(seriesNo-1)); + disp([num2str(seriesNo) ': "' thisStr '"']) + end + disp('===') + + % Get the subsets of the movie to analyze + subsetSize = options.numFramesSubset; + movieLength = dataDim.z; + numSubsets = ceil(movieLength/subsetSize)+1; + subsets = round(linspace(1,movieLength,numSubsets)); + otherwise + end + + subsetsDiff = diff(subsets); + % to compensate for flooring of linspace + subsetsDiff(end) = subsetsDiff(end)+1; + display(['subsets: ' num2str(subsets)]); + display(['subsets diffs: ' num2str(subsetsDiff)]); + % Change output filename if user request a different folder than where original file is. if ~isempty(options.saveFolder) [pathstr,name,ext] = fileparts(options.newFilename); options.newFilename = [options.saveFolder filesep name '.h5']; @@ -65,19 +178,27 @@ [pathstr,name,ext] = fileparts(options.newFilename); options.newFilenameTwo = [options.saveFolderTwo filesep name '.h5']; end + + % Pre-establish connection to file to save time. + switch options.movieType + case 'hdf5' + % hdf5FileWorkerConstant.Value = H5F.open(inputMovie); + case 'bioformats' + if isempty(fileIdOpen) + disp(['Opening connection to Bio-Formats file:' inputFilePath]) + startReadTime = tic; + fileIdOpen = bfGetReader(inputFilePath); + toc(startReadTime) + end + otherwise + end display(repmat('+',1,21)) - display(['saving to: ' options.newFilename]) - startTime = tic; - % movie dimensions and subsets to analyze - [subsets dataDim] = getSubsetOfDataToAnalyze(inputFilePath, options, varargin); - subsetsDiff = diff(subsets); - % to compensate for flooring of linspace - subsetsDiff(end) = subsetsDiff(end)+1; - display(['subsets: ' num2str(subsets)]); - display(['subsets diffs: ' num2str(subsetsDiff)]); + disp(['Saving to: ' options.newFilename]) + % display(['saving to: ' options.newFilename]) + try nSubsets = (length(subsets)-1); - for currentSubset=1:nSubsets + for currentSubset = 1:nSubsets loopStartTime = tic; % get current subset location and size currentSubsetLocation = subsets(currentSubset); @@ -88,14 +209,29 @@ display('---') % display(sprintf(['current location: ' num2str(round(currentSubsetLocation/dataDim.z*100)) '% | ' num2str(currentSubsetLocation) '/' num2str(dataDim.z) '\noffset: ' num2str(offset) '\nblock: ' num2str(block)])); fprintf('current location: %d%% | %d/%d \noffset: %s \nblock: %s\n',round(currentSubsetLocation/dataDim.z*100),currentSubsetLocation,dataDim.z,mat2str(offset),mat2str(block)); - % load subset of HDF5 file into memory - inputMovie = readHDF5Subset(inputFilePath,offset,block,'datasetName',options.inputDatasetName); + + switch options.movieType + case 'hdf5' + % load subset of HDF5 file into memory + inputMovie = readHDF5Subset(inputFilePath,offset,block,'datasetName',options.inputDatasetName); + case 'bioformats' + thisFrameList = currentSubsetLocation:(currentSubsetLocation+lengthSubset-1); + disp(['Frames ' num2str(thisFrameList(1)) ' to ' num2str(thisFrameList(end))]) + [inputMovie] = local_readBioformats(inputFilePath,thisFrameList,fileIdOpen,options); + otherwise + end + + % split into second movie if need be if ~isempty(options.saveFolderTwo) inputMovieTwo = inputMovie; end - % downsample section of the movie, keep in memory - downsampleMovieNested('downsampleDimension', 'space','downsampleFactor',options.downsampleFactor,'waitbarInterval',options.waitbarInterval); + + % Downsample section of the movie, keep in memory + if options.downsampleFactor==1 + else + downsampleMovieNested('downsampleDimension', 'space','downsampleFactor',options.downsampleFactor,'waitbarInterval',options.waitbarInterval); + end % thisMovie = uint16(thisMovie); display(['subset class: ' class(inputMovie)]) @@ -246,6 +382,84 @@ function downsampleMovieNested(varargin) end end +function [outputMovie] = local_readBioformats(thisMoviePath,thisFrameList,bfreaderTmp,options) + % Reads in Bio-Formats data in chunks, uses open ID to speed up read times. + + % Setup movie class + numMovies = 1; + + % Account for 0-base indexing. + bfSeriesNoTrue = options.bfSeriesNo-1; + + % bfreaderTmp = bfGetReader(thisMoviePath); + bfreaderTmp.setSeries(bfSeriesNoTrue); + + omeMeta = bfreaderTmp.getMetadataStore(); + stackSizeX = omeMeta.getPixelsSizeX(bfSeriesNoTrue).getValue(); % image width, pixels + stackSizeY = omeMeta.getPixelsSizeY(bfSeriesNoTrue).getValue(); % image height, pixels + stackSizeZ = omeMeta.getPixelsSizeZ(bfSeriesNoTrue).getValue(); + nChannels = omeMeta.getChannelCount(bfSeriesNoTrue); + + % Get the number of time points (frames). + nFramesHere = bfreaderTmp.getSizeT; + + xyDims = [stackSizeX stackSizeY]; + + if isempty(thisFrameList) + nFrames = nFramesHere; + framesToGrab = 1:nFrames; + else + nFrames = length(thisFrameList); + framesToGrab = thisFrameList; + end + vidHeight = xyDims(2); + vidWidth = xyDims(1); + + iPlane = bfreaderTmp.getIndex(0, options.bfChannelNo-1, 0)+1; + tmpFrame = bfGetPlane(bfreaderTmp, iPlane); + imgClass = class(tmpFrame); + + % Preallocate movie structure. + if numMovies==1 + outputMovie = zeros(vidHeight, vidWidth, nFrames, imgClass); + else + tmpMovie = zeros(vidHeight, vidWidth, nFrames, imgClass); + end + + % Read one frame at a time. + reverseStr = ''; + iframe = 1; + nFrames = length(framesToGrab); + + + zPlane = 1; + iZ = 1; + dispEvery = round(nFrames/20); + % Adjust for zero-based indexing + % framesToGrab = framesToGrab-1; + + % figure; + reverseStr = ''; + for t = framesToGrab + % Assume 1-based adjust to 0-based indexing + iPlane = bfreaderTmp.getIndex(zPlane-1, options.bfChannelNo-1, t-1)+1; + tmpFrame = bfGetPlane(bfreaderTmp, iPlane); + % figure; + % imagesc(tmpFrame); + if numMovies==1 + outputMovie(:,:,iZ) = tmpFrame; + else + tmpMovie(:,:,iZ) = tmpFrame; + end + iZ = iZ+1; + + if options.displayInfo==1 + reverseStr = ciapkg.view.cmdWaitbar(iZ,nFrames,reverseStr,'inputStr','Loading bio-formats file: ','waitbarOn',options.waitbarOn,'displayEvery',dispEvery); + end + % pause(0.01) + end +end + function [subsets dataDim] = getSubsetOfDataToAnalyze(inputFilePath, options, varargin) import ciapkg.api.* % import CIAtah functions in ciapkg package API. @@ -290,93 +504,4 @@ function downsampleMovieNested(varargin) hReadInfo = hinfo.GroupHierarchy.Groups(thisDatasetName).Datasets; end end -end -% function createHdf5File(filename, datasetName, inputData, varargin) -% % Create the HDF5 file -% fcpl_id = H5P.create('H5P_FILE_CREATE'); -% fapl_id = H5P.create('H5P_FILE_ACCESS'); - -% fid = H5F.create(filename, 'H5F_ACC_TRUNC', fcpl_id, fapl_id); - -% % Create the Space for the Dataset -% initDims = size(inputData); -% h5_initDims = fliplr(initDims); -% maxDims = [initDims(1) initDims(2) -1]; -% h5_maxDims = fliplr(maxDims); -% space_id = H5S.create_simple(3, h5_initDims, h5_maxDims); - -% % Create the Dataset -% % datasetName = '1'; -% dcpl_id = H5P.create('H5P_DATASET_CREATE'); -% chunkSize = [initDims(1) initDims(2) 1]; -% h5_chunkSize = fliplr(chunkSize); -% H5P.set_chunk(dcpl_id, h5_chunkSize); - -% % dsetType_id = H5T.copy('H5T_NATIVE_DOUBLE'); -% dsetType_id = H5T.copy('H5T_NATIVE_UINT16'); - -% dset_id = H5D.create(fid, datasetName, dsetType_id, space_id, dcpl_id); - -% % Initial Data to Write -% % rowDim = initDims(1); colDim = initDims(2); -% initDataToWrite = rand(initDims); - -% % Write the initial data -% H5D.write(dset_id, 'H5ML_DEFAULT', 'H5S_ALL', 'H5S_ALL', 'H5P_DEFAULT', inputData); - -% % Close the open Identifiers -% H5S.close(space_id); -% H5D.close(dset_id); -% H5F.close(fid); - - -% function appendDataToHdf5(filename, datasetName, inputData, varargin) -% % appends 3D data to existing dataset in hdf5 file, assumes x,y are the same, only appending z. - -% % open the dataset and append data to the unlimited dimension which in this case is the second dimension as seen from matlab. -% fid = H5F.open(filename, 'H5F_ACC_RDWR', 'H5P_DEFAULT'); -% dset_id = H5D.open(fid, datasetName); - -% % create the data to be appended -% % dimsOfData = [7 7 42]; -% dimsOfData = size(inputData); -% h5_dimsOfData = fliplr(dimsOfData); - -% % get the dataspace of the dataset to be appended -% space_id = H5D.get_space(dset_id); - -% [~, h5_currDims] = H5S.get_simple_extent_dims(space_id); -% currDims = fliplr(h5_currDims); - -% % update the extend of the dataspace to match the data to be appended -% newDims = currDims; -% newDims(3) = currDims(3) + dimsOfData(3); -% h5_newDims = fliplr(newDims); - -% H5D.set_extent(dset_id, h5_newDims); - -% % Data to append -% % rowDim = dimsOfData(1); colDim = dimsOfData(2); -% % dataToWrite = rand(dimsOfData); - -% % Update the File Space ID such that only the appended data is written. -% H5S.close(space_id); -% space_id = H5D.get_space(dset_id); - -% % Define the hyperslab selection -% start = [0 0 currDims(3)]; h5_start = fliplr(start); -% stride = [1 1 1]; h5_stride = fliplr(stride); -% count = [1 1 1]; h5_count = fliplr(count); -% block = dimsOfData; h5_block = fliplr(block); - -% H5S.select_hyperslab(space_id, 'H5S_SELECT_SET', h5_start, h5_stride, h5_count, h5_block); - -% % Write the Data -% memSpace_id = H5S.create_simple(3, h5_dimsOfData, []); -% H5D.write(dset_id, 'H5ML_DEFAULT', memSpace_id, space_id, 'H5P_DEFAULT', inputData); - -% % Close the open identifiers -% H5S.close(memSpace_id); -% H5S.close(space_id); -% H5D.close(dset_id); -% H5F.close(fid); \ No newline at end of file +end \ No newline at end of file diff --git a/+ciapkg/+hdf5/readHDF5Subset.m b/+ciapkg/+hdf5/readHDF5Subset.m index f973b5c..8bfa0cc 100644 --- a/+ciapkg/+hdf5/readHDF5Subset.m +++ b/+ciapkg/+hdf5/readHDF5Subset.m @@ -1,4 +1,4 @@ -function [dataSubset fid] = readHDF5Subset(inputFilePath, offset, block, varargin) +function [dataSubset, fid] = readHDF5Subset(inputFilePath, offset, block, varargin) % Gets a subset of data from an HDF5 file. % Biafra Ahanonu % started: 2013.11.10 @@ -223,7 +223,7 @@ try if options.displayInfo==1 - display('Block contains extra dimension'); + disp('Block contains extra dimension'); end % offset and size of the block to get, flip dimensions so in format that H5S wants offsetTmp = offset; diff --git a/+ciapkg/+hdf5/writeHDF5Data.m b/+ciapkg/+hdf5/writeHDF5Data.m index cfd2f15..b4ac241 100644 --- a/+ciapkg/+hdf5/writeHDF5Data.m +++ b/+ciapkg/+hdf5/writeHDF5Data.m @@ -1,15 +1,18 @@ function [success] = writeHDF5Data(inputData,fileSavePath,varargin) + % [success] = writeHDF5Data(inputData,fileSavePath,varargin) + % % Saves input data to a HDF5 file, tries to preserve datatype. + % % Biafra Ahanonu % started: 2013.11.01 % % inputs - % inputData: matrix, [x y frames] preferred. - % fileSavePath: str, path where HDF5 file should be saved. + % inputData: matrix, [x y frames] preferred. + % fileSavePath: str, path where HDF5 file should be saved. % outputs - % success = 1 if successful save, 0 if error. + % success = 1 if successful save, 0 if error. % options - % datasetname = HDF5 hierarchy where data should be stored + % datasetname = HDF5 hierarchy where data should be stored % changelog % 2014.01.23 - updated so that it saves as the input data-type rather than defaulting to double @@ -25,6 +28,7 @@ %======================== % old way of saving, only temporary until full switch + % Str: name of HDF5 dataset to save data into. options.datasetname = '/1'; % HDF5: append (don't blank HDF5 file) or new (blank HDF5 file) options.writeMode = 'new'; diff --git a/+ciapkg/+hdf5/writeMatfileToHDF5.m b/+ciapkg/+hdf5/writeMatfileToHDF5.m new file mode 100644 index 0000000..93423a7 --- /dev/null +++ b/+ciapkg/+hdf5/writeMatfileToHDF5.m @@ -0,0 +1,133 @@ +function [success] = writeMatfileToHDF5(inputMatfilePath,varName,outputFilename,varargin) + % [success] = writeMatfileToHDF5(inputMatfilePath,varName,outputFilename,varargin) + % + % Writes a MAT-file dataset to HDF5 by reading the matrix in parts to avoid large overhead. + % + % Biafra Ahanonu + % started: 2022.03.13 [23:49:18] + % + % inputs + % inputMatfilePath - path to matfile object containing data. + % varName - Str: name of variable in MAT-file to save. Should be matrix of [x y frames]. + % inputFilename - Str: path where HDF5 file should be saved. + % + % outputs + % success - Binary: 1 = data saved correctly, 0 = data not saved correctly. + + % changelog + % + % TODO + % + + % ======================== + % Str: name of HDF5 dataset to save data into. + options.datasetname = '/1'; + % HDF5: append (don't blank HDF5 file) or new (blank HDF5 file) + options.writeMode = 'new'; + % Int: Number of frames to use for chunking the data to save in parts. + options.chunkSize = 200; + % Int: Defines gzip compression level (0-9). 0 = no compression, 9 = most compression. + options.deflateLevel = 9; + % Int: chunk size in [x y z] of the dataset, leave empty for auto chunking + options.dataDimsChunkCopy = []; + % Struct: structure of information to add. Will create a HDF5 file + options.addInfo = []; + % Str: e.g. '/movie/processingSettings' + options.addInfoName = ''; + % get options + options = ciapkg.io.getOptions(options,varargin); + % display(options) + % unpack options into current workspace + % fn=fieldnames(options); + % for i=1:length(fn) + % eval([fn{i} '=options.' fn{i} ';']); + % end + % ======================== + + try + success = 0; + + % Create matfile object. + inputObj = matfile(inputMatfilePath); + + % Get size of the input data, call this way instead of size(inputObj.varName) to avoid loading entire dataset into memory. + dataDims = size(inputObj,varName); + + if length(dataDims)~=3 + disp('Data is not a 3D matrix of size [:, :, >1], returning...') + return; + end + + % Get coordinates to use. + nFrames = dataDims(3); + subsetSize = options.chunkSize; + movieLength = nFrames; + numSubsets = ceil(movieLength/subsetSize)+1; + subsetList = round(linspace(1,movieLength,numSubsets)); + nSubsets = (length(subsetList)-1); + + % Write data out in chunks. + for thisSet = 1:nSubsets + % subsetStartTime = tic; + + subsetStartIdx = subsetList(thisSet); + subsetEndIdx = subsetList(thisSet+1); + disp(repmat('$',1,7)) + if thisSet==nSubsets + movieSubset = subsetStartIdx:subsetEndIdx; + else + movieSubset = subsetStartIdx:(subsetEndIdx-1); + end + disp([num2str(movieSubset(1)) '-' num2str(movieSubset(end)) ' ' num2str(thisSet) '/' num2str(nSubsets)]) + + % Slice into the desired variable using dynamic field references to avoid loading entire dataset into memory. + inputDataSlice = inputObj.(varName)(:,:,movieSubset); + + if thisSet==1 + ciapkg.hdf5.createHdf5File(outputFilename, options.datasetname, inputDataSlice,'deflateLevel',options.deflateLevel,'dataDimsChunkCopy',options.dataDimsChunkCopy); + else + ciapkg.hdf5.appendDataToHdf5(outputFilename, options.datasetname, inputDataSlice); + end + + % toc(subsetStartTime) + end + + % add information about data to HDF5 file + if strcmp(options.writeMode,'new') + hdf5write(outputFilename,'/movie/info/dimensions',dataDims,'WriteMode','append'); + currentDateTimeStr = datestr(now,'yyyymmdd_HHMM','local'); + hdf5write(outputFilename,'/movie/info/date',currentDateTimeStr,'WriteMode','append'); + hdf5write(outputFilename,'/movie/info/savePath',outputFilename,'WriteMode','append'); + hdf5write(outputFilename,'/movie/info/Deflate',options.deflateLevel,'WriteMode','append'); + end + if ~isempty(options.addInfo) + if ~iscell(options.addInfo) + options.addInfo = {options.addInfo}; + options.addInfoName = {options.addInfoName}; + end + addInfoLen = length(options.addInfo); + for addInfoStructNo = 1:addInfoLen + thisAddInfo = options.addInfo{addInfoStructNo}; + infoList = fieldnames(thisAddInfo); + nInfo = length(infoList); + for fieldNameNo = 1:nInfo + thisField = infoList{fieldNameNo}; + hdf5write(outputFilename,[options.addInfoName{addInfoStructNo} '/' thisField],thisAddInfo.(thisField),'WriteMode','append'); + % h5write(fileSavePath,[options.addInfoName{addInfoStructNo} '/' thisField],thisAddInfo.(thisField),'WriteMode','append'); + end + end + end + + success = 1; + catch err + success = 0; + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end + + % function [outputs] = nestedfxn_exampleFxn(arg) + % % Always start nested functions with "nestedfxn_" prefix. + % % outputs = ; + % end +end \ No newline at end of file diff --git a/+ciapkg/+image/computeImageFeatures.m b/+ciapkg/+image/computeImageFeatures.m index 2e00237..906839f 100644 --- a/+ciapkg/+image/computeImageFeatures.m +++ b/+ciapkg/+image/computeImageFeatures.m @@ -1,12 +1,18 @@ function [imgStats] = computeImageFeatures(inputImages, varargin) + % [imgStats] = computeImageFeatures(inputImages, varargin) + % % Filters large and small objects in an set of images, returns filtered matricies along with vector with decisions and sizes. + % % Biafra Ahanonu % 2013.10.31 % based on SpikeE code + % % inputs % inputImages - [x y nSignals] + % % outputs % imgStats - + % % options % minNumPixels % maxNumPixels @@ -17,8 +23,10 @@ % 2017.01.14 [20:06:04] - support switched from [nSignals x y] to [x y nSignals] % 2019.07.17 [00:29:16] - Added support for sparse input images (mainly ndSparse format). % 2019.09.10 [20:51:00] - Converted to parfor and removed unpacking options, bad practice. - % 2019.10.02 [21:25:36] - Updated addedFeatures support for parfor and allowed this feature to be properly accessed by users. + % 2019.10.02 [21:25:36] - Updated addedFeatures support for parfor and allowed this feature to be properly accessed by users. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.04.13 [02:28:15] - Make default thresholding fast thresholding. + % 2022.07.20 [14:42:00] - Improved annotation of code and added options.fastThresholding. Also by default plots are not made. Refactored extra features code and finding centroid to avoid unnecessary function calls, speeding up code. % TODO % @@ -28,7 +36,7 @@ % get options options.minNumPixels = 25; options.maxNumPixels = 600; - options.makePlots = 1; + options.makePlots = 0; options.waitbarOn = 1; options.thresholdImages = 1; options.threshold = 0.5; @@ -40,11 +48,18 @@ % Input images for add features options.addedFeaturesInputImages = []; options.runRegionprops = 1; - + % options.xCoords = []; options.yCoords = []; - + % options.parforAltSwitch = 0; + % Binary: 1 = fast thresholding (vectorized), 0 = normal thresholding + options.fastThresholding = 1; + % image filter: none, median, + options.imageFilter = 'none'; + + % Binary: 1 = whether to display info on command line. + options.displayInfo = 1; options = getOptions(options,varargin); % unpack options into current workspace @@ -72,15 +87,7 @@ if options.addedFeatures==1&&isempty(options.addedFeaturesInputImages) options.addedFeaturesInputImages = inputImages; end - inputImages = thresholdImages(inputImages,'waitbarOn',1,'binary',1,'threshold',options.threshold); - end - - % get the centroids and other info for movie - if isempty(options.xCoords) - [xCoords, yCoords] = findCentroid(inputImages,'waitbarOn',options.waitbarOn,'runImageThreshold',0); - else - xCoords = options.xCoords; - yCoords = options.yCoords; + inputImages = thresholdImages(inputImages,'waitbarOn',1,'binary',1,'threshold',options.threshold,'fastThresholding',options.fastThresholding,'removeUnconnected',1,'imageFilter',options.imageFilter); end % Only implement in Matlab 2017a and above @@ -97,7 +104,9 @@ options_waitbarOn = options.waitbarOn; % loop over images and get their stats - disp('Computing image features...') + if options.displayInfo==1 + disp('Computing image features...') + end regionStat = cell([nImages 1]); if options_runRegionprops==1 % ticBytes(gcp) @@ -110,18 +119,6 @@ thisFilt = full(thisFilt); end regionStat{imageNo} = regionprops(thisFilt, options_featureList); - % regionStat - % figure;imagesc(inputImages(:,:,imageNo)); - % for ifeature = featureList - % % regionStat = regionprops(iImage, ifeature{1}); - % try - % % eval(['imgStats.' ifeature{1} '(imageNo) = regionStat.' ifeature{1} ';']); - % imgStats.(ifeature{1})(imageNo) = regionStat.(ifeature{1}); - % catch - % % eval(['imgStats.' ifeature{1} '(imageNo) = NaN;']); - % imgStats.(ifeature{1})(imageNo) = NaN; - % end - % end if ~verLessThan('matlab', '9.2') send(D, imageNo); % Update end @@ -131,7 +128,9 @@ % Add region states to the general pool if ~verLessThan('matlab', '9.2'); p=1;end - disp('Adding regionprops features to output...') + if options.displayInfo==1 + disp('Adding regionprops features to output...') + end for imageNo = 1:nImages for ifeature = options_featureList % regionStat = regionprops(iImage, ifeature{1}); @@ -152,12 +151,23 @@ if ~verLessThan('matlab', '9.2'); p=1;end - if parforAltSwitch==1 - disp('Computing alternative image features with parfor...') - imgKurtosis = NaN([1 nImages]); - imgSkewness = NaN([1 nImages]); - if options.addedFeatures==1 - addedFeaturesInputImages = options.addedFeaturesInputImages; + + if options.addedFeatures==1 + % get the centroids and other info for movie + if isempty(options.xCoords) + [xCoords, yCoords] = findCentroid(inputImages,'waitbarOn',options.waitbarOn,'runImageThreshold',0); + else + xCoords = options.xCoords; + yCoords = options.yCoords; + end + + if parforAltSwitch==1 + imgKurtosis = NaN([1 nImages]); + imgSkewness = NaN([1 nImages]); + if options.displayInfo==1 + disp('Computing alternative image features with parfor...') + end + addedFeaturesInputImages = options.addedFeaturesInputImages; parfor imageNo = 1:nImages thisFilt = addedFeaturesInputImages(:,:,imageNo); if issparse(thisFilt) @@ -179,11 +189,11 @@ imgStats.imgKurtosis = imgKurtosis; imgStats.imgSkewness = imgSkewness; - end - else - disp('Computing alternative image features...') - for imageNo = 1:nImages - if options.addedFeatures==1 + else + if options.displayInfo==1 + disp('Computing alternative image features...') + end + for imageNo = 1:nImages % iImage2 = squeeze(options.addedFeaturesInputImages(:,:,imageNo)); % figure(11);imagesc(iImage2);title(num2str(imageNo)) % [imageNo xCoords(imageNo) yCoords(imageNo)] @@ -203,22 +213,9 @@ end % imgStats.imgKurtosis(imageNo) = kurtosis(iImage(:)); % imgStats.imgSkewness(imageNo) = skewness(iImage(:)); - else - - end - % regionStat = regionprops(iImage, 'Eccentricity','EquivDiameter','Area','Orientation','Perimeter','Solidity'); - % imgStats.Eccentricity(imageNo) = regionStat.Eccentricity; - % imgStats.EquivDiameter(imageNo) = regionStat.EquivDiameter; - % imgStats.Area(imageNo) = regionStat.Area; - % imgStats.Orientation(imageNo) = regionStat.Orientation; - % imgStats.Perimeter(imageNo) = regionStat.Perimeter; - % imgStats.Solidity(imageNo) = regionStat.Solidity; - - % if (imageNo==1||mod(imageNo,10)==0||imageNo==nImages)&&options.waitbarOn==1 - % reverseStr = cmdWaitbar(imageNo,nImages,reverseStr,'inputStr','computing image features'); - % end - if ~verLessThan('matlab', '9.2') - send(D, imageNo); % Update + if ~verLessThan('matlab', '9.2') + send(D, imageNo); % Update + end end end end @@ -260,15 +257,6 @@ xlabel('rank'); ylabel(fn{i}) hold off end - - % subplot(2,1,1) - % scatter3(imgStats.Eccentricity(valid),imgStats.Perimeter(valid),imgStats.Orientation(valid),[pointColor '.']) - % xlabel('Eccentricity');ylabel('perimeter');zlabel('Orientation'); - % rotate3d on;hold on; - % subplot(2,1,2) - % scatter3(imgStats.Area(valid),imgStats.Perimeter(valid),imgStats.Solidity(valid),[pointColor '.']) - % xlabel('area');ylabel('perimeter');zlabel('solidity'); - % rotate3d on;hold on; end end @@ -288,4 +276,41 @@ function nUpdateParforProgress(~) end end end -end \ No newline at end of file +end + +% OLD code + + +% regionStat +% figure;imagesc(inputImages(:,:,imageNo)); +% for ifeature = featureList +% % regionStat = regionprops(iImage, ifeature{1}); +% try +% % eval(['imgStats.' ifeature{1} '(imageNo) = regionStat.' ifeature{1} ';']); +% imgStats.(ifeature{1})(imageNo) = regionStat.(ifeature{1}); +% catch +% % eval(['imgStats.' ifeature{1} '(imageNo) = NaN;']); +% imgStats.(ifeature{1})(imageNo) = NaN; +% end +% end + +% regionStat = regionprops(iImage, 'Eccentricity','EquivDiameter','Area','Orientation','Perimeter','Solidity'); +% imgStats.Eccentricity(imageNo) = regionStat.Eccentricity; +% imgStats.EquivDiameter(imageNo) = regionStat.EquivDiameter; +% imgStats.Area(imageNo) = regionStat.Area; +% imgStats.Orientation(imageNo) = regionStat.Orientation; +% imgStats.Perimeter(imageNo) = regionStat.Perimeter; +% imgStats.Solidity(imageNo) = regionStat.Solidity; + +% if (imageNo==1||mod(imageNo,10)==0||imageNo==nImages)&&options.waitbarOn==1 +% reverseStr = cmdWaitbar(imageNo,nImages,reverseStr,'inputStr','computing image features'); +% end + +% subplot(2,1,1) +% scatter3(imgStats.Eccentricity(valid),imgStats.Perimeter(valid),imgStats.Orientation(valid),[pointColor '.']) +% xlabel('Eccentricity');ylabel('perimeter');zlabel('Orientation'); +% rotate3d on;hold on; +% subplot(2,1,2) +% scatter3(imgStats.Area(valid),imgStats.Perimeter(valid),imgStats.Solidity(valid),[pointColor '.']) +% xlabel('area');ylabel('perimeter');zlabel('solidity'); +% rotate3d on;hold on; \ No newline at end of file diff --git a/+ciapkg/+image/filterImages.m b/+ciapkg/+image/filterImages.m index 6ab1b37..bbd66df 100644 --- a/+ciapkg/+image/filterImages.m +++ b/+ciapkg/+image/filterImages.m @@ -15,6 +15,7 @@ % 2017.01.14 [20:06:04] - support switched from [nSignals x y] to [x y nSignals] % 2019.07.17 [00:29:16] - Added support for sparse input images (mainly ndSparse format). % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.04.13 [02:28:15] - Make default thresholding fast thresholding. % TODO % @@ -68,9 +69,9 @@ if options.thresholdImages==1 if options.modifyInputImage==1 - [inputImages,~,numObjects] = thresholdImages(inputImages,'waitbarOn',options.waitbarOn,'binary',1,'threshold',options.threshold); + [inputImages,~,numObjects] = thresholdImages(inputImages,'waitbarOn',options.waitbarOn,'binary',1,'threshold',options.threshold,'fastThresholding',1); else - [inputImagesCopy,~,numObjects] = thresholdImages(inputImages,'waitbarOn',options.waitbarOn,'binary',1,'threshold',options.threshold); + [inputImagesCopy,~,numObjects] = thresholdImages(inputImages,'waitbarOn',options.waitbarOn,'binary',1,'threshold',options.threshold,'fastThresholding',1); end else if options.modifyInputImage==1 diff --git a/+ciapkg/+image/findCentroid.m b/+ciapkg/+image/findCentroid.m index 2b04804..a893ed8 100644 --- a/+ciapkg/+image/findCentroid.m +++ b/+ciapkg/+image/findCentroid.m @@ -12,6 +12,8 @@ % 2016.08.06 - some changes to speed up algorithm by using thresholded rather than weighted sum of image. % 2017.01.14 [20:06:04] - support switched from [nSignals x y] to [x y nSignals] % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.04.13 [02:28:15] - Make default thresholding fast thresholding. + % 2022.04.23 [17:30:06] - Update to ensure threshold based on max value if user inputs non-normalized (e.g. distribution forced to zero to one) thresholded images. % TODO % @@ -53,7 +55,7 @@ end if options.runImageThreshold==1 - inputMatrixThreshold = thresholdImages(inputMatrix,'waitbarOn',options.waitbarOn,'threshold',options.imageThreshold,'removeUnconnected',1); + inputMatrixThreshold = thresholdImages(inputMatrix,'waitbarOn',options.waitbarOn,'threshold',options.imageThreshold,'removeUnconnected',1,'fastThresholding',1); else inputMatrixThreshold = inputMatrix; end @@ -76,7 +78,7 @@ % get the sum of the image % imagesum = sum(thisImage(:)); % get coordinates - [i,j,imgValue] = find(thisImage > options_thresholdValue); + [i,j,imgValue] = find(thisImage > (options_thresholdValue*max(thisImage(:),[],'omitnan'))); % weight the centroid by the intensity of the image % imgValue = imgValue*0; imgValue = imgValue+1; diff --git a/+ciapkg/+image/groupImagesByColor.m b/+ciapkg/+image/groupImagesByColor.m index daf4da1..4a796c7 100644 --- a/+ciapkg/+image/groupImagesByColor.m +++ b/+ciapkg/+image/groupImagesByColor.m @@ -10,14 +10,21 @@ % changelog % 2017.01.14 [20:06:04] - support switched from [nSignals x y] to [x y nSignals] % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.07.20 [14:47:26] - Added fast thresholding option and other threshold image options. % TODO % import ciapkg.api.* % import CIAtah functions in ciapkg package API. %======================== - % 1 = threshold images, 0 = images already thresholded + % Binary: 1 = threshold images, 0 = images already thresholded options.thresholdImages = 1; + % Float: fraction of image maximum value below which all pixels set to zero (range 0:1) + options.threshold = 0.5; + % Binary: 1 = fast thresholding (vectorized), 0 = normal thresholding + options.fastThresholding = 1; + % image filter: none, median, + options.imageFilter = 'none'; % get options options = getOptions(options,varargin); % display(options) @@ -29,7 +36,8 @@ %======================== if options.thresholdImages==1 - [thresholdedImages] = thresholdImages(inputImages,'binary',1,'waitbarOn',0); + disp('Thresholding images...') + [thresholdedImages] = thresholdImages(inputImages,'binary',1,'waitbarOn',0,'threshold',options.threshold,'fastThresholding',options.fastThresholding,'imageFilter',options.imageFilter); else thresholdedImages = inputImages; end diff --git a/+ciapkg/+image/thresholdImages.m b/+ciapkg/+image/thresholdImages.m index 3490a5e..731237b 100644 --- a/+ciapkg/+image/thresholdImages.m +++ b/+ciapkg/+image/thresholdImages.m @@ -24,6 +24,7 @@ % 2021.07.06 [10:12:33] - Added support for fast thresholding using vectorized form, faster than parfor loop. % 2021.07.06 [18:57:02] - Fast border calculation using convn for options.fastThresholding==1, less precise than bwboundaries or bwareafilt but works for fast display purposes. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.06.27 [19:41:34] - manageParallelWorkers now passed options.waitbarOn value to reduce command line clutter if user request in thresholdImages. % TODO % @@ -88,7 +89,7 @@ % pre-allocate for speed % thresholdedImages = zeros(size(inputImages),class(inputImages)); boundaryIndices = cell([nImages 1]); - manageParallelWorkers('parallel',options.parallel); + manageParallelWorkers('parallel',options.parallel,'displayInfo',options.waitbarOn); if options.waitbarOn==1 disp('thresholding images...') end @@ -154,6 +155,13 @@ % body end + if options.removeUnconnectedBinary==1 + parfor(imageNo=1:nImages,nWorkers) + thisFilt = inputImages(:,:,imageNo); + [~,numObjects(imageNo)] = bwlabel(thisFilt); + end + end + if options_getBoundaryIndex==1 % inputImagesTmp = diff(inputImages>0,[],1); % inputImagesTmp = convn(inputImages>0,[-1 -1 -1; -1 1 -1; -1 -1 -1;]); diff --git a/+ciapkg/+io/getFileInfo.m b/+ciapkg/+io/getFileInfo.m index 6e093eb..07287ff 100644 --- a/+ciapkg/+io/getFileInfo.m +++ b/+ciapkg/+io/getFileInfo.m @@ -13,6 +13,7 @@ % 2015.12.11 - Noting modifications to allow a second type of file format to be supported, mostly for antipsychotic analysis % 2017.04.15 - added multiplane support % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.03.18 [10:10:11] - Add trial # to output. % TODO % @@ -125,16 +126,26 @@ catch fileInfo.assayNum = 0; end + + % ======= % get trial trialListOriginal = ['(' options.trialList{:} ')']; trialList = strcat(trialListOriginal, '\d+'); - fileInfo.trial = regexp(fileStr,trialListOriginal, 'match'); + % fileInfo.trial = regexp(fileStr,trialListOriginal, 'match'); + fileInfo.trial = regexp(fileStr,trialList, 'match'); % add NULL string if no assay found if ~isempty(fileInfo.trial) fileInfo.trial = fileInfo.trial{1}; else fileInfo.trial = 'NULL000'; end + % % get out the trial number + try + tokenMatches = regexp(fileInfo.trial,'\d+','match'); + fileInfo.trialNo = str2num(cell2mat(tokenMatches(end))); + catch + fileInfo.trialNo = 0; + end % date fileInfo.date = regexp(fileStr,options.dateRegexp, 'match'); diff --git a/+ciapkg/+io/getFileList.m b/+ciapkg/+io/getFileList.m index 8fcb7cb..a9b345d 100644 --- a/+ciapkg/+io/getFileList.m +++ b/+ciapkg/+io/getFileList.m @@ -1,12 +1,13 @@ function [fileList] = getFileList(inputDir, filterExp,varargin) - % Gathers a list of files based on an input regular expression. + % [fileList] = getFileList(inputDir, filterExp,varargin) + % Gathers a list of files or folders in a directory based on an input regular expression. % Biafra Ahanonu % started: 2013.10.08 [11:02:31] % inputs - % inputDir - directory to gather files from and regexp filter for files - % filterExp - regexp used to find files + % inputDir - directory to gather files from and regexp filter for files + % filterExp - regexp used to find files/folders. % outputs - % file list, full path + % fileList - cell array of strings containing identified files or folders. % changelog % 2014.03.21 - added feature to input cell array of filters @@ -14,8 +15,10 @@ % 2019.03.08 [13:12:59] - added support for natural sorting of files % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2021.09.10 [03:17:56] - Added support to exclude adding the input directory to each file path. + % 2022.05.26 [22:24:42] - Improved support multiple directory input. + % 2022.06.12 [16:42:24] - Added folder filter. % TODO - % Fix recusive to recursive in a backwards compatible way + % [DONE] Fix "recusive" (sp) option to recursive in a backwards compatible way. import ciapkg.api.* % import CIAtah functions in ciapkg package API. @@ -31,6 +34,8 @@ % Char: lexicographic (e.g. 1 10 11 2 21 22 unless have 01 02 10 11 21 22) or numeric (e.g. 1 2 10 11 21 22) or natural (e.g. 1 2 10 11 21 22) % options.sortMethod = 'lexicographic'; options.sortMethod = 'natural'; + % Binary: 1 = only include folders in the output. 0 = include folders and files. + options.onlyFolders = 0; % DEPRECIATED 1 = recursively find files in all sub-directories. 0 = only find files in inputDir directory. options.recusive = 0; % get options @@ -55,8 +60,12 @@ end fileList = {}; - for thisDir = inputDir - thisDirHere = thisDir{1}; + nDirs = length(inputDir); + %for thisDir = inputDir + for i = 1:nDirs + thisDir = inputDir{i}; + % thisDirHere = thisDir{i}; + thisDirHere = thisDir; if options.recusive==0 files = dir(thisDirHere); else @@ -68,7 +77,11 @@ if options.regexpWithFolder==1 filename = [options.regexpWithFolder filesep filename]; end - + % If option selected, remove non-folders. + if options.onlyFolders==1&&isfolder(filename)==0 + continue + end + % Add found file/folder to the list. if(~isempty(cell2mat(regexpi(filename, filterExp)))) if options.addInputDirToPath==1 fileList{end+1} = [thisDirHere filesep filename]; @@ -81,6 +94,11 @@ else filename = files(file,:); filename = filename{1}; + % If option selected, remove non-folders. + if options.onlyFolders==1&&isfolder(filename)==0 + continue + end + % Add found file/folder to the list. if(~isempty(cell2mat(regexpi(filename, filterExp)))) if options.addInputDirToPath==1 fileList{end+1} = [filename]; diff --git a/+ciapkg/+io/getMovieFileType.m b/+ciapkg/+io/getMovieFileType.m index fe45c72..30239be 100644 --- a/+ciapkg/+io/getMovieFileType.m +++ b/+ciapkg/+io/getMovieFileType.m @@ -4,7 +4,7 @@ % Determine whether movie is a type supported by CIAtah, don't assume every movie in list is of the same type. % % Biafra Ahanonu - % started: 2020.09.01 [‏‎14:16:57] + % started: 2020.09.01 [14:16:57] % % Inputs % thisMoviePath - String: path to movie file. @@ -21,6 +21,8 @@ % 2022.01.04 [13:28:05] - Update docs. % 2022.02.24 [09:37:55] - Added varargin support. % 2022.03.01 [08:56:21] - Added support for checking if a cell was accidentally input instead of a string path along with verifying that input was a string. Added support for oir and czi/lsm Olympus and Zeiss file formats that already was in loadMovieList. + % 2022.03.14 [02:17:28] - Added MAT-file support. + % 2022.07.05 [21:21:35] - Add SlideBook Bio-Formats support. % TODO % @@ -77,12 +79,16 @@ movieType = 'tiff'; elseif endsWith(ext,'.avi','IgnoreCase',true) movieType = 'avi'; + elseif endsWith(ext,'.mat','IgnoreCase',true) + movieType = 'mat'; elseif endsWith(ext,'.isxd','IgnoreCase',true) % Inscopix file format movieType = 'isxd'; elseif endsWith(ext,'.oir') % Olympus file format movieType = 'bioformats'; elseif endsWith(ext,{'.czi','.lsm'}) % Zeiss file format movieType = 'bioformats'; + elseif endsWith(ext,'.sld') % SlideBook file format + movieType = 'bioformats'; else movieType = ''; supported = 0; diff --git a/+ciapkg/+io/getOptions.m b/+ciapkg/+io/getOptions.m index 4d05999..9a3d7d2 100644 --- a/+ciapkg/+io/getOptions.m +++ b/+ciapkg/+io/getOptions.m @@ -1,13 +1,17 @@ function [options] = getOptions(options,inputArgs,varargin) + % [options] = getOptions(options,inputArgs,varargin) + % % Gets default options for a function and replaces them with inputArgs inputs if they are present in Name-Value pair input (e.g. varargin). + % % Biafra Ahanonu % Started: 2013.11.04. % % inputs - % options - structure passed by parent function with each fieldname containing an option to be used by the parent function. - % inputArgs - an even numbered cell array, with {'option','value'} as the ordering. Normally pass varargin. + % options - structure passed by parent function with each fieldname containing an option to be used by the parent function. + % inputArgs - an even numbered cell array, with {'option','value'} as the ordering. Normally pass varargin. % Outputs - % options - options structure passed back to parent function with modified Name-Value inputs to function added. + % options - options structure passed back to parent function with modified Name-Value inputs to function added. + % % NOTE % Use the 'options' name-value pair to input an options structure that will overwrite default options in a function, example below. % options.Stargazer = 1; @@ -58,6 +62,7 @@ % 2020.06.29 [18:54:56] - Support case where calling getOptions from command line or where there is no stack. % 2020.09.29 [13:21:09] - Added passArgs option, this mimics the ... construct in R, so users can pass along arguments without having to define them in the calling function (e.g. in the case of wrapper functions). % 2021.10.07 [10:44:30] - Ensure no warnings are shown. Added fix to handle users calling API version of getOptions with getOptions variable input arguments. + % 2022.03.03 [15:06:54] - In 2021a MATLAB introduced Name=value syntax for passing name-value arguments. These are passed within varargin as a string array instead of a char array as was the case with comma-separated syntax. getOptions now checks for isstring in addition to ischar to add support for this syntax to CIAtah functions. See https://www.mathworks.com/help/matlab/release-notes.html?rntext=&startrelease=R2021a&endrelease=R2021a&groupby=release&sortby=descending&searchHighlight=#mw_77c0932f-4a31-44e6-b550-db5a736c2de3. % TODO % Allow input of an option structure - DONE! @@ -128,7 +133,7 @@ for i = 1:2:length(inputArgs) % inputArgs = inputArgs{1}; val = inputArgs{i}; - if ischar(val) + if ischar(val)||isstring(val) %display([inputArgs{i} ': ' num2str(inputArgs{i+1})]); if strcmp('options',val) % Special options struct, only add field names defined by the user. Keep all original field names that are not input by the user. diff --git a/+ciapkg/+io/loadDependencies.m b/+ciapkg/+io/loadDependencies.m index e536a18..cfe11a2 100644 --- a/+ciapkg/+io/loadDependencies.m +++ b/+ciapkg/+io/loadDependencies.m @@ -22,6 +22,12 @@ function loadDependencies(varargin) % 2021.07.23 [00:22:22] - Added gramm (https://github.com/piermorel/gramm) support/downloading for graphics plotting. % 2021.07.26 [13:16:37] - Added Turboreg (moved from within ciapkg) to make explicit that this is an external program. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.03.04 [06:57:14] - Added PatchWarp (https://github.com/ryhattori/PatchWarp). Code clean up. + % 2022.03.09 [17:01:54] - Updated NoRMCorre to use https://github.com/bahanonu/NoRMCorre as that is a package version, cleaner namespace. + % 2022.04.08 [15:17:28] - Added CIAtah utilities repository (https://github.com/bahanonu/ciatah_utils) to separate outside code from main repository. + % 2022.07.10 [20:27:29] - Add SlideBook .jar reader to Bio-Formats download. + % 2022.09.14 [09:33:53] - Ensure NoRMCorre is in default options. + % 2022.09.14 [09:47:20] - Ensure bfmatlab_readers directory exists, else websave errors occur. % TODO % Verify all dependencies download and if not ask user to download again. @@ -31,11 +37,39 @@ function loadDependencies(varargin) % DESCRIPTION options.externalProgramsDir = ciapkg.getDirExternalPrograms(); options.guiEnabled = 1; - options.dependencyStr = {'downloadTurboreg','downloadImageJ','downloadCnmfGithubRepositories','example_downloadTestData','downloadNeuroDataWithoutBorders','downloadEXTRACT','downloadBioFormats','downloadGramm','downloadNoRMCorre','downloadMiji','loadMiji'}; + options.dependencyStr = {... + 'downloadCIAtahUtils'; + 'downloadTurboreg'; + 'downloadImageJ'; + 'downloadCnmfGithubRepositories'; + 'example_downloadTestData'; + 'downloadNeuroDataWithoutBorders'; + 'downloadEXTRACT'; + 'downloadBioFormats'; + 'downloadGramm'; + 'downloadNoRMCorre'; + 'downloadMiji'; + 'downloadPatchWarp'; + 'loadMiji'; + }; - options.dispStr = {'Download Turboreg (motion correction)','Download ImageJ','Download CNMF, CNMF-E, and CVX code.','Download test one- and two photon datasets.','Download NWB (NeuroDataWithoutBorders)','Download EXTRACT','Download Bio-Formats','Download gramm (GRAMmar of graphics for Matlab, e.g. ggplot2-like)','Download NoRMCorre (motion correction)','Download Fiji (to run Miji)','Load Fiji/Miji into MATLAB path.'}; + options.dispStr = {... + 'Download CIAtah utilities/dependencies'; + 'Download Turboreg (motion correction)'; + 'Download ImageJ'; + 'Download CNMF, CNMF-E, and CVX code.'; + 'Download test one- and two photon datasets.'; + 'Download NWB (NeuroDataWithoutBorders)'; + 'Download EXTRACT'; + 'Download Bio-Formats'; + 'Download gramm (GRAMmar of graphics for Matlab, e.g. ggplot2-like)'; + 'Download NoRMCorre (motion correction)'; + 'Download Fiji (to run Miji)'; + 'Download PatchWarp (motion correction)'; + 'Load Fiji/Miji into MATLAB path.'; + }; % Int vector: index of options.dependencyStr to run by default with no GUI - options.depIdxArray = [1 2 3 4 5 6 7 8]; + options.depIdxArray = [1 2 3 4 5 6 7 8 9 10]; % Binary: 1 = force update even if already downloaded. 0 = skip if already downloaded options.forceUpdate = 0; % get options @@ -73,6 +107,10 @@ function loadDependencies(varargin) for depNo = 1:length(depIdxArray) disp([10 repmat('>',1,42)]) disp(dispStr{depNo}) + + optionsH.forceUpdate = forceUpdate; + optionsH.signalExtractionDir = options.externalProgramsDir; + switch analysisTypeD{depNo} case 'downloadCnmfGithubRepositories' [success] = downloadCnmfGithubRepositories('forceUpdate',forceUpdate); @@ -95,9 +133,17 @@ function loadDependencies(varargin) modelAddOutsideDependencies('miji'); case 'example_downloadTestData' example_downloadTestData(); + case 'downloadCIAtahUtils' + optionsH.gitNameDisp = {'ciatah_utils'}; + % optionsH.gitRepos = {'https://github.com/flatironinstitute/NoRMCorre'}; + optionsH.gitRepos = {'https://github.com/bahanonu/ciatah_utils'}; + optionsH.gitRepos = cellfun(@(x) [x '/archive/master.zip'],optionsH.gitRepos,'UniformOutput',false); + optionsH.outputDir = optionsH.gitNameDisp; + % optionsH.gitName = cellfun(@(x) [x '-master'],optionsH.gitNameDisp,'UniformOutput',false); + % optionsH.gitName = {'NoRMCorre-public-master'}; + optionsH.gitName = {'ciatah_utils-master'}; + [success] = downloadGithubRepositories('options',optionsH); case 'downloadTurboreg' - optionsH.forceUpdate = forceUpdate; - optionsH.signalExtractionDir = options.externalProgramsDir; optionsH.gitNameDisp = {'turboreg'}; optionsH.gitRepos = {'http://tiny.ucsf.edu/ciatahTurboreg'}; optionsH.outputDir = optionsH.gitNameDisp; @@ -105,8 +151,6 @@ function loadDependencies(varargin) optionsH.gitName = {'Motion_Correction_Turboreg'}; [success] = downloadGithubRepositories('options',optionsH); case 'downloadCellExtraction' - optionsH.forceUpdate = forceUpdate; - optionsH.signalExtractionDir = options.externalProgramsDir; optionsH.gitNameDisp = {'cellmax_clean','extract'}; optionsH.gitRepos = {'https://github.com/schnitzer-lab/CELLMax_CLEAN','https://github.com/schnitzer-lab/EXTRACT'}; optionsH.gitRepos = cellfun(@(x) [x '/archive/master.zip'],optionsH.gitRepos,'UniformOutput',false); @@ -114,18 +158,24 @@ function loadDependencies(varargin) optionsH.gitName = cellfun(@(x) [x '-master'],optionsH.gitNameDisp,'UniformOutput',false); [success] = downloadGithubRepositories('options',optionsH); case 'downloadNoRMCorre' - optionsH.forceUpdate = forceUpdate; - optionsH.signalExtractionDir = options.externalProgramsDir; optionsH.gitNameDisp = {'normcorre'}; - optionsH.gitRepos = {'https://github.com/flatironinstitute/NoRMCorre'}; + % optionsH.gitRepos = {'https://github.com/flatironinstitute/NoRMCorre'}; + optionsH.gitRepos = {'https://github.com/bahanonu/NoRMCorre'}; optionsH.gitRepos = cellfun(@(x) [x '/archive/master.zip'],optionsH.gitRepos,'UniformOutput',false); optionsH.outputDir = optionsH.gitNameDisp; % optionsH.gitName = cellfun(@(x) [x '-master'],optionsH.gitNameDisp,'UniformOutput',false); - optionsH.gitName = {'NoRMCorre-public-master'}; + % optionsH.gitName = {'NoRMCorre-public-master'}; + optionsH.gitName = {'NoRMCorre-master'}; + [success] = downloadGithubRepositories('options',optionsH); + case 'downloadPatchWarp' + optionsH.gitNameDisp = {'patchwarp'}; + optionsH.gitRepos = {'https://github.com/ryhattori/PatchWarp'}; + optionsH.gitRepos = cellfun(@(x) [x '/archive/master.zip'],optionsH.gitRepos,'UniformOutput',false); + optionsH.outputDir = optionsH.gitNameDisp; + % optionsH.gitName = cellfun(@(x) [x '-master'],optionsH.gitNameDisp,'UniformOutput',false); + optionsH.gitName = {'PatchWarp-main'}; [success] = downloadGithubRepositories('options',optionsH); case 'downloadGramm' - optionsH.forceUpdate = forceUpdate; - optionsH.signalExtractionDir = options.externalProgramsDir; optionsH.gitNameDisp = {'gramm'}; optionsH.gitRepos = {'https://github.com/piermorel/gramm'}; optionsH.gitRepos = cellfun(@(x) [x '/archive/master.zip'],optionsH.gitRepos,'UniformOutput',false); @@ -134,8 +184,6 @@ function loadDependencies(varargin) optionsH.gitName = {'gramm-master'}; [success] = downloadGithubRepositories('options',optionsH); case 'downloadEXTRACT' - optionsH.forceUpdate = forceUpdate; - optionsH.signalExtractionDir = options.externalProgramsDir; optionsH.gitNameDisp = {'extract'}; optionsH.gitRepos = {'https://github.com/schnitzer-lab/EXTRACT-public'}; optionsH.gitRepos = cellfun(@(x) [x '/archive/master.zip'],optionsH.gitRepos,'UniformOutput',false); @@ -144,8 +192,6 @@ function loadDependencies(varargin) optionsH.gitName = {'EXTRACT-public-master'}; [success] = downloadGithubRepositories('options',optionsH); case 'downloadNeuroDataWithoutBorders' - optionsH.forceUpdate = forceUpdate; - optionsH.signalExtractionDir = options.externalProgramsDir; optionsH.gitNameDisp = {'nwbpkg','yamlmatlab','matnwb'}; optionsH.gitRepos = {'https://github.com/schnitzer-lab/nwbpkg','https://github.com/ewiger/yamlmatlab'}; % 'https://github.com/NeurodataWithoutBorders/matnwb' @@ -163,14 +209,25 @@ function loadDependencies(varargin) ciapkg.nwb.setupNwb('checkDependencies',0); % obj.loadBatchFunctionFolders; case 'downloadBioFormats' - optionsH.forceUpdate = forceUpdate; - optionsH.signalExtractionDir = options.externalProgramsDir; optionsH.gitNameDisp = {'bfmatlab'}; - optionsH.gitRepos = {'https://downloads.openmicroscopy.org/bio-formats/6.6.1/artifacts/bfmatlab.zip'}; + optionsH.gitRepos = {'https://downloads.openmicroscopy.org/bio-formats/6.10.0/artifacts/bfmatlab.zip'}; optionsH.outputDir = optionsH.gitNameDisp; % optionsH.gitName = cellfun(@(x) [x '-master'],optionsH.gitNameDisp,'UniformOutput',false); optionsH.gitName = {'bfmatlab'}; [success] = downloadGithubRepositories('options',optionsH); + + % Download SlideBook reader. + downloadUrl = 'https://sites.imagej.net/SlideBook/jars/bio-formats/SlideBook6Reader.jar-20190125132114'; + % Ensure directory exists + ciapkg.io.mkdir(fullfile(ciapkg.getDirExternalPrograms(),'bfmatlab_readers')); + % Download + websave(fullfile(ciapkg.getDirExternalPrograms(),'bfmatlab_readers','SlideBook6Reader.jar'),downloadUrl); + + slideBookPath = fullfile(ciapkg.getDirExternalPrograms(),'bfmatlab_readers','SlideBook6Reader.jar'); + if isfile(slideBookPath)==1 + javaaddpath(slideBookPath); + end + case 'downloadImageJ' % Download mij.jar and ij.ar with Box backup in case mij.jar site offline. downloadFiles = {'http://bigwww.epfl.ch/sage/soft/mij/mij.jar','http://rsb.info.nih.gov/ij/upgrade/ij.jar','http://tiny.ucsf.edu/3wFyol'}; @@ -214,4 +271,5 @@ function loadDependencies(varargin) % nothing end end + ciapkg.loadBatchFxns; end \ No newline at end of file diff --git a/+ciapkg/+io/loadMovieList.m b/+ciapkg/+io/loadMovieList.m index 942a349..972eca5 100644 --- a/+ciapkg/+io/loadMovieList.m +++ b/+ciapkg/+io/loadMovieList.m @@ -1,35 +1,81 @@ function [outputMovie, movieDims, nPixels, nFrames] = loadMovieList(movieList, varargin) + % [outputMovie, movieDims, nPixels, nFrames] = loadMovieList(movieList, varargin) + % % Load movies, automatically detects type (avi, tif, or hdf5) and concatenates if multiple movies in a list. - % NOTE: - % The function assumes input is 2D time series movies with [x y frames] as dimensions - % If movies are different sizes, use largest dimensions and align all movies to top-left corner. + % NOTE: + % The function assumes input is 2D time series movies with [x y frames] as dimensions + % If movies are different sizes, use largest dimensions and align all movies to top-left corner. + % % Biafra Ahanonu % started: 2013.11.01 - % inputs - % movieList = either a char string containing a path name or a cell array containing char strings, e.g. 'pathToFile' or {'path1','path2'} - % outputs - % outputMovie - [x y frame] matrix. - % movieDims - structure containing x,y,z information for the movie. - % nPixels - total number of pixels in the movie across all frames. - % nFrames - total number of frames in the movie depending on user requests in option.frameList. - % options - % options.supportedTypes = {'.h5','.nwb','.hdf5','.tif','.tiff','.avi'}; - % % movie type - % options.movieType = 'tiff'; - % % hierarchy name in hdf5 where movie is - % options.inputDatasetName = '/1'; - % % convert file movie to double? - % options.convertToDouble = 0; - % % 'single','double' - % options.loadSpecificImgClass = []; - % % list of specific frames to load - % options.frameList = []; - % % should the waitbar be shown? - % options.waitbarOn=1; - % % just return the movie dimensions - % options.getMovieDims = 0; - % % treat movies in list as continuous with regards to frame - % options.treatMoviesAsContinuous = 0; + % + % Inputs + % movieList = either a char string containing a path name or a cell array containing char strings, e.g. 'pathToFile' or {'path1','path2'} + % + % Outputs + % outputMovie - [x y frame] matrix. + % movieDims - structure containing x,y,z information for the movie. + % nPixels - total number of pixels in the movie across all frames. + % nFrames - total number of frames in the movie depending on user requests in option.frameList. + % + % Options (input as Name-Value with Name = options.(Name)) + % % Int vector: list of specific frames to load. + % options.frameList = []; + % % Str: path to MAT-file to load movie into, e.g. when conducting processing on movies that are larger than RAM. + % options.matfile = ''; + % % Str: Variable name for movie to store MAT-file in. DO NOT CHANGE for the moment. + % options.matfileVarname = 'outputMovie'; + % % Cell array of str: list of supported file types, in general DO NOT change. + % options.supportedTypes = {'.h5','.hdf5','.tif','.tiff','.avi',... + % '.nwb',... % Neurodata Without Borders format + % '.isxd',... % Inscopix format + % '.oir',... % Olympus formats + % '.czi','.lsm'... % Zeiss formats + % }; + % % Str: movie type. + % options.movieType = 'tiff'; + % % Str: hierarchy name in HDF5 file where movie data is located. + % options.inputDatasetName = '/1'; + % % Str: default NWB hierarchy names in HDF5 file where movie data is located, will look in the order indicates + % options.defaultNwbDatasetName = {'/acquisition/TwoPhotonSeries/data'}; + % % Str: fallback hierarchy name, e.g. '/images' + % options.inputDatasetNameBackup = []; + % % Binary: 1 = convert file movie to double, 0 = keep original format. + % options.convertToDouble = 0; + % % Str: 'single','double' + % options.loadSpecificImgClass = []; + % % Binary: 1 = read frame by frame to save memory, 0 = read continuous chunk. + % options.forcePerFrameRead = 0; + % % Binary: 1 = waitbar/progress bar is shown, 0 = no progress shown. + % options.waitbarOn = 1; + % % Binary: 1 = just return the movie dimensions, do not load the movie. + % options.getMovieDims = 0; + % % Binary: 1 = treat movies in list as continuous with regards to frames to extract. + % options.treatMoviesAsContinuous = 0; + % % Binary: 1 = whether to display info on command line. + % options.displayInfo = 1; + % % Binary: Whether to display diagnostic information + % options.displayDiagnosticInfo = 0; + % % Binary: 1 = display diagnostic information, 0 = do not display diagnostic information. + % options.displayWarnings = 1; + % % Matrix: Pre-specify the size, if need to get around memory re-allocation issues + % options.presetMovieSize = []; + % % Binary: 1 = avoid pre-allocating if single large matrix, saves memory. 0 = pre-allocate matrix. + % options.largeMovieLoad = 0; + % % Int: [numParts framesPerPart] number of equal parts to load the movie in + % options.loadMovieInEqualParts = []; + % % Binary: 1 = only check information for 1st file then populate the rest with identical information, useful for folders with thousands of TIF or other images + % options.onlyCheckFirstFileInfo = 0; + % % Binary: 1 = h5info, 0 = hdf5info. DO NOT rely on this, will be deprecated/eliminated soon. + % options.useH5info = 1; + % % Int: [] = do nothing, 1-3 indicates R,G,B channels to take from multicolor RGB AVI + % options.rgbChannel = []; + % % Int: Bio-Formats series number to load. + % options.bfSeriesNo = 1; + % % Int: Bio-Formats channel number to load. + % options.bfChannelNo = 1; + % % Cell array: Store file information, e.g. make TIFF reading faster. + % options.fileInfo = {}; % changelog % 2014.02.14 [14:14:39] now can load non-monotonic lists for avi and hdf5 files. @@ -58,7 +104,12 @@ % 2021.08.13 [02:31:48] - Added HDF5 capitalized file extension. % 2021.08.26 [16:15:37] - Ensure that loadMovieList has all output arguments set no matter return conditions. % 2022.02.24 [10:24:28] - AVI now read(...,'native') is faster. - + % 2022.03.07 [15:56:58] - Speed improvements related to imfinfo calls. + % 2022.03.13 [19:43:23] - Add option to load movie into a MAT-file. + % 2022.03.23 [22:24:46] - Improve Bio-Formats support, including using bfGetPlane to partially load data along with adding ndpi support. + % 2022.07.05 [21:21:35] - Add SlideBook Bio-Formats support. Remove local function getMovieFileType to force use of CIAtah getMovieFileType function. Updated getIndex call to avoid issues with certain Bio-Formats. Ensure getting correct Bio-Formats frames and loadMovieList output. + % 2022.07.28 [18:44:47] - Hide TIF warnings. + % TODO % OPEN % Bio-Formats @@ -76,6 +127,12 @@ import ciapkg.api.* % import CIAtah functions in ciapkg package API. % ======================== + % Int vector: list of specific frames to load. + options.frameList = []; + % Str: path to MAT-file to load movie into, e.g. when conducting processing on movies that are larger than RAM. + options.matfile = ''; + % Str: Variable name for movie to store MAT-file in. DO NOT CHANGE for the moment. + options.matfileVarname = 'outputMovie'; % Cell array of str: list of supported file types, in general DO NOT change. options.supportedTypes = {'.h5','.hdf5','.tif','.tiff','.avi',... '.nwb',... % Neurodata Without Borders format @@ -95,8 +152,6 @@ options.convertToDouble = 0; % Str: 'single','double' options.loadSpecificImgClass = []; - % Int vector: list of specific frames to load. - options.frameList = []; % Binary: 1 = read frame by frame to save memory, 0 = read continuous chunk. options.forcePerFrameRead = 0; % Binary: 1 = waitbar/progress bar is shown, 0 = no progress shown. @@ -113,7 +168,7 @@ options.displayWarnings = 1; % Matrix: Pre-specify the size, if need to get around memory re-allocation issues options.presetMovieSize = []; - % Binary: 1 = avoid pre-allocating if single large matrix, saves memory + % Binary: 1 = avoid pre-allocating if single large matrix, saves memory. 0 = pre-allocate matrix. options.largeMovieLoad = 0; % Int: [numParts framesPerPart] number of equal parts to load the movie in options.loadMovieInEqualParts = []; @@ -127,6 +182,10 @@ options.bfSeriesNo = 1; % Int: Bio-Formats channel number to load. options.bfChannelNo = 1; + % Int: Bio-Formats z dimension to load. + options.bfZdimNo = 1; + % Cell array: Store file information, e.g. make TIFF reading faster. + options.fileInfo = {}; % get options options = ciapkg.io.getOptions(options,varargin); % unpack options into current workspace @@ -146,11 +205,26 @@ if options.displayInfo==1 display(repmat('#',1,3)) end + + % ======================== + % Create MAT-file object and variable + if ~isempty(options.matfile) + save(options.matfile,'outputMovie','-v7.3'); + matObj = matfile(options.matfile,'Writable',true); + + % There is no need to pre-allocate the MAT-file, skip. + options.largeMovieLoad = 1; + end + % ======================== - % allow usr to input just a string if a single movie + % Allow usr to input just a string if a single movie if ischar(movieList) movieList = {movieList}; - end + end + % Setup file info. + if isempty(options.fileInfo) + options.fileInfo = cell([1 length(movieList)]); + end % ======================== % modify frameList if loading equal parts @@ -231,15 +305,19 @@ if options.displayWarnings==0 warning off end + warning off tiffHandle = Tiff(thisMoviePath, 'r+'); + warning on tmpFrame = tiffHandle.read(); xyDims = size(tmpFrame); + options.fileInfo{iMovie} = imfinfo(thisMoviePath,'tif'); % nTiles = numberOfTiles(tiffHandle); - nTiles = size(imfinfo(thisMoviePath,'tif'),1);; + nTiles = size(options.fileInfo{iMovie},1);; if options.displayWarnings==0 warning on end + % dims.class{iMovie} = class(tmpFrame); dims.x(iMovie) = xyDims(1); dims.y(iMovie) = xyDims(2); % dims.z(iMovie) = size(imfinfo(thisMoviePath),1); @@ -250,7 +328,8 @@ dims.three(iMovie) = nTiles; if dims.z(iMovie)==1 - fileInfo = imfinfo(thisMoviePath,'tif'); + % fileInfo = imfinfo(thisMoviePath,'tif'); + fileInfo = options.fileInfo{iMovie}; try numFramesStr = regexp(fileInfo.ImageDescription, 'images=(\d*)', 'tokens'); nFrames = str2double(numFramesStr{1}{1}); @@ -347,11 +426,15 @@ tmpFrame = inputMovieIsx.get_frame_data(0); case 'bioformats' bfreaderTmp = bfGetReader(thisMoviePath); + bfreaderTmp.setSeries(options.bfSeriesNo); + omeMeta = bfreaderTmp.getMetadataStore(); - stackSizeX = omeMeta.getPixelsSizeX(0).getValue(); % image width, pixels - stackSizeY = omeMeta.getPixelsSizeY(0).getValue(); % image height, pixels - stackSizeZ = omeMeta.getPixelsSizeZ(0).getValue(); - nFrames = stackSizeZ; + stackSizeX = omeMeta.getPixelsSizeX(options.bfSeriesNo).getValue(); % image width, pixels + stackSizeY = omeMeta.getPixelsSizeY(options.bfSeriesNo).getValue(); % image height, pixels + stackSizeZ = omeMeta.getPixelsSizeZ(options.bfSeriesNo).getValue(); + % Get time points (frames) for this series + nFrames = bfreaderTmp.getSizeT; + xyDims = [stackSizeY stackSizeX]; dims.x(iMovie) = xyDims(1); dims.y(iMovie) = xyDims(2); @@ -359,8 +442,10 @@ dims.one(iMovie) = xyDims(1); dims.two(iMovie) = xyDims(2); dims.three(iMovie) = nFrames; - tmpFrame = bfGetPlane(bfreaderTmp, 1); - nChannels = omeMeta.getChannelCount(0); + + iPlane = bfreaderTmp.getIndex(0, options.bfChannelNo-1, 0)+1; + tmpFrame = bfGetPlane(bfreaderTmp, iPlane); + nChannels = omeMeta.getChannelCount(options.bfSeriesNo); end if isempty(options.loadSpecificImgClass) imgClass = class(tmpFrame); @@ -374,7 +459,7 @@ if any(frameListTmp>dims.three(iMovie)) rmIDx = frameListTmp>dims.three(iMovie); if options.displayInfo==1 - disp(sprintf('Removing %d frames outside movie length.',sum(rmIDx))) + fprintf('Removing %d frames outside movie length.\n',sum(rmIDx)); end frameListTmp(rmIDx) = []; else @@ -408,7 +493,7 @@ if any(zDimLength>sum(dims.three)) rmIDx = frameListTmp>sum(dims.three); if options.displayInfo==1 - disp(sprintf('Removing %d frames outside movie length.',sum(rmIDx))) + fprintf('Removing %d frames outside movie length.\n',sum(rmIDx)); end frameListTmp(rmIDx) = []; zDimLength = length(frameListTmp); @@ -445,7 +530,7 @@ globalFrame{i} = frameList(g{i}) - zdimsCumsum(i); dims.z(i) = length(globalFrame{i}); end - [cellfun(@max,globalFrame,'UniformOutput',false); cellfun(@min,globalFrame,'UniformOutput',false)] + disp([cellfun(@max,globalFrame,'UniformOutput',false); cellfun(@min,globalFrame,'UniformOutput',false)]) % pause else globalFrame = []; @@ -516,7 +601,7 @@ if any(thisFrameList>dims.three(iMovie)) rmIDx2 = thisFrameList>dims.three(iMovie); if options.displayInfo==1 - disp(sprintf('Removing %d frames outside movie length.',sum(rmIDx2))) + fprintf('Removing %d frames outside movie length.\n',sum(rmIDx2)); end thisFrameList(rmIDx2) = []; end @@ -532,19 +617,21 @@ thisMoviePath = movieList{iMovie}; tiffHandle = Tiff(thisMoviePath, 'r'); tmpFramePerma = tiffHandle.read(); - fileInfoH = imfinfo(thisMoviePath,'tif'); + % fileInfoH = imfinfo(thisMoviePath,'tif'); + fileInfoH = options.fileInfo{iMovie}; displayInfoH = 1; NumberframeH = dims.z(iMovie); elseif options.onlyCheckFirstFileInfo==1&&iMovie>1 displayInfoH = 0; NumberframeH = dims.z(iMovie); + fileInfoH = options.fileInfo{iMovie}; else % For all other cases (e.g. TIF stacks) don't alter tmpFramePerma = []; displayInfoH = 1; Numberframe = []; NumberframeH = []; - fileInfoH = []; + fileInfoH = options.fileInfo{iMovie}; end if options.displayInfo==0 @@ -553,19 +640,39 @@ if numMovies==1 if isempty(thisFrameList) - outputMovie = load_tif_movie(thisMoviePath,1,'displayInfo',options.displayInfo); - outputMovie = outputMovie.Movie; + outputMovie = load_tif_movie(thisMoviePath,1,'displayInfo',options.displayInfo,'fileInfo',fileInfoH); + % outputMovie = outputMovie.Movie; else - outputMovie = load_tif_movie(thisMoviePath,1,'frameList',thisFrameList,'displayInfo',options.displayInfo); + outputMovie = load_tif_movie(thisMoviePath,1,'frameList',thisFrameList,'displayInfo',options.displayInfo,'fileInfo',fileInfoH); + end + + if isempty(options.matfile) outputMovie = outputMovie.Movie; + else + matObj.outputMovie = outputMovie.Movie; end else if isempty(thisFrameList) tmpMovie = load_tif_movie(thisMoviePath,1,'tmpImage',tmpFramePerma,'displayInfo',displayInfoH,'Numberframe',NumberframeH,'fileInfo',fileInfoH); - tmpMovie = tmpMovie.Movie; + % tmpMovie = tmpMovie.Movie; else tmpMovie = load_tif_movie(thisMoviePath,1,'frameList',thisFrameList,'tmpImage',tmpFramePerma,'displayInfo',displayInfoH,'Numberframe',NumberframeH,'fileInfo',fileInfoH); + end + + if isempty(options.matfile) tmpMovie = tmpMovie.Movie; + else + if iMovie==1 + if dims.z(iMovie)==0 + matObj.outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),1) = tmpMovie.Movie; + else + matObj.outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),1:dims.z(iMovie)) = tmpMovie.Movie; + end + else + % assume 3D movies with [x y frames] as dimensions + zOffset = sum(dims.z(1:iMovie-1)); + matObj.outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),(zOffset+1):(zOffset+dims.z(iMovie))) = tmpMovie.Movie; + end end end % ======================== @@ -583,7 +690,7 @@ hReadInfo = getHdf5Info(); % read in the file % hReadInfo.Attributes - if options.largeMovieLoad==1 + if options.largeMovieLoad==1&&numMovies==1 if options.useH5info==1 outputMovie = h5read(thisMoviePath,options.inputDatasetName); else @@ -779,13 +886,17 @@ case 'bioformats' % Setup movie class bfreaderTmp = bfGetReader(thisMoviePath); + bfreaderTmp.setSeries(options.bfSeriesNo); + omeMeta = bfreaderTmp.getMetadataStore(); - stackSizeX = omeMeta.getPixelsSizeX(0).getValue(); % image width, pixels - stackSizeY = omeMeta.getPixelsSizeY(0).getValue(); % image height, pixels - stackSizeZ = omeMeta.getPixelsSizeZ(0).getValue(); - nChannels = omeMeta.getChannelCount(0); + stackSizeX = omeMeta.getPixelsSizeX(options.bfSeriesNo).getValue(); % image width, pixels + stackSizeY = omeMeta.getPixelsSizeY(options.bfSeriesNo).getValue(); % image height, pixels + stackSizeZ = omeMeta.getPixelsSizeZ(options.bfSeriesNo).getValue(); + nChannels = omeMeta.getChannelCount(options.bfSeriesNo); + + % Get the number of time points (frames). + nFramesHere = bfreaderTmp.getSizeT; - nFramesHere = stackSizeZ; xyDims = [stackSizeX stackSizeY]; if isempty(thisFrameList) @@ -795,45 +906,85 @@ nFrames = length(thisFrameList); framesToGrab = thisFrameList; end - vidHeight = xyDims(1); - vidWidth = xyDims(2); + vidHeight = xyDims(2); + vidWidth = xyDims(1); % Preallocate movie structure. - tmpMovie = zeros(vidHeight, vidWidth, nFrames, imgClass); + if numMovies==1 + outputMovie = zeros(vidHeight, vidWidth, nFrames, imgClass); + else + tmpMovie = zeros(vidHeight, vidWidth, nFrames, imgClass); + end % Read one frame at a time. reverseStr = ''; iframe = 1; nFrames = length(framesToGrab); - % Read in movie data - tmpMovie = bfopen(thisMoviePath); - % bfopen returns an n-by-4 cell array, where n is the number of series in the dataset. If s is the series index between 1 and n: - % The data{s, 1} element is an m-by-2 cell array, where m is the number of planes in the s-th series. If t is the plane index between 1 and m: - % The data{s, 1}{t, 1} element contains the pixel data for the t-th plane in the s-th series. - % The data{s, 1}{t, 2} element contains the label for the t-th plane in the s-th series. - % The data{s, 2} element contains original metadata key/value pairs that apply to the s-th series. - % The data{s, 3} element contains color lookup tables for each plane in the s-th series. - % The data{s, 4} element contains a standardized OME metadata structure, which is the same regardless of the input file format, and contains common metadata values such as physical pixel sizes - see OME metadata below for examples. + zPlane = 0; + iZ = 1; + dispEvery = round(nFrames/20); + % Adjust for zero-based indexing + framesToGrab = framesToGrab-1; - % Frame information - frameInfo = tmpMovie{options.bfSeriesNo, 1}(:,2); + % figure; + reverseStr = ''; + for t = framesToGrab + iPlane = bfreaderTmp.getIndex(zPlane, options.bfChannelNo-1, t)+1; + tmpFrame = bfGetPlane(bfreaderTmp, iPlane); + % figure; + % imagesc(tmpFrame); + if numMovies==1 + outputMovie(:,:,iZ) = tmpFrame; + else + tmpMovie(:,:,iZ) = tmpFrame; + end + iZ = iZ+1; + + if options.displayInfo==1 + reverseStr = cmdWaitbar(iZ,nFrames,reverseStr,'inputStr','Loading bio-formats file: ','waitbarOn',options.waitbarOn,'displayEvery',dispEvery); + end + % pause(0.01) + end + if numMovies==1 + clear tmpMovie + end + % ciapkg.api.playMovie(tmpMovie); + % size(tmpMovie) - % Grab just the movie frames and convert from cell to matrix. - tmpMovie = tmpMovie{options.bfSeriesNo, 1}(:,1); - tmpMovie = cat(3,tmpMovie{:}); + % continue; + extraBioformatsFlag = 0; + if extraBioformatsFlag==1 + % Read in movie data + tmpMovie = bfopen(thisMoviePath); - % Only keep single channel if more than 1 channel in an image. - if nChannels>1 - try - chanKeepIdx = cell2mat(cellfun(@(x) str2num(cell2mat(regexp(x,'(?<=C\?=|C=)\d+(?=/)','match'))),frameInfo,'UniformOutput',false)); - chanKeepIdx = chanKeepIdx==options.bfChannelNo; - tmpMovie = tmpMovie(:,:,chanKeepIdx); - catch err - disp(repmat('@',1,7)) - disp(getReport(err,'extended','hyperlinks','on')); - disp(repmat('@',1,7)) + % bfopen returns an n-by-4 cell array, where n is the number of series in the dataset. If s is the series index between 1 and n: + % The data{s, 1} element is an m-by-2 cell array, where m is the number of planes in the s-th series. If t is the plane index between 1 and m: + % The data{s, 1}{t, 1} element contains the pixel data for the t-th plane in the s-th series. + % The data{s, 1}{t, 2} element contains the label for the t-th plane in the s-th series. + % The data{s, 2} element contains original metadata key/value pairs that apply to the s-th series. + % The data{s, 3} element contains color lookup tables for each plane in the s-th series. + % The data{s, 4} element contains a standardized OME metadata structure, which is the same regardless of the input file format, and contains common metadata values such as physical pixel sizes - see OME metadata below for examples. + + % Frame information + frameInfo = tmpMovie{options.bfSeriesNo, 1}(:,2); + + % Grab just the movie frames and convert from cell to matrix. + tmpMovie = tmpMovie{options.bfSeriesNo, 1}(:,1); + tmpMovie = cat(3,tmpMovie{:}); + + % Only keep single channel if more than 1 channel in an image. + if nChannels>1 + try + chanKeepIdx = cell2mat(cellfun(@(x) str2num(cell2mat(regexp(x,'(?<=C\?=|C=)\d+(?=/)','match'))),frameInfo,'UniformOutput',false)); + chanKeepIdx = chanKeepIdx==options.bfChannelNo; + tmpMovie = tmpMovie(:,:,chanKeepIdx); + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end end end % ======================== @@ -846,7 +997,7 @@ if dims.z(iMovie)==0 outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),1) = tmpMovie; else - outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),1:dims.z(iMovie)) = tmpMovie; + outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),1:dims.z(iMovie)) = tmpMovie; end % outputMovie(:,:,:) = tmpMovie; else @@ -981,37 +1132,40 @@ end end end - -function [movieType, supported] = getMovieFileType(thisMoviePath) - % determine how to load movie, don't assume every movie in list is of the same type - supported = 1; - try - [pathstr,name,ext] = fileparts(thisMoviePath); - catch - movieType = ''; - supported = 0; - return; - end - % files are assumed to be named correctly (lying does no one any good) - if any(strcmp(ext,{'.h5','.hdf5','.HDF5'})) - movieType = 'hdf5'; - elseif strcmp(ext,'.nwb') - movieType = 'hdf5'; - elseif any(strcmp(ext,{'.tif','.tiff'})) - movieType = 'tiff'; - elseif strcmp(ext,'.avi') - movieType = 'avi'; - elseif strcmp(ext,'.isxd') % Inscopix file format - movieType = 'isxd'; - elseif strcmp(ext,'.oir') % Olympus file format - movieType = 'bioformats'; - elseif any(strcmp(ext,{'.czi','.lsm'})) % Zeiss file format - movieType = 'bioformats'; - else - movieType = ''; - supported = 0; - end -end +% function [movieType, supported] = getMovieFileType(thisMoviePath) +% % determine how to load movie, don't assume every movie in list is of the same type +% supported = 1; +% try +% [pathstr,name,ext] = fileparts(thisMoviePath); +% catch +% movieType = ''; +% supported = 0; +% return; +% end +% % files are assumed to be named correctly (lying does no one any good) +% if any(strcmp(ext,{'.h5','.hdf5','.HDF5'})) +% movieType = 'hdf5'; +% elseif strcmp(ext,'.nwb') +% movieType = 'hdf5'; +% elseif any(strcmp(ext,{'.tif','.tiff'})) +% movieType = 'tiff'; +% elseif strcmp(ext,'.avi') +% movieType = 'avi'; +% elseif strcmp(ext,'.isxd') % Inscopix file format +% movieType = 'isxd'; +% elseif strcmp(ext,'.ndpi') % Hamamatsu file format +% movieType = 'bioformats'; +% elseif strcmp(ext,'.oir') % Olympus file format +% movieType = 'bioformats'; +% elseif any(strcmp(ext,{'.czi','.lsm'})) % Zeiss file format +% movieType = 'bioformats'; +% elseif endsWith(ext,'.sld') % SlideBook file format +% movieType = 'bioformats'; +% else +% movieType = ''; +% supported = 0; +% end +% end function subfxnDisplay(str,options) if options.displayInfo==1 disp(str) diff --git a/+ciapkg/+io/loadSignalExtraction.m b/+ciapkg/+io/loadSignalExtraction.m index 3ec1ad4..724ae79 100644 --- a/+ciapkg/+io/loadSignalExtraction.m +++ b/+ciapkg/+io/loadSignalExtraction.m @@ -1,25 +1,33 @@ -function [inputImages,inputSignals,infoStruct,algorithmStr,inputSignals2] = loadSignalExtraction(inputFilePath,varargin) +function [inputImages,inputSignals,infoStruct,algorithmStr,inputSignals2,valid] = loadSignalExtraction(inputFilePath,varargin) + % [inputImages,inputSignals,infoStruct,algorithmStr,inputSignals2,valid] = loadSignalExtraction(inputFilePath,varargin) + % % Loads CIAtah-style MAT or NWB files containing signal extraction results. % Biafra Ahanonu % started: 2021.02.03 [10:53:11] - % inputs - % inputFilePath - path to signal extraction output - % outputs - % inputImages - 3D or 4D matrix containing cells and their spatial information, format: [x y nCells]. - % inputSignals - 2D matrix containing activity traces in [nCells nFrames] format. - % infoStruct - contains information about the file, e.g. the 'description' property that can contain information about the algorithm. - % algorithmStr - String of the algorithm name. - % inputSignals2 - same as inputSignals but for secondary traces an algorithm outputs. + % + % Inputs + % inputFilePath - Str: path to signal extraction output. + % + % Outputs + % inputImages - 3D or 4D matrix containing cells and their spatial information, format: [x y nCells]. + % inputSignals - 2D matrix containing activity traces in [nCells nFrames] format. + % infoStruct - contains information about the file, e.g. the 'description' property that can contain information about the algorithm. + % algorithmStr - String of the algorithm name. + % inputSignals2 - same as inputSignals but for secondary traces an algorithm outputs. + % valid - logical vector indicating which signals are valid and should be kept. % changelog % 2021.03.10 [18:50:48] - Updated to add support for initial set of cell-extraction algorithms. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.05.31 [10:20:11] - Added option to load manual sorting as well and output as standard vector. % TODO % import ciapkg.api.* % import CIAtah functions in ciapkg package API. % ======================== + % Str: Full path to MAT-file containing sorted signals (manual or automatic [e.g. with CLEAN]). + options.sortedSignalPath = ''; % Struct: name of structures for CIAtah-style outputs. options.extractionMethodStructVarname = struct(... 'PCAICA', 'pcaicaAnalysisOutput',... @@ -61,6 +69,7 @@ inputSignals2 = []; algorithmStr = ''; infoStruct = struct; + valid = NaN; if iscell(inputFilePath) inputFilePath = inputFilePath{1}; diff --git a/+ciapkg/+io/load_tif_movie.m b/+ciapkg/+io/load_tif_movie.m index ec62335..b7b0577 100644 --- a/+ciapkg/+io/load_tif_movie.m +++ b/+ciapkg/+io/load_tif_movie.m @@ -15,6 +15,7 @@ % 2019.03.10 [20:17:09] Allow user to pre-input TIF temporary file to speed-up load times, esp. with individual files, see options.tmpImage and options.fileInfo % 2020.09.01 [14:27:12] - Suppress warning at user request and remove unecessary file handle call. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.03.07 [15:56:58] - Speed improvements related to imfinfo calls. Added tiffread support and ScanImageTiffReader support (not user accessible currently). % TODO % @@ -43,8 +44,11 @@ %First load a single frame of the movie to get generic information if options.displayInfo==0 warning off - end + end + warning off TifLink = Tiff(filename, 'r'); % Create the Tiff object + warning on; + warning off TmpImage = TifLink.read();%Read in one picture to get the image size and data type warning on @@ -59,7 +63,10 @@ % Pre-allocate the movie if isempty(options.Numberframe) - out.Numberframe=size(imfinfo(filename),1);% Number of frames + if isempty(options.fileInfo) + options.fileInfo = imfinfo(filename); + end + out.Numberframe = size(options.fileInfo,1);% Number of frames framesToGrab = 1:out.Numberframe; else out.Numberframe = options.Numberframe; @@ -157,29 +164,59 @@ function standardTIFF_new() warning off tiffID = Tiff(filename,'r'); + + % tsStack = TIFFStack(filename); % tiffID.setDirectory(1); % rgbTiff = size(read(tiffID),3); - for frameNo = 1:nFramesDim - % out.Movie(:,:,frameNo) = fread(fileID, [fileInfo.Width fileInfo.Height], fileType, 0, byteorder)'; - % out.Movie(:,:,frameNo) = fread(fileID, [imgWidth(1) imgHeight(1)], fileType, 0, byteorder)'; - % fseek(fileID, StripOffsets(frameNo), 'bof'); - - tiffID.setDirectory(framesToGrab2(frameNo)); - % rgbTiff = size(read(tiffID),3); - %if rgbTiff==1 - try - out.Movie(:,:,frameNo) = read(tiffID); - catch - tmpFrame = read(tiffID); - out.Movie(:,:,frameNo) = tmpFrame(:,:,1); - end - %elseif rgbTiff==3 - % tmpFrame = read(tiffID); - % out.Movie(:,:,frameNo) = tmpFrame(:,:,1); - %end - reverseStr = cmdWaitbar(frameNo,nFrames,reverseStr,'inputStr','loading ImageJ tif','waitbarOn',1,'displayEvery',50); + + % tic + %out.Movie = loadtiff(filename); + % toc + + %return + + % tic + if length(1:nFramesDim)==nFrames + out.Movie = tiffread(filename,[],'ReadUnknownTags',1); + else + out.Movie = tiffread(filename, framesToGrab2,'ReadUnknownTags',1); end + out.Movie = cat(3,out.Movie(:).data); + % toc + warning on + return; + + % Use scan image reader + if 0&&length(1:nFramesDim)==nFrames + tifReader = ScanImageTiffReader.ScanImageTiffReader(filename); + tifReader = tifReader.data(); + else + tic + for frameNo = 1:nFramesDim + % out.Movie(:,:,frameNo) = fread(fileID, [fileInfo.Width fileInfo.Height], fileType, 0, byteorder)'; + % out.Movie(:,:,frameNo) = fread(fileID, [imgWidth(1) imgHeight(1)], fileType, 0, byteorder)'; + % fseek(fileID, StripOffsets(frameNo), 'bof'); + + tiffID.setDirectory(framesToGrab2(frameNo)); + % rgbTiff = size(read(tiffID),3); + %if rgbTiff==1 + try + out.Movie(:,:,frameNo) = read(tiffID); + % out.Movie(:,:,frameNo) = tsStack(:,:,frameNo); + catch + tmpFrame = read(tiffID); + % tmpFrame = tsStack(frameNo); + out.Movie(:,:,frameNo) = tmpFrame(:,:,1); + end + %elseif rgbTiff==3 + % tmpFrame = read(tiffID); + % out.Movie(:,:,frameNo) = tmpFrame(:,:,1); + %end + reverseStr = cmdWaitbar(frameNo,nFrames,reverseStr,'inputStr','loading ImageJ tif','waitbarOn',1,'displayEvery',50); + end + toc + end warning on % playMovie(out.Movie) @@ -204,7 +241,7 @@ function nonstandardTIFF() fileInfo = fileInfo(1); end catch - display('assuming single frame'); + disp('assuming single frame'); nFrames = 1; end else diff --git a/+ciapkg/+io/manageParallelWorkers.m b/+ciapkg/+io/manageParallelWorkers.m index a874e57..f80052d 100644 --- a/+ciapkg/+io/manageParallelWorkers.m +++ b/+ciapkg/+io/manageParallelWorkers.m @@ -28,6 +28,7 @@ % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2022.02.09 [19:03:24] - Added nCoresFree option for users to set the number of cores to remain free. % 2022.02.28 [18:36:15] - Added ability to input just the number of workers to open as 1st single input argument that aliases for the "setNumCores" Name-Value input, still support other input arguments as well. + % 2022.06.27 [19:40:56] - Added displayInfo option. % TODO % @@ -59,6 +60,8 @@ options.forceParpoolStart = 0; % Int: default number of logical cores that will remain free (e.g. number of workers to load will be nLogicalCores - options.nCoresFree). options.nCoresFree = 1; + % Binary: 1 = whether to display info on command line. + options.displayInfo = 1; % get options options = getOptions(options,varargin); % options = getOptions(options,varargin,'getFunctionDefaults',1); @@ -88,7 +91,9 @@ % Check whether user has disabled auto-load, if so, they do not run manageParallelWorkers parSet = parallel.Settings; if parSet.Pool.AutoCreate==false - disp('User has set parSet.Pool.AutoCreate to false, DO NOT auto-start parallel pool.') + if options.displayInfo==1 + disp('User has set parSet.Pool.AutoCreate to false, DO NOT auto-start parallel pool. Use Name-Value input "forceParpoolStart=1" to force starting of parallel pool.') + end return; end end diff --git a/+ciapkg/+io/mergeStructs.m b/+ciapkg/+io/mergeStructs.m new file mode 100644 index 0000000..789c471 --- /dev/null +++ b/+ciapkg/+io/mergeStructs.m @@ -0,0 +1,163 @@ +function [toStruct] = mergeStructs(toStruct,fromStruct,varargin) + % [toStruct] = mergeStructs(fromStruct,toStruct,overwritePullFields) + % + % Copies fields in fromStruct into toStruct, if there is an overlap in field names, fromStruct overwrites toStruct unless specified otherwise. + % + % Biafra Ahanonu + % started: 2014.02.12 + % + % inputs + % toStruct - Structure that is to be updated with values from fromStruct. + % fromStruct - structure to use to overwrite toStruct. + % overwritePullFields - 1 = overwrite toStruct fields with fromStruct, 0 = don't overwrite. + % outputs + % toStruct - structure with fromStructs values added. + + % changelog + % 2022.07.06 [11:13:02] - Make updates from getOptions to include recursive structures and other options. Remove overwritePullFields input argument, make Name-Value input instead. Change naming of fromStruct and toStruct to fromStruct and toStruct, easier for users. + % TODO + % + + %======================== + % Options for getOptions. + % Avoid recursion here, hence don't use getOptions for getOptions's options. + % Binary: 1 = whether getOptions should use recursive structures or crawl through a structure's field names or just replace the entire structure. For example, if "1" then options that themselves are a structure or contain sub-structures, the fields will be replaced rather than the entire strucutre. + goptions.recursiveStructs = 1; + % Binary: 1 = show warning if user inputs Name-Value pair option input that is not in original structure. + goptions.showWarnings = 1; + % Int: number of parent stacks to show during warning. + goptions.nParentStacks = 1; + % Binary: 1 = get defaults for a function from getSettings. + goptions.getFunctionDefaults = 0; + % Binary: 1 = show full stack trace on incorrect option. + goptions.showStack = 1; + % OBSOLETE | Binary: 1 = overwrite toStruct input fields. + goptions.overwritePullFields = 1; + % Update getOptions's options based on user input. + + % get user options, else keeps the defaults + goptions = ciapkg.io.getOptions(goptions,varargin); + % unpack options into current workspace + % fn=fieldnames(options); + % for i=1:length(fn) + % eval([fn{i} '=options.' fn{i} ';']); + % end + %======================== + + pushNames = fieldnames(fromStruct); + pullNames = fieldnames(toStruct); + % loop over all fromStruct fields, add them to fromStruct + % for name = 1:length(pushNames) + % iPushName = pushNames{name}; + + % % % don't overwrite + % % if overwritePullFields==0&~isempty(strmatch(iPushName,pullNames)) + % % continue; + % % end + % % toStruct.(iPushName) = fromStruct.(iPushName); + + % end + [toStruct] = mirrorRightStruct(fromStruct,toStruct,goptions,'options'); +end +function [toStruct] = mirrorRightStruct(fromStruct,toStruct,goptions,toStructName) + % Overwrites fields in toStruct with those in fromStruct, other toStruct fields remain intact. + % More generally, copies fields in fromStruct into toStruct, if there is an overlap in field names, fromStruct overwrites. + % Fields present in toStruct but not fromStruct are kept in toStruct output. + fromNames = fieldnames(fromStruct); + for name = 1:length(fromNames) + fromField = fromNames{name}; + % if a field name is a struct, recursively grab user options from it + if isfield(toStruct, fromField)|isprop(toStruct, fromField) + if isstruct(fromStruct.(fromField))&goptions.recursiveStructs==1 + % safety check: field exist in toStruct and is also a structure + if isstruct(toStruct.(fromField)) + toStruct.(fromField) = mirrorRightStruct(fromStruct.(fromField),toStruct.(fromField),goptions,[toStructName '.' fromField]); + else + if goptions.showWarnings==1 + localShowWarnings(3,'notstruct',toStructName,fromField,'',goptions.nParentStacks,goptions.showStack); + end + end + else + toStruct.(fromField) = fromStruct.(fromField); + end + else + if goptions.showWarnings==1 + localShowWarnings(3,'struct',toStructName,fromField,'',goptions.nParentStacks,goptions.showStack); + end + end + end +end +function localShowErrorReport(err) + % Displays an error report. + display(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + display(repmat('@',1,7)) +end +function localShowWarnings(stackLevel,displayType,toStructName,fromField,val,nParentStacks,showStack) + % Sub-function to centralize displaying of warnings within the function + try + % Calling localShowWarnings adds to the stack, adjust accordingly. + stackLevel = stackLevel+1; + + % Get the entire function-call stack. + [ST,~] = dbstack; + if isempty(ST)|length(ST)=(stackLevelTwo) + callingFxnParent = ST(stackLevelTwo).name; + callingFxnParentPath = which(ST(stackLevelTwo).file); + callingFxnParentLine = num2str(ST(stackLevelTwo).line); + callingFxnParentStr = [callingFxnParentStr ' | ' callingFxnParent ' line ' callingFxnParentLine]; + else + callingFxnParentStr = ''; + end + stackLevelTwo = stackLevelTwo+1; + end + end + + % Display different information based on what type of warning occurred. + switch displayType + case 'struct' + warning(['WARNING: ' toStructName '.' fromField ' is not a valid option for ' callingFxn ' on line ' callingFxnLine callingFxnParentStr]) + case 'notstruct' + warning(['WARNING: ' toStructName '.' fromField ' is not originally a STRUCT, ignoring. ' callingFxn ' on line ' callingFxnLine callingFxnParentStr]) + case 'name-value incorrect' + warning(['WARNING: enter the parameter name before its associated value in ' callingFxn ' on line ' callingFxnLine callingFxnParentStr]) + case 'name-value' + warning(['WARNING: ' val ' is not a valid option for ' callingFxn ' on line ' callingFxnLine callingFxnParentStr]) + otherwise + % do nothing + end + catch err + localShowErrorReport(err); + subfxnShowWarningsError(stackLevel,displayType,toStructName,fromField,val,nParentStacks); + end +end +function subfxnShowWarningsError(stackLevel,displayType,toStructName,fromField,val,nParentStacks) + callingFxn = 'UNKNOWN FUNCTION'; + % Display different information based on what type of warning occurred. + switch displayType + case 'struct' + warning(['WARNING: ' toStructName '.' fromField ' is not a valid option for "' callingFxn '"']) + case 'notstruct' + warning('Unknown error.') + case 'name-value incorrect' + warning(['WARNING: enter the parameter name before its associated value in "' callingFxn '"']) + case 'name-value' + warning(['WARNING: ' val ' is not a valid option for "' callingFxn '"']) + otherwise + % do nothing + end +end \ No newline at end of file diff --git a/+ciapkg/+io/readFrame.m b/+ciapkg/+io/readFrame.m index 4f61d58..854e5c3 100644 --- a/+ciapkg/+io/readFrame.m +++ b/+ciapkg/+io/readFrame.m @@ -18,6 +18,7 @@ % 2021.07.03 [09:02:14] - Updated to have backup read method for different tiff styles. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2022.02.24 [10:24:28] - AVI now read(...,'native') is faster. + % 2022.07.27 [13:48:43] - Ensure subfxn_loadFrame nested function outputs frame data (even if file does not contain the requested frame, e.g. empty matrix). % TODO % @@ -88,6 +89,7 @@ end function [thisFrame, inputMovieDims] = subfxn_loadFrame(inputMoviePathHere,options) inputMovieDims = options.inputMovieDims; + thisFrame = []; switch movieType case 'hdf5' % Much faster to input the existing movie dimensions. @@ -97,7 +99,7 @@ else inputMovieDims = options.inputMovieDims; end - + % inputMovieDims thisFrame = h5read(inputMoviePathHere,options.inputDatasetName,[1 1 frameNo],[inputMovieDims(1) inputMovieDims(2) 1]); case 'tiff' warning off; diff --git a/+ciapkg/+io/saveMatrixToFile.m b/+ciapkg/+io/saveMatrixToFile.m index 50cdf71..55b87ca 100644 --- a/+ciapkg/+io/saveMatrixToFile.m +++ b/+ciapkg/+io/saveMatrixToFile.m @@ -1,16 +1,21 @@ function [success] = saveMatrixToFile(inputMatrix,savePath,varargin) - % Save 3D matrix to arbitrary file type (HDF5, TIF, AVI for now). + % [success] = saveMatrixToFile(inputMatrix,savePath,varargin) + % + % Save 3D matrix to user-specified file type. Currently supports HDF5, TIF, NWB, and AVI. + % % Biafra Ahanonu % started: 2016.01.12 [11:09:53] - % inputs - % inputMatrix - [x y frame] movie matrix - % savePath - character string of path to file with extension included, - % outputs - % + % + % Inputs + % inputMatrix - 3D matrix: [x y t] movie matrix, where t = frames normally. + % savePath - Str: character string of path to save file with extension included. + % Outputs + % success - Binary: 1 = saved successfully, 0 = error during save. % changelog % 2021.04.24 [16:00:01] - updated TIFF saving to add support for export of multi-channel color timeseries TIFF stack if in format [x y C t] where x,y = width/height, C = RGB channels, t = frames % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.03.06 [12:27:29] - Use Fast_Tiff_Write to write out TIF files instead of saveastiff by default. Give option with options.tifWriter. % TODO % Add checking of data size so tiff can be automatically switched @@ -37,6 +42,8 @@ options.deflateLevel = 1; % Str: description of imaging plane options.descriptionImagingPlane = 'NA'; + % Str: 'saveastiff' (normally slower) or 'Fast_Tiff_Write'. + options.tifWriter = 'Fast_Tiff_Write'; % get options options = getOptions(options,varargin); % display(options) @@ -83,14 +90,31 @@ end close(writerObj); case 'tiff' - tiffOptions.comp = 'no'; + % tiffOptions.comp = 'no'; + tiffOptions.compress = 'lzw'; tiffOptions.overwrite = true; if length(size(inputMatrix))==4 tiffOptions.color = true; disp('Saving TIFF as color timeseries stack.'); end fprintf('Saving to: %s\n',savePath); - saveastiff(inputMatrix, savePath, tiffOptions); + + switch options.tifWriter + case 'saveastiff' + saveastiff(inputMatrix, savePath, tiffOptions); + case 'Fast_Tiff_Write' + compression = 0; + tic; + fTIF = Fast_Tiff_Write(savePath,1,compression); + for i=1:size(inputMatrix,3) + fTIF.WriteIMG(squeeze(inputMatrix(:,:,i))'); + end + fTIF.close; + otherwise + saveastiff(inputMatrix, savePath, tiffOptions); + end + + case 'hdf5' fprintf('Saving to: %s\n',savePath); [output] = writeHDF5Data(inputMatrix,savePath,'datasetname',options.inputDatasetName,'addInfo',options.addInfo,'addInfoName',options.addInfoName,'writeMode',options.writeMode,'deflateLevel',options.deflateLevel); diff --git a/+ciapkg/+io/writeDataToMatfile.m b/+ciapkg/+io/writeDataToMatfile.m new file mode 100644 index 0000000..9ecddc9 --- /dev/null +++ b/+ciapkg/+io/writeDataToMatfile.m @@ -0,0 +1,121 @@ +function [success] = writeDataToMatfile(inputDataPath,varName,outputMatfilePath,varargin) + % [success] = writeDataToMatfile(inputDataPath,varName,outputMatfilePath,varargin) + % + % Converts 3D movie matrix stored in a file (HDF5, TIF, AVI, NWB) into a MAT-file for use with larger-than-memory processing. + % + % Biafra Ahanonu + % started: 2022.03.14 [00:44:08] + % + % inputs + % inputMatfilePath - Str: path to matfile object containing data. + % varName - Str: name of variable in MAT-file to save. Should be matrix of [x y frames]. + % inputFilename - Str: path where HDF5 file should be saved. + % + % outputs + % success - Binary: 1 = data saved correctly, 0 = data not saved correctly. + % + + % changelog + % + % TODO + % + + % ======================== + % Str: name of HDF5 dataset to save data into. + options.inputDatasetname = '/1'; + % Int: Number of frames to use for chunking the data to save in parts. + options.chunkSize = 200; + % Int: Defines gzip compression level (0-9). 0 = no compression, 9 = most compression. + options.deflateLevel = 9; + % Int: chunk size in [x y z] of the dataset, leave empty for auto chunking + options.dataDimsChunkCopy = []; + % Str: class to force data to be, e.g. 'single', 'double', 'uint16', etc. + options.saveSpecificImgClass = ''; + % get options + options = ciapkg.io.getOptions(options,varargin); + % disp(options) + % unpack options into current workspace + % fn=fieldnames(options); + % for i=1:length(fn) + % eval([fn{i} '=options.' fn{i} ';']); + % end + % ======================== + + try + success = 0; + + % Get size of the input data, call this way instead of size(inputObj.varName) to avoid loading entire dataset into memory. + % dataDim = size(inputObj,varName); + dataDimStruct = ciapkg.io.getMovieInfo(inputDataPath); + dataDim = [dataDimStruct.one dataDimStruct.two dataDimStruct.three]; + + if length(dataDim)~=3 + disp('Data is not a 3D matrix of size [:, :, >1], returning...') + return; + end + + % Use saving of individual fields of a structure to dynamically store user variable name. + saveStruct = struct; + % Save a temporary empty vector variable to MAT-file. Use -v7.3 to allow partial loading, saves memory. + if isempty(options.saveSpecificImgClass) + saveStruct.(varName) = zeros([dataDim(1) dataDim(2) 2],'single'); + else + saveStruct.(varName) = zeros([dataDim(1) dataDim(2) 2],options.saveSpecificImgClass); + end + disp(['Creating MAT-file: ' outputMatfilePath]) + save(outputMatfilePath,'-struct','saveStruct','-v7.3'); + disp('Done creating MAT-file') + + % Create matfile object to saved MAT-file. + disp('Creating matfile object.') + inputObj = matfile(outputMatfilePath,'Writable',true); + + % Get coordinates to use. + nFrames = dataDim(3); + subsetSize = options.chunkSize; + movieLength = nFrames; + numSubsets = ceil(movieLength/subsetSize)+1; + subsetList = round(linspace(1,movieLength,numSubsets)); + nSubsets = (length(subsetList)-1); + + disp('Writing file data to matfile in chunks:') + % Write data out in chunks. + for thisSet = 1:nSubsets + % subsetStartTime = tic; + + subsetStartIdx = subsetList(thisSet); + subsetEndIdx = subsetList(thisSet+1); + disp(repmat('$',1,7)) + if thisSet==nSubsets + movieSubset = subsetStartIdx:subsetEndIdx; + else + movieSubset = subsetStartIdx:(subsetEndIdx-1); + end + disp([num2str(movieSubset(1)) '-' num2str(movieSubset(end)) ' ' num2str(thisSet) '/' num2str(nSubsets)]) + + % Get slice of the data + inputDataSlice = ciapkg.io.loadMovieList(inputDataPath,'frameList',movieSubset,'inputDatasetName',options.inputDatasetname,'largeMovieLoad',1); + + if ~isempty(options.saveSpecificImgClass) + inputDataSlice = cast(inputDataSlice,options.saveSpecificImgClass); + end + + % Slice into the desired variable using dynamic field references to avoid loading entire dataset into memory. + inputObj.(varName)(:,:,movieSubset) = inputDataSlice; + + % toc(subsetStartTime) + end + + success = 1; + + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end + + function [outputs] = nestedfxn_exampleFxn(arg) + % Always start nested functions with "nestedfxn_" prefix. + % outputs = ; + end +end \ No newline at end of file diff --git a/+ciapkg/+motion_correction/computeManualMotionCorrection.m b/+ciapkg/+motion_correction/computeManualMotionCorrection.m index ae1a209..022b599 100644 --- a/+ciapkg/+motion_correction/computeManualMotionCorrection.m +++ b/+ciapkg/+motion_correction/computeManualMotionCorrection.m @@ -28,6 +28,10 @@ % 2021.11.17 [02:30:23] - Improve speed of GUI and responsiveness. % 2021.12.19 [11:00:15] - Force altInputImages to initialize as input class type to avoid automatic conversion to double (save space/ram). % 2022.01.29 [19:13:55] - Significant speed improvements by removing certain calls to figure, axis, imagesc, etc. that slow down UI over time when many sessions aligned. + % 2022.03.09 [16:34:47] - Check if inputs are sparse, convert to single. + % 2022.03.16 [08:54:11] - By default use 'painters' renderer as that can produce smoother rendering than opengl. + % 2022.04.25 [11:16:24] - Update to make sure titles update correctly for test images. + % 2022.09.14 [08:55:37] - Add renderer option to allow users to choose painters or opengl. % TODO % Add ability to auto-crop if inputs are not of the right size them convert back to correct size after manual correction % inputRegisterImage - [x y nCells] - Image to register to. @@ -61,6 +65,8 @@ options.translationAmt = 1; % Binary: 1 = include images output struct, 0 = do not include. options.includeImgsOutputStruct = 1; + % Str: 'painters' or 'opengl' + options.renderer = 'opengl'; % get options options = getOptions(options,varargin); % display(options) @@ -99,6 +105,14 @@ switchInputImagesBack = 0; end + % ======================== + % Check for sparse inputs + if issparse(inputImages) + disp('Converting from sparse to single.') + inputImages = single(full(inputImages)); + end + % ======================== + % Get register frame inputRegisterImage = inputImages(:,:,options.refFrame); @@ -266,7 +280,11 @@ end inputRegisterImageOutlinesOriginal = inputRegisterImageOutlines; - [figHandle, figNo] = openFigure(options.translationFigNo, ''); + [figHandle, ~] = openFigure(options.translationFigNo, ''); + + % Set the default renderer + set(figHandle,'Renderer',options.renderer); + colormap(ciapkg.api.customColormap()); % Force current character to be a new figure. set(gcf,'currentch','0'); @@ -312,7 +330,7 @@ disp('Setup #3'); disp(toc);tic - imgTitleFxn = @(imgNo,imgN,gammaCorrection,gammaCorrectionRef,translationVector,rotationVector,translationAmt) sprintf('Image %d/%d\nup/down/left/right arrows for translation | A/S = rotate +1 left/right, Q/W = rotate +90 left/right | 5/6 = flip x/y | f to finish\n1/2 keys for image gamma down/up | gamma = %0.3f | gamma(ref) = %0.3f | translation %d %d | rotation %d\npurple = reference image, green = image to manually translate | %d px/click',imgNo,imgN,gammaCorrection,gammaCorrectionRef,translationVector,rotationVector,translationAmt); + imgTitleFxn = @(imgNo,imgN,gammaCorrection,gammaCorrectionRef,translationVector,rotationVector,translationAmt) sprintf('Image %d/%d\nup/down/left/right arrows for translation\nA/S = rotate +1 left/right, Q/W = rotate +90 left/right | 5/6 = flip x/y | f to finish\n1/2 keys for image gamma down/up | gamma = %0.3f | gamma(ref) = %0.3f | translation %d %d | rotation %d\npurple = reference image, green = image to manually translate | %d px/click',imgNo,imgN,gammaCorrection,gammaCorrectionRef,translationVector,rotationVector,translationAmt); imgTitle = imgTitleFxn(imgNo,imgN,gammaCorrection,gammaCorrectionRef,translationVector,rotationVector,options.translationAmt); @@ -347,7 +365,7 @@ axis image; % axis equal tight box off; - title('Reference') + title(['Reference (#' num2str(options.refFrame) ')']) hold on; horzLineMid(rgbImage,'k-'); vertLineMid(rgbImage,'k-'); @@ -364,14 +382,17 @@ axis image; % axis equal tight box off; - title('Test') + % title('Test') + imgHandle3_title = title(['Test (#' num2str(imgNo) ')']); hold on; horzLineMid(rgbImage,'k-'); vertLineMid(rgbImage,'k-'); imgStruct.imgHandle3 = imgHandle3; + imgStruct.imgHandle3_title = imgHandle3_title; else imgHandle3 = imgStruct.imgHandle3; + set(imgStruct.imgHandle3_title,'String',['Test (#' num2str(imgNo) ')']); end userClickTimer = tic; diff --git a/+ciapkg/+motion_correction/getNoRMCorreParams.m b/+ciapkg/+motion_correction/getNoRMCorreParams.m new file mode 100644 index 0000000..d3792f2 --- /dev/null +++ b/+ciapkg/+motion_correction/getNoRMCorreParams.m @@ -0,0 +1,255 @@ +function [optsNC] = getNoRMCorreParams(movieDims,varargin) + % [optsNC] = getNoRMCorreParams(varargin) + % + % Automatically returns default NoRMCorre parameters or ask user to input parameters. + % + % Biafra Ahanonu + % started: 2022.07.18 [10:30:01] + % + % Inputs + % No inputs by default, all Name-Value pairs. + % + % Outputs + % optsNC - structure consisting of NormCorre options. + % + % Options (input as Name-Value with Name = options.(Name)) + % % DESCRIPTION + % options.exampleOption = ''; + + % Changelog + % + % TODO + % + + % ======================== + % Binary: 1 = use GUI display for user to customize parameters. + options.guiDisplay = 0; + % Struct: Options from prior run of getNoRMCorreParams(). + options.priorOpts = struct; + % get options + options = ciapkg.io.getOptions(options,varargin); + % disp(options) + % unpack options into current workspace + % fn=fieldnames(options); + % for i=1:length(fn) + % eval([fn{i} '=options.' fn{i} ';']); + % end + % ======================== + + optsNC = struct; + + try + % [d1,d2,T] = size(inputMovieHere); + d1 = movieDims(1); + d2 = movieDims(2); + T = movieDims(3); + bound = 0; + + % Update to CIAtah default settings + if isempty(fieldnames(options.priorOpts)) + optsNC2.d1 = d1-bound; + optsNC2.d2 = d2-bound; + optsNC2.init_batch = 10; + optsNC2.bin_width = 50; + optsNC2.grid_size = [128,128]; + optsNC2.mot_uf = 4; + optsNC2.correct_bidir = false; + optsNC2.overlap_pre = 32; + optsNC2.overlap_post = 32; + optsNC2.max_dev = 50; + optsNC2.use_parallel = true; + optsNC2.print_msg = true; + optsNC2.us_fac = 4; + optsNC2.max_shift = 100; + optsNC2.boundary = 'NaN'; + else + optsNC2 = options.priorOpts; + end + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end + + try + optsNC = normcorre.NoRMCorreSetParms(... + 'd1', d1-bound,... + 'd2', d2-bound,... + 'init_batch', optsNC2.init_batch,... + 'bin_width', optsNC2.bin_width,... + 'grid_size', optsNC2.grid_size,... + 'mot_uf', optsNC2.mot_uf,... + 'correct_bidir', optsNC2.correct_bidir,... + 'overlap_pre', optsNC2.overlap_pre,... + 'overlap_post', optsNC2.overlap_post,... + 'max_dev', optsNC2.max_dev,... + 'use_parallel', optsNC2.use_parallel,... + 'print_msg', optsNC2.print_msg,... + 'us_fac', optsNC2.us_fac,... + 'max_shift', optsNC2.max_shift,... + 'boundary', optsNC2.boundary... + ); + % 'init_batch_interval',options.refFrame,... + + % [optsNC] = ciapkg.io.mergeStructs(optsNC,optsNC2); + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end + if options.guiDisplay==0 + return; + end + + try + % optsNC = normcorre.NoRMCorreSetParms(... + % 'd1', d1-bound,... + % 'd2', d2-bound,... + % 'd3', 1,... + % 'init_batch', optsNC2.init_batch,... + % 'bin_width', optsNC2.bin_width,... + % 'grid_size', optsNC2.grid_size,... + % 'mot_uf', optsNC2.mot_uf,... + % 'correct_bidir', optsNC2.correct_bidir,... + % 'overlap_pre', optsNC2.overlap_pre,... + % 'overlap_post', optsNC2.overlap_post,... + % 'max_dev', optsNC2.max_dev,... + % 'use_parallel', optsNC2.use_parallel,... + % 'print_msg', optsNC2.print_msg,... + % 'us_fac', optsNC2.us_fac,... + % 'max_shift', optsNC2.max_shift,... + % 'boundary', optsNC2.boundary... + % ); + + modFn = fieldnames(optsNC2); + + % [optsNC] = ciapkg.io.mergeStructs(optsNC,optsNC2); + + optsFn = { + % dataset info + {'d1', "number of rows"}; + {'d2', "number of cols"}; + % {'d3', "number of planes (for 3d imaging, default: 1)"}; + % patches + {'grid_size', "size of non-overlapping regions (default: [d1,d2,d3])"}; + {'overlap_pre', "size of overlapping region (default: [32,32,16])"}; + {'min_patch_size', "minimum size of patch (default: [32,32,16]) "}; + {'min_diff', "minimum difference between patches (default: [16,16,5])"}; + {'us_fac', "upsampling factor for subpixel registration (default: 20)"}; + {'mot_uf', "degree of patches upsampling (default: [4,4,1])"}; + {'max_dev', "maximum deviation of patch shift from rigid shift (default: [3,3,1])"}; + {'overlap_post', "size of overlapping region after upsampling (default: [32,32,16])"}; + {'max_shift', "maximum rigid shift in each direction (default: [15,15,5])"}; + {'phase_flag', "flag for using phase correlation (default: false)"}; + {'shifts_method', "method to apply shifts ('FFT','cubic','linear')"}; + + % template updating + {'upd_template', "flag for online template updating (default: true)"}; + {'init_batch', "length of initial batch (default: 100)"}; + {'bin_width', "width of each bin (default: 200)"}; + {'buffer_width', "number of local means to keep in memory (default: 50)"}; + {'method', "method for averaging the template (default: {'median';'mean})"}; + {'iter', "number of data passes (default: 1)"}; + {'boundary', "method of boundary treatment 'NaN','copy','zero','template' (default:} 'copy')"}; + + % misc + {'add_value', "add dc value to data (default: 0)"}; + {'use_parallel', "for each frame, update patches in parallel (default: false)"}; + {'memmap', "flag for saving memory mapped motion corrected file (default: false)"}; + {'mem_filename', "name for memory mapped file (default: 'motion_corrected.mat')"}; + {'mem_batch_size', "batch size during memory mapping for speed (default: 5000)"}; + {'print_msg', "flag for printing progress to command line (default: true)"}; + + % plotting + {'plot_flag', "flag for plotting results in real time (default: false)"}; + {'make_avi', "flag for making movie (default: false)"}; + {'name', "name for movie (default: 'motion_corrected.avi')"}; + {'fr', "frame rate for movie (default: 30)"}; + + % output type + {'output_type', "'mat' (load in memory), 'memmap', 'tiff', 'hdf5', 'bin' (default:mat)"}; + {'h5_groupname', "name for hdf5 dataset (default: 'mov')"}; + {'h5_filename', "name for hdf5 saved file (default: 'motion_corrected.h5')"}; + {'tiff_filename', "name for saved tiff stack (default: 'motion_corrected.tif')"}; + {'output_filename', "name for saved file will be used if `h5_,tiff_filename` are not} specified"}; + + % use windowing + {'use_windowing', "flag for windowing data before fft (default: false)"}; + {'window_length', "length of window on each side of the signal as a fraction of signal length. Total length = length(signal)(1 + 2*window_length). (default: 0.5)"}; + {'bitsize', "bitsize for reading .raw files (default: 2 (uint16). other choices 1 (uint8), 4 (single), 8 (double))"}; + + % offset from bidirectional sampling + {'correct_bidir', "check for offset due to bidirectional scanning (default: true)"}; + {'nFrames', "number of frames to average (default: 50)"}; + {'bidir_us', "upsampling factor for bidirectional sampling (default: 10)"}; + {'col_shift', "known bi-directional offset provided by the user (default: [])"}; + }; + + for iz = 1:length(optsFn) + optsFn{iz}{2} = char(optsFn{iz}{2}); + end + + optsDefault = optsFn; + + movieSettingsStrs = {}; + defaultVals = {}; + for iz = 1:length(optsFn) + movieSettingsStrs{end+1} = optsFn{iz}{2}; + end + + % optsTmp = cellfun(@num2str,struct2cell(optsDefault),'UniformOutput',false); + for iz = 1:length(movieSettingsStrs) + valStr = optsFn{iz}{1}; + dVal = optsNC.(valStr); + if isnumeric(dVal)|islogical(dVal) + dVal = num2str(dVal); + elseif iscell(dVal) + dVal = cell2str(dVal); + else + + end + defaultVals{end+1} = dVal; + movieSettingsStrs{iz} = strrep([valStr ' | ' movieSettingsStrs{iz} ' | default: ' dVal],'_','\_'); + if any(strcmp(modFn,valStr))==1 + movieSettingsStrs{iz} = ['\bf\color{red}' movieSettingsStrs{iz}]; + end + end + % defaultVals + % cellfun(@class,defaultVals,'UniformOutput',false) + + dlgStr = 'NoRMCorre (red options are those to focus on'; + AddOpts.Resize='on'; + AddOpts.WindowStyle='normal'; + % AddOpts.WindowStyle='non-modal'; + AddOpts.Interpreter='tex'; + AddOpts.DlgSize = [1200 100]; + mSet = inputdlgcol(movieSettingsStrs,... + dlgStr,1,... + defaultVals,... + AddOpts,3); + + sNo = 1; + + for iz = 1:length(movieSettingsStrs) + valStr = optsFn{iz}{1}; + dVal = optsNC.(valStr); + if isnumeric(dVal)|islogical(dVal) + optsNC.(valStr) = str2num(mSet{iz}); + elseif iscell(dVal) + optsNC.(valStr) = eval(mSet{iz}); + else + optsNC.(valStr) = mSet{iz}; + end + end + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + end + + function [outputs] = nestedfxn_exampleFxn(arg) + % Always start nested functions with "nestedfxn_" prefix. + % outputs = ; + end +end + diff --git a/+ciapkg/+motion_correction/turboregMovie.m b/+ciapkg/+motion_correction/turboregMovie.m index f297e75..1f5a86b 100644 --- a/+ciapkg/+motion_correction/turboregMovie.m +++ b/+ciapkg/+motion_correction/turboregMovie.m @@ -1,11 +1,24 @@ function [inputMovie, ResultsOutOriginal] = turboregMovie(inputMovie, varargin) - % Motion corrects (using turboreg) a movie. - % - Both turboreg (to get 2D translation coordinates) and registering images (transfturboreg, imwarp, imtransform) have been parallelized. - % - Can also turboreg to one set of images and apply the registration to another set (e.g. for cross-day alignment). - % - Spatial filtering is applied after obtaining registration coordinates but before transformation, this reduced chance that 0s or NaNs at edge after transformation mess with proper spatial filtering. + % [inputMovie, ResultsOutOriginal] = turboregMovie(inputMovie, varargin) + % + % Motion corrects (using turboreg or NoRMCorre) a movie. + % - Both turboreg (to get 2D translation coordinates) and registering images (transfturboreg, imwarp, imtransform) have been parallelized. + % - Can also turboreg to one set of images and apply the registration to another set (e.g. for cross-day alignment). + % - Spatial filtering is applied after obtaining registration coordinates but before transformation, this reduced chance that 0s or NaNs at edge after transformation mess with proper spatial filtering. + % % Biafra Ahanonu % started 2013.11.09 [11:04:18] - % modified from code created by Jerome Lecoq in 2011 and parallel code update by biafra ahanonu + % + % Parts of code based on that from Jerome Lecoq (2011) and parallel code update by Biafra Ahanonu (2013). + % + % Input + % inputMovie - 3D matrix: [x y frames] matrix containing data to be motion corrected across frames. + % + % Output + % inputMovie - 3D matrix: [x y frames] matrix containing data after motion correction across frames. + % ResultsOutOriginal + % TurboReg - Cell array {1 frames}: contains the motion correction data for each frame for later use. + % NoRMCorre - Structure: contains the motion correction data for each frame for later use. % changelog % 2013.03.29 - parallelizing turboreg v1 @@ -27,6 +40,12 @@ % 2021.09.11 [10:40:05] - Additional matlab disk normalizeType options. % 2021.11.01 [12:15:10] - Additional display of information. % 2021.11.16 [11:58:14] - Added verification that turboreg MEX function is in the path. + % 2022.01.22 [20:46:51] - Refactor code to remove need to transform movie into cell array, performance and memory improvements. + % 2022.03.08 [12:23:53] - Added NoRMCorre support, function will eventually be merged with "registerMovie" or renamed to indicate support for multiple algorithms. Reason to integrate NoRMCorre into this function is takes advantage of existing pre-processing pipeline and integration with other algorithms (e.g. cross-session). + % 2022.03.09 [16:34:47] - Check if inputs are sparse, convert to single. + % 2022.03.09 [17:32:42] - Use custom bahanonu NoRMCorre that is within a package and update to use reference picture instead of template. + % 2022.04.23 [18:40:22] - Updated to which('normcorre.normcorre') from which('normcorre') since normcorre now inside a package. + % 2022.09.12 [20:46:04] - Additional NoRMCorre support and getNoRMCorreParams checking before running NoRMCorre. % TO-DO % Add support for "imregtform" based registration. @@ -53,6 +72,11 @@ % ======================== % get options % =======IMPORTANT OPTIONS===== + % Str: motion correction algorithm. + % 'turboreg' - TurboReg as developed by Philippe Thevenaz. + % 'normcorre' - NoRMCorre as developed by several authors at Flatiron Institute. + options.mcMethod = 'turboreg'; + % options.mcMethod = 'normcorre'; % Str: dataset name in HDF5 file where data is stored, if inputMovie is a path to a movie. options.inputDatasetName = '/1'; % Int vector: if loading movie inside function, provide frameList to load specific frames @@ -171,7 +195,11 @@ options.returnNormalizedMovie = 0; % Binary: 1 = run correlation before spatial filtering, 0 = do not run correlation. options.computeCorr = 0; - + % ======= + % NoRMCorre + % Struct: NoRMCorre settings + options.optsNoRMCorre = ciapkg.motion_correction.getNoRMCorreParams([1 1 1],'guiDisplay',0); + % % get options options = getOptions(options,varargin); % display(options) @@ -181,17 +209,31 @@ % eval([fn{i} '=options.' fn{i} ';']); % end if options.displayOptions==1 - options + fn_structdisp(options) end % ======================== % Verify that turboreg MEX function is in the path. - if isempty(which('turboreg'))==1 - ciapkg.loadBatchFxns(); + if isempty(which('turboreg'))==1||isempty(which('normcorre.normcorre'))==1 + % ciapkg.loadBatchFxns(); + ciapkg.loadBatchFxns('removeDirFxnToFindExclude','normcorre.m'); + end + + % ======================== + % Algorithm specific options + switch options.mcMethod + case 'turboreg' + + case 'normcorre' + % NoRMCorre is to be run on the entire input matrix. + options.cropCoords = []; + otherwise + end + % ======================== % check that Miji is present - if strcmp(options.normalizeType,'imagejFFT')|strcmp(options.normalizeBeforeRegister,'imagejFFT') + if strcmp(options.normalizeType,'imagejFFT')||strcmp(options.normalizeBeforeRegister,'imagejFFT') % if exist('Miji.m','file')==2 % disp(['Miji located in: ' which('Miji.m')]); % % Miji is loaded, continue @@ -209,6 +251,12 @@ modelAddOutsideDependencies('miji'); end % ======================== + % Check for sparse inputs + if issparse(inputMovie) + disp('Converting from sparse to single.') + inputMovie = single(full(inputMovie)); + end + % ======================== inputMovieClass = class(inputMovie); if ischar(inputMovie) inputMovie = loadMovieList(inputMovie,'inputDatasetName',options.inputDatasetName,'frameList',options.frameList); @@ -248,14 +296,17 @@ ResultsOut = ResultsOutTemp; % Remove NaNs from inputMovie so transfturboreg doesn't run into issue. inputMovie(isnan(inputMovie)) = 0; - convertInputMovieToCell(); + % convertInputMovieToCell(); % size(inputMovie) % class(inputMovie) % size(inputMovie{1}) % class(inputMovie{1}) InterpListSelection = turboRegOptions.Interp; registerMovie(); - inputMovie = cat(3,inputMovie{:}); + + % Convert back into matrix. + % inputMovie = cat(3,inputMovie{:}); + ResultsOutOriginal = ResultsOut; return; end @@ -271,14 +322,17 @@ % ResultsOut = ResultsOutTemp; % Remove NaNs from inputMovie so transfturboreg doesn't run into issue. inputMovie(isnan(inputMovie)) = 0; - convertInputMovieToCell(); + % convertInputMovieToCell(); % size(inputMovie) % class(inputMovie) % size(inputMovie{1}) % class(inputMovie{1}) InterpListSelection = turboRegOptions.Interp; registerMovie(); - inputMovie = cat(3,inputMovie{:}); + + % Convert back into matrix. + % inputMovie = cat(3,inputMovie{:}); + ResultsOutOriginal = ResultsOut; return; end @@ -327,8 +381,8 @@ end if ~isempty(options.saveTurboregCoords) - options.saveTurboregCoords - ResultsOut + disp(options.saveTurboregCoords) + disp(ResultsOut) end ResultsOutOriginal = ResultsOut; @@ -361,7 +415,8 @@ % === % if we are using the turboreg coordinates for frame #options.altMovieRegisterNum to register all frames from options.altMovieRegister, want to give registerMovie an identical sized array to altMovieRegister like it normally expects % this was made for having refCellmap and testCellmap, aligning the testCellmap to the refCellmap then registering all the cell images for testCellmap to refCellmap - for resultNo=1:size(options.altMovieRegister,3); + ResultsOutTemp = cell([1 size(options.altMovieRegister,3)]); + for resultNo=1:size(options.altMovieRegister,3) ResultsOutTemp{resultNo} = ResultsOut{options.altMovieRegisterNum}; end ResultsOut = ResultsOutTemp; @@ -369,17 +424,17 @@ %Convert array to cell array, allows slicing (not contiguous memory block) % add input movie to inputMovie = options.altMovieRegister; - convertInputMovieToCell(); + % convertInputMovieToCell(); elseif ~isempty(options.cropCoords) disp('restoring uncropped movie and converting to cell...'); clear registeredMovie; %Convert array to cell array, allows slicing (not contiguous memory block) - convertInputMovieToCell(); + % convertInputMovieToCell(); % === else - disp('converting movie to cell...'); + % disp('converting movie to cell...'); %Convert array to cell array, allows slicing (not contiguous memory block) - convertInputMovieToCell(); + % convertInputMovieToCell(); % === end % ======================== @@ -399,11 +454,11 @@ toc(startTime) % ======================== - disp('converting cell array back to matrix') + % disp('converting cell array back to matrix') %Convert cell array back to 3D matrix - inputMovie = cat(3,inputMovie{:}); - inputMovie = single(inputMovie); + % inputMovie = cat(3,inputMovie{:}); + inputMovie = single(inputMovie); subfxn_dispMovieFrames(inputMovie,'Registration==1',2); @@ -439,7 +494,7 @@ function convertInputMovieToCell() %Get dimension information about 3D movie matrix [inputMovieX, inputMovieY, inputMovieZ] = size(inputMovie); - reshapeValue = size(inputMovie); + % reshapeValue = size(inputMovie); %Convert array to cell array, allows slicing (not contiguous memory block) inputMovie = squeeze(mat2cell(inputMovie,inputMovieX,inputMovieY,ones(1,inputMovieZ))); end @@ -447,7 +502,7 @@ function convertInputMovieToCell() function convertinputMovieCroppedToCell() %Get dimension information about 3D movie matrix [inputMovieX, inputMovieY, inputMovieZ] = size(inputMovieCropped); - reshapeValue = size(inputMovieCropped); + % reshapeValue = size(inputMovieCropped); %Convert array to cell array, allows slicing (not contiguous memory block) inputMovieCropped = squeeze(mat2cell(inputMovieCropped,inputMovieX,inputMovieY,ones(1,inputMovieZ))); end @@ -469,197 +524,270 @@ function nUpdateParforProgress(~) % function [ResultsOut averagePictureEdge] = turboregMovieParallel(inputMovie,turboRegOptions,options) function turboregMovieParallel() - % get reference picture and other pre-allocation - postProcessPic = single(squeeze(inputMovieCropped(:,:,options.refFrame))); - mask = single(ones(size(postProcessPic))); - imgRegMask = single(double(mask)); - % we add an offset to be able to give NaN to black borders - averagePictureEdge = zeros(size(imgRegMask)); - refPic = single(squeeze(inputMovieCropped(:,:,options.refFrame))); - % refPic = squeeze(inputMovieCropped(:,:,options.refFrame)); - - MatrixMotCorrDispl=zeros(3,options.maxFrame); - - % === - %Convert array to cell array, allows slicing (not contiguous memory block) - convertinputMovieCroppedToCell(); - % === + switch options.mcMethod + case 'turboreg' + % get reference picture and other pre-allocation + postProcessPic = single(squeeze(inputMovieCropped(:,:,options.refFrame))); + mask = single(ones(size(postProcessPic))); + imgRegMask = single(double(mask)); + % we add an offset to be able to give NaN to black borders + averagePictureEdge = zeros(size(imgRegMask)); + refPic = single(squeeze(inputMovieCropped(:,:,options.refFrame))); + % refPic = squeeze(inputMovieCropped(:,:,options.refFrame)); + + MatrixMotCorrDispl = zeros(3,options.maxFrame); + + % === + %Convert array to cell array, allows slicing (not contiguous memory block) + % convertinputMovieCroppedToCell(); + % === + + % Get data class, can be removed... + movieClass = class(inputMovieCropped); + % you need this FileExchange function for progress in a parfor loop + disp('turboreg-ing...'); + disp(''); + % parallel for loop, since each turboreg operation is independent, can send each frame to separate workspaces + startTurboRegTime = tic; + % + nFramesToTurboreg = options.maxFrame; + if options.parallel==1; nWorkers=Inf;else;nWorkers=0;end + parfor (frameNo=1:nFramesToTurboreg,nWorkers) + % get current frames + % thisFrame = inputMovieCropped{frameNo}; + + thisFrame = inputMovieCropped(:,:,frameNo); + + thisFrameToAlign=single(thisFrame); + % thisFrameToAlign=thisFrame; + + if ismac + % Code to run on Mac platform + [ImageOut,ResultsOut{frameNo}] = turboreg(refPic,thisFrameToAlign,mask,imgRegMask,turboRegOptions); + % create a mask + averagePictureEdge = averagePictureEdge | ImageOut==0; + elseif isunix + % Code to run on Linux platform + [ResultsOut{frameNo}] = turboreg(refPic,thisFrameToAlign,mask,imgRegMask,turboRegOptions); + % create a mask + % averagePictureEdge = averagePictureEdge | ImageOut==0; + elseif ispc + % Code to run on Windows platform + [ImageOut,ResultsOut{frameNo}] = turboreg(refPic,thisFrameToAlign,mask,imgRegMask,turboRegOptions); + % create a mask + averagePictureEdge = averagePictureEdge | ImageOut==0; + else + % return; + disp('Platform not supported') + end - % Get data class, can be removed... - movieClass = class(inputMovieCropped); - % you need this FileExchange function for progress in a parfor loop - disp('turboreg-ing...'); - disp(''); - % parallel for loop, since each turboreg operation is independent, can send each frame to separate workspaces - startTurboRegTime = tic; - % - nFramesToTurboreg = options.maxFrame; - if options.parallel==1; nWorkers=Inf;else;nWorkers=0;end - parfor (frameNo=1:nFramesToTurboreg,nWorkers) - % get current frames - thisFrame = inputMovieCropped{frameNo}; - thisFrameToAlign=single(thisFrame); - % thisFrameToAlign=thisFrame; - - if ismac - % Code to run on Mac platform - [ImageOut,ResultsOut{frameNo}]=turboreg(refPic,thisFrameToAlign,mask,imgRegMask,turboRegOptions); - % create a mask - averagePictureEdge = averagePictureEdge | ImageOut==0; - elseif isunix - % Code to run on Linux platform - [ResultsOut{frameNo}]=turboreg(refPic,thisFrameToAlign,mask,imgRegMask,turboRegOptions); - % create a mask - % averagePictureEdge = averagePictureEdge | ImageOut==0; - elseif ispc - % Code to run on Windows platform - [ImageOut,ResultsOut{frameNo}]=turboreg(refPic,thisFrameToAlign,mask,imgRegMask,turboRegOptions); - % create a mask - averagePictureEdge = averagePictureEdge | ImageOut==0; - else - % return; - disp('Platform not supported') - end + if ~verLessThan('matlab', '9.2') + send(D, frameNo); % Update + end + end + % dispstat('Finished.','keepprev'); + toc(startTurboRegTime); + drawnow; + % save('ResultsOutFile','ResultsOut'); + case 'normcorre' + startTurboRegTime = tic; + + bound = 0; + refPic = single(squeeze(inputMovieCropped(:,:,options.refFrame))); + inputMovieCroppedTmp = inputMovieCropped(bound/2+1:end-bound/2,bound/2+1:end-bound/2,:); + + optsNoRMCorre = options.optsNoRMCorre; + if isempty(fieldnames(optsNoRMCorre)) + disp('Getting NoRMCorre params...') + optsNoRMCorre = ciapkg.motion_correction.getNoRMCorreParams(size(inputMovieCropped),'guiDisplay',0); + end - if ~verLessThan('matlab', '9.2') - send(D, frameNo); % Update - end + % Ensure NoRMCorre gets the correct inputs + optsNoRMCorre.d1 = size(inputMovieCropped,1); + optsNoRMCorre.d2 = size(inputMovieCropped,2); + % fn_structdisp(optsNoRMCorre) + % [optsNoRMCorre] = subfxn_getNoRMCorreParams(inputMovieCropped); + [~,ResultsOut,template2] = normcorre.normcorre_batch(... + inputMovieCroppedTmp,... + optsNoRMCorre,... + refPic); + clear inputMovieCroppedTmp + toc(startTurboRegTime); + otherwise end - % dispstat('Finished.','keepprev'); - toc(startTurboRegTime); - drawnow; - % save('ResultsOutFile','ResultsOut'); + + end + + function [optsNC] = subfxn_getNoRMCorreParams(inputMovieHere) + [d1,d2,T] = size(inputMovieHere); + bound = 0; + sizeCorFactor = 2; + % sizeCorFactor = 1; + optsNC = normcorre.NoRMCorreSetParms(... + 'd1', d1-bound,... + 'd2', d2-bound,... + 'init_batch', 10,... + 'bin_width', 50,... + 'grid_size', [128,128]/sizeCorFactor,... % [128,128]/2 [64,64] + 'mot_uf', 4,... + 'correct_bidir', false,... + 'overlap_pre', 32,... + 'overlap_post', 32,... + 'max_dev', 50,... + 'use_parallel', true,... + 'print_msg', true,... + 'us_fac', 4,... + 'max_shift', 100,... + 'boundary', 'NaN'); + % 'init_batch_interval',options.refFrame,... end + % function registerMovie(movieData,ResultsOut,InterpListSelection,TransformationType,options) function registerMovie() disp('registering frames...'); disp(''); - % need to register subsets of the movie so parfor won't crash due to serialization errors - % TODO: make this subset based on the size of the movie, e.g. only send 1GB chunks to workers - subsetSize = options.subsetSizeFrames; - numSubsets = ceil(length(inputMovie)/subsetSize)+1; - subsetList = round(linspace(1,length(inputMovie),numSubsets)); - display(['registering sublists: ' num2str(subsetList)]); - if options.turboregRotation==1 - disp('Using rotation in registration') - end - fprintf('Performing %s registration and %s transformation.\n',options.registrationFxn,TransformationType); - % ResultsOut{1}.Rotation - nSubsets = (length(subsetList)-1); - for thisSet=1:nSubsets - subsetStartIdx = subsetList(thisSet); - subsetEndIdx = subsetList(thisSet+1); - if thisSet==nSubsets - movieSubset = subsetStartIdx:subsetEndIdx; - display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx) ' ' num2str(thisSet) '/' num2str(nSubsets)]) - else - movieSubset = subsetStartIdx:(subsetEndIdx-1); - display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx-1) ' ' num2str(thisSet) '/' num2str(nSubsets)]) - end - movieDataTemp(movieSubset) = inputMovie(movieSubset); - % loop over and register each frame - if options.parallel==1; nWorkers=Inf;else;nWorkers=0;end - turboregRotationOption = options.turboregRotation; - registrationFxnOption = options.registrationFxn; - nMovieSubsets = length(movieSubset); - - % Create anonymous transform function to save CPU cycles in loop - switch TransformationType - case 'affine' - transformFxn = @(xform) affine2d(xform); - % tform = affine2d(double(xform)); - case 'projective' - transformFxn = @(xform) projective2d(xform); - % tform = projective2d(double(xform)); - otherwise - transformFxn = @(xform) affine2d(xform); - % tform = affine2d(double(xform)); - end + switch options.mcMethod + case 'normcorre' + startTurboRegTime = tic; + [optsNC] = subfxn_getNoRMCorreParams(inputMovie); + bound = 0; + inputMovie = normcorre.apply_shifts(inputMovie,ResultsOut,optsNC,bound/2,bound/2); % apply the shifts to the removed percentile + toc(startTurboRegTime); + case 'turboreg' + % Need to register subsets of the movie so parfor won't crash due to serialization errors. + % TODO: make this subset based on the size of the movie, e.g. only send 1GB chunks to workers. + subsetSize = options.subsetSizeFrames; + nFramesHere = size(inputMovie,3); + % numSubsets = ceil(length(inputMovie)/subsetSize)+1; + numSubsets = ceil(nFramesHere/subsetSize)+1; + % subsetList = round(linspace(1,length(inputMovie),numSubsets)); + subsetList = round(linspace(1,nFramesHere,numSubsets)); + display(['registering sublists: ' num2str(subsetList)]); + if options.turboregRotation==1 + disp('Using rotation in registration') + end + fprintf('Performing %s registration and %s transformation.\n',options.registrationFxn,TransformationType); + % ResultsOut{1}.Rotation + nSubsets = (length(subsetList)-1); + for thisSet=1:nSubsets + subsetStartIdx = subsetList(thisSet); + subsetEndIdx = subsetList(thisSet+1); + if thisSet==nSubsets + movieSubset = subsetStartIdx:subsetEndIdx; + display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx) ' ' num2str(thisSet) '/' num2str(nSubsets)]) + else + movieSubset = subsetStartIdx:(subsetEndIdx-1); + display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx-1) ' ' num2str(thisSet) '/' num2str(nSubsets)]) + end + + % Get a slice of the data. + % movieDataTemp(movieSubset) = inputMovie(movieSubset); + + % movieDataTemp = inputMovie(:,:,movieSubset); + + % loop over and register each frame + if options.parallel==1; nWorkers=Inf;else;nWorkers=0;end + + turboregRotationOption = options.turboregRotation; + registrationFxnOption = options.registrationFxn; + nMovieSubsets = length(movieSubset); + + % [transformFxn] = subfxn_transformFxn(TransformationType); + + parfor (i = movieSubset,nWorkers) + rOutTmp = ResultsOut{i}; - parfor (i = movieSubset,nWorkers) - % thisFrame = movieDataTemp{i}; - % get rotation and translation profile for image - % if turboregRotationOption==1 - % MatrixMotCorrDispl(:,i)=[ResultsOut{i}.Translation(1) ResultsOut{i}.Translation(2) ResultsOut{i}.Rotation]; - % else - % MatrixMotCorrDispl(:,i)=[ResultsOut{i}.Translation(1) ResultsOut{i}.Translation(2) 0]; - % end - - % Transform movie given results of turboreg - switch registrationFxnOption - case {'imtransform','imwarp'} + % thisFrameT = movieDataTemp{i}; + thisFrameT = inputMovie(:,:,i); + + % get rotation and translation profile for image % if turboregRotationOption==1 - % rotMat = [... - % cos(ResultsOut{i}.Rotation) sin(ResultsOut{i}.Rotation) 0;... - % -sin(ResultsOut{i}.Rotation) cos(ResultsOut{i}.Rotation) 0;... - % 0 0 0]; + % MatrixMotCorrDispl(:,i)=[rOutTmp.Translation(1) rOutTmp.Translation(2) rOutTmp.Rotation]; % else - % rotMat = [0 0 0;0 0 0;0 0 0]; + % MatrixMotCorrDispl(:,i)=[rOutTmp.Translation(1) rOutTmp.Translation(2) 0]; % end - % translateMat =... - % [0 0 0;... - % 0 0 0;... - % ResultsOut{i}.Translation(2) ResultsOut{i}.Translation(1) 0]; - % xform = translateMat + SkewingMat; - - % Get the skew/translation/rotation matrix from turboreg - SkewingMat = ResultsOut{i}.Skew; - - rotMat = [... - cos(ResultsOut{i}.Rotation) sin(ResultsOut{i}.Rotation) 0;... - -sin(ResultsOut{i}.Rotation) cos(ResultsOut{i}.Rotation) 0;... - 0 0 1]; - - translateMat =... - [1 0 0;... - 0 1 0;... - ResultsOut{i}.Translation(2) ResultsOut{i}.Translation(1) 1]; - - xform = translateMat*SkewingMat*rotMat; - - if strcmp(registrationFxnOption,'imtransform')==1 - % Perform the transformation - tform = maketform(TransformationType,double(xform)); - % InterpListSelection = 'nearest'; - movieDataTemp{i} = single(imtransform(movieDataTemp{i},tform,char(InterpListSelection),... - 'UData',[1 size(movieDataTemp{i},2)]-ResultsOut{i}.Origin(2)-1,... - 'VData',[1 size(movieDataTemp{i},1)]-ResultsOut{i}.Origin(1)-1,... - 'XData',[1 size(movieDataTemp{i},2)]-ResultsOut{i}.Origin(2)-1,... - 'YData',[1 size(movieDataTemp{i},1)]-ResultsOut{i}.Origin(1)-1,... - 'fill',NaN)); - elseif strcmp(registrationFxnOption,'imwarp')==1 - tform = transformFxn(double(xform)); - % Define input spatial referencing. - RI = imref2d(size(movieDataTemp{i}),[[1 size(movieDataTemp{i},2)]-ResultsOut{i}.Origin(2)-1],[[1 size(movieDataTemp{i},1)]-ResultsOut{i}.Origin(1)-1]); - - % Define output spatial referencing. - Rout = imref2d(size(movieDataTemp{i}),[[1 size(movieDataTemp{i},2)]-ResultsOut{i}.Origin(2)-1],[[1 size(movieDataTemp{i},1)]-ResultsOut{i}.Origin(1)-1]); - - movieDataTemp{i} = single(imwarp(movieDataTemp{i},RI,tform,char(InterpListSelection),... - 'OutputView',Rout,... - 'FillValues',NaN)); + % Transform movie given results of turboreg + + switch registrationFxnOption + case {'imtransform','imwarp'} + % if turboregRotationOption==1 + % rotMat = [... + % cos(rOutTmp.Rotation) sin(rOutTmp.Rotation) 0;... + % -sin(rOutTmp.Rotation) cos(rOutTmp.Rotation) 0;... + % 0 0 0]; + % else + % rotMat = [0 0 0;0 0 0;0 0 0]; + % end + + % translateMat =... + % [0 0 0;... + % 0 0 0;... + % rOutTmp.Translation(2) rOutTmp.Translation(1) 0]; + % xform = translateMat + SkewingMat; + + % Get the skew/translation/rotation matrix from turboreg + SkewingMat = rOutTmp.Skew; + + rotMat = [... + cos(rOutTmp.Rotation) sin(rOutTmp.Rotation) 0;... + -sin(rOutTmp.Rotation) cos(rOutTmp.Rotation) 0;... + 0 0 1]; + + translateMat =... + [1 0 0;... + 0 1 0;... + rOutTmp.Translation(2) rOutTmp.Translation(1) 1]; + + xform = translateMat*SkewingMat*rotMat; + + if strcmp(registrationFxnOption,'imtransform')==1 + % Perform the transformation + tform = maketform(TransformationType,double(xform)); + % InterpListSelection = 'nearest'; + thisFrameT = single(imtransform(thisFrameT,tform,char(InterpListSelection),... + 'UData',[1 size(thisFrameT,2)]-rOutTmp.Origin(2)-1,... + 'VData',[1 size(thisFrameT,1)]-rOutTmp.Origin(1)-1,... + 'XData',[1 size(thisFrameT,2)]-rOutTmp.Origin(2)-1,... + 'YData',[1 size(thisFrameT,1)]-rOutTmp.Origin(1)-1,... + 'fill',NaN)); + elseif strcmp(registrationFxnOption,'imwarp')==1 + tform = subfxn_transformFxn(TransformationType,xform); + % Define input spatial referencing. + RI = imref2d(size(thisFrameT),[[1 size(thisFrameT,2)]-rOutTmp.Origin(2)-1],[[1 size(thisFrameT,1)]-rOutTmp.Origin(1)-1]); + + % Define output spatial referencing. + Rout = imref2d(size(thisFrameT),[[1 size(thisFrameT,2)]-rOutTmp.Origin(2)-1],[[1 size(thisFrameT,1)]-rOutTmp.Origin(1)-1]); + + thisFrameT = single(imwarp(thisFrameT,RI,tform,char(InterpListSelection),... + 'OutputView',Rout,... + 'FillValues',NaN)); + end + case 'transfturboreg' + frameClass = class(thisFrameT); + thisFrameT = ... + cast(... + transfturboreg(... + single(thisFrameT),... + ones(size(thisFrameT),'single'),... + rOutTmp),... + frameClass); + otherwise + % do nothing end - case 'transfturboreg' - frameClass = class(movieDataTemp{i}); - movieDataTemp{i} = ... - cast(... - transfturboreg(... - single(movieDataTemp{i}),... - ones(size(movieDataTemp{i}),'single'),... - ResultsOut{i}),... - frameClass); - otherwise - % do nothing + + inputMovie(:,:,i) = thisFrameT; + end + dispstat('Finished.','keepprev'); + + % inputMovie(movieSubset)=movieDataTemp(movieSubset); + % clear movieDataTemp; end + otherwise + % Do nothing end - dispstat('Finished.','keepprev'); - - inputMovie(movieSubset)=movieDataTemp(movieSubset); - clear movieDataTemp; - end end function removeInputMovieEdges() % turboreg outputs 0s where movement goes off the screen @@ -669,13 +797,13 @@ function removeInputMovieEdges() case 'imtransform' reverseStr = ''; for row=1:size(inputMovie,1) - thisMovieMinMask(row,:) = logical(nanmax(isnan(squeeze(inputMovie(3,:,:))),[],2)); + thisMovieMinMask(row,:) = logical(max(isnan(squeeze(inputMovie(3,:,:))),[],2,'omitnan')); reverseStr = cmdWaitbar(row,size(inputMovie,1),reverseStr,'inputStr','getting crop amount','waitbarOn',1,'displayEvery',5); end case 'transfturboreg' reverseStr = ''; for row=1:size(inputMovie,1) - thisMovieMinMask(row,:) = logical(nanmin(squeeze(inputMovie(row,:,:))~=0,[],2)==0); + thisMovieMinMask(row,:) = logical(min(squeeze(inputMovie(row,:,:))~=0,[],2,'omitnan')==0); reverseStr = cmdWaitbar(row,size(inputMovie,1),reverseStr,'inputStr','getting crop amount','waitbarOn',1,'displayEvery',5); end otherwise @@ -765,8 +893,8 @@ function cropMatrixPreProcess(pxToCropPreprocess) bottomRowCrop = size(inputMovie,1)-pxToCropPreprocess; % bottom row rightColCrop = size(inputMovie,2)-pxToCropPreprocess; % right column - rowLen = size(inputMovie,1); - colLen = size(inputMovie,2); + % rowLen = size(inputMovie,1); + % colLen = size(inputMovie,2); % set leftmost columns to NaN inputMovie(1:end,1:leftColCrop,:) = NaN; % set rightmost columns to NaN @@ -804,7 +932,7 @@ function addBlackEdgeToMovie() rectCrop=[xmin ymin xmax-xmin ymax-ymin]; if options.showFigs==1 - [figHandle, figNo] = openFigure(100, ''); + [~, ~] = openFigure(100, ''); imagesc(imcrop(inputMovie(:,:,1),rectCrop)); end % To get the final size, we just apply on the first figure @@ -923,11 +1051,13 @@ function cropAndNormalizeInputMovie() %Get dimension information about 3D movie matrix [inputMovieX, inputMovieY, inputMovieZ] = size(inputMovieCropped); - reshapeValue = size(inputMovieCropped); + % reshapeValue = size(inputMovieCropped); + %Convert array to cell array, allows slicing (not contiguous memory block) - inputMovieCropped = squeeze(mat2cell(inputMovieCropped,inputMovieX,inputMovieY,ones(1,inputMovieZ))); + % inputMovieCropped = squeeze(mat2cell(inputMovieCropped,inputMovieX,inputMovieY,ones(1,inputMovieZ))); - imageNow = squeeze(inputMovieCropped{1}); + % imageNow = squeeze(inputMovieCropped{1}); + imageNow = squeeze(inputMovieCropped(:,:,1)); [rows,cols] = size(imageNow); r1 = min(rows,cols)/options.matlabdiskR1; r2 = options.matlabdiskR2; @@ -935,20 +1065,24 @@ function cropAndNormalizeInputMovie() hDisk2 = fspecial('disk', r2); transform = @(A) transform_2(A,hDisk,hDisk2); reverseStr = ''; - % nImages = size(inputMovieCropped,3); - nImages = length(inputMovieCropped); + nImages = size(inputMovieCropped,3); + % nImages = length(inputMovieCropped); if options.parallel==1; nWorkers=Inf;else;nWorkers=0;end parfor (imageNo = 1:nImages,nWorkers) - imageNow = squeeze(inputMovieCropped{imageNo}); - inputMovieCropped{imageNo} = transform(imageNow); + % imageNow = squeeze(inputMovieCropped{imageNo}); + % inputMovieCropped{imageNo} = transform(imageNow); + + imageNow = squeeze(inputMovieCropped(:,:,imageNo)); + inputMovieCropped(:,:,imageNo) = transform(imageNow); + % if (mod(imageNo,20)==0|imageNo==nImages) % reverseStr = cmdWaitbar(imageNo,nImages,reverseStr,'inputStr','fspecial normalizing'); % end end dispstat('Finished.','keepprev'); - inputMovieCropped = cat(3,inputMovieCropped{:}); + % inputMovieCropped = cat(3,inputMovieCropped{:}); case 'divideByLowpass' disp('dividing movie by lowpass...') inputMovieCropped = normalizeMovie(single(inputMovieCropped),'normalizationType','imfilter','blurRadius',20,'waitbarOn',1); @@ -1065,4 +1199,19 @@ function subfxn_dispMovieFrames(inputMovieCropped,titleStr,inputMod) A_tr = imfilter(A_tr, asm_filter); +end +function [tform] = subfxn_transformFxn(TransformationType,xform) + % Create anonymous transform function to save CPU cycles in loop + switch TransformationType + case 'affine' + % transformFxn = @(xform) affine2d(xform); + tform = affine2d(double(xform)); + % tform = subfxn_transformFxn(TransformationType,double(xform)); + case 'projective' + % transformFxn = @(xform) projective2d(xform); + tform = projective2d(double(xform)); + otherwise + % transformFxn = @(xform) affine2d(xform); + tform = affine2d(double(xform)); + end end \ No newline at end of file diff --git a/+ciapkg/+movie_processing/downsampleMovie.m b/+ciapkg/+movie_processing/downsampleMovie.m index c8e8d02..fb32d30 100644 --- a/+ciapkg/+movie_processing/downsampleMovie.m +++ b/+ciapkg/+movie_processing/downsampleMovie.m @@ -1,27 +1,34 @@ function [inputMovie] = downsampleMovie(inputMovie, varargin) + % [inputMovie] = downsampleMovie(inputMovie, varargin) + % % Downsamples a movie in either space or time, uses floor to calculate downsampled dimensions. + % % Biafra Ahanonu % started 2013.11.09 [09:31:32] % % inputs - % inputMovie: a NxMxP matrix + % inputMovie: a [x y t] matrix. % options - % downsampleType - % downsampleFactor - amount to downsample in time + % downsampleDimension - Str: 'time' or 'space'. Dimension to downsample + % downsampleType - Str: 'bilinear' or 'bicubic'. Type of downsampling. + % downsampleFactor - Int or float: amount to downsample dimension by. + % % changelog % 2013.12.19 added the spatial downsampling to the function. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2022.02.09 [23:42:18] - Update for Matlab standards. + % 2022.06.28 [16:58:08] - Update information for users/comments. % TODO import ciapkg.api.* % import CIAtah functions in ciapkg package API. %======================== % default options - % time or space + % Str: 'time' or 'space'. Dimension to downsample options.downsampleDimension = 'time'; + % Str: 'bilinear' or 'bicubic'. Type of downsampling. options.downsampleType = 'bilinear'; - % any value, integers preferred + % Int or float: amount to downsample dimension by. options.downsampleFactor = 4; % exact dimensions to downsample in Z (time) options.downsampleZ = []; diff --git a/+ciapkg/+movie_processing/normalizeMovie.m b/+ciapkg/+movie_processing/normalizeMovie.m index 5423f1d..fa6c26b 100644 --- a/+ciapkg/+movie_processing/normalizeMovie.m +++ b/+ciapkg/+movie_processing/normalizeMovie.m @@ -15,6 +15,7 @@ % 2021.01.15 [21:09:55] - Moved detrend support into normalizeMovie. % 2021.06.02 [20:04:49] - Detrend movie now uses nanmean to get around issues in motion corrected videos. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.05.16 [16:39:45] - Switch away from using nanmean to mean('omitnan'). Detrend flight code refactor. % TODO % @@ -721,7 +722,7 @@ function subfxnDetrend() %Get dimension information about 3D movie matrix [inputMovieX, inputMovieY, inputMovieZ] = size(inputMovie); - frameMeanInputMovie = squeeze(nanmean(inputMovie,[1 2])); + frameMeanInputMovie = squeeze(mean(inputMovie,[1 2],'omitnan')); trendVals = frameMeanInputMovie - detrend(frameMeanInputMovie,option.detrendDegree); @@ -740,12 +741,14 @@ function subfxnDetrend() % end parfor frame = 1:nFramesToNormalize - thisFrame = inputMovie(:,:,frame); - thisFrame = squeeze(thisFrame); - thisFrame = thisFrame - trendVals(frame); - thisFrame = thisFrame + meanInputMovie; + inputMovie(:,:,frame) = inputMovie(:,:,frame) - trendVals(frame) + meanInputMovie; - inputMovie(:,:,frame) = thisFrame; + % thisFrame = inputMovie(:,:,frame); + % thisFrame = squeeze(thisFrame); + % thisFrame = thisFrame - trendVals(frame); + % thisFrame = thisFrame + meanInputMovie; + + % inputMovie(:,:,frame) = thisFrame; if ~verLessThan('matlab', '9.2') send(D, frame); % Update diff --git a/+ciapkg/+signal_extraction/computeCnmfSignalExtractionPatch.m b/+ciapkg/+signal_extraction/computeCnmfSignalExtractionPatch.m index dbcd4db..45eefeb 100644 --- a/+ciapkg/+signal_extraction/computeCnmfSignalExtractionPatch.m +++ b/+ciapkg/+signal_extraction/computeCnmfSignalExtractionPatch.m @@ -1,5 +1,5 @@ function [cnmfAnalysisOutput] = computeCnmfSignalExtractionPatch(inputMovie,numExpectedComponents,varargin) - % Brapper function for CNMF, update for most recent versions. + % Wrapper function for CNMF, update for most recent versions. % Building off of demo_script.m in CNMF github repo % Most recent commit tested on: https://github.com/epnev/ca_source_extraction/commit/187bbdbe66bca466b83b81861b5601891a95b8d1 % https://github.com/epnev/ca_source_extraction/blob/master/demo_script_class.m diff --git a/+ciapkg/+signal_extraction/computeCnmfSignalExtraction_v2.m b/+ciapkg/+signal_extraction/computeCnmfSignalExtraction_v2.m index e196a95..9e77f56 100644 --- a/+ciapkg/+signal_extraction/computeCnmfSignalExtraction_v2.m +++ b/+ciapkg/+signal_extraction/computeCnmfSignalExtraction_v2.m @@ -1,5 +1,5 @@ function [cnmfAnalysisOutput] = computeCnmfSignalExtraction_v2(inputMovie,numExpectedComponents,varargin) - % Brapper function for CNMF, update for most recent versions. + % Wrapper function for CNMF, update for most recent versions. % Building off of demo_script.m in CNMF github repo % Most recent commit tested on: https://github.com/epnev/ca_source_extraction/commit/187bbdbe66bca466b83b81861b5601891a95b8d1 % https://github.com/epnev/ca_source_extraction/blob/master/demo_script_class.m diff --git a/+ciapkg/+signal_extraction/computeCnmfeSignalExtraction_batch.m b/+ciapkg/+signal_extraction/computeCnmfeSignalExtraction_batch.m index ae739d4..a1b2245 100644 --- a/+ciapkg/+signal_extraction/computeCnmfeSignalExtraction_batch.m +++ b/+ciapkg/+signal_extraction/computeCnmfeSignalExtraction_batch.m @@ -21,6 +21,7 @@ % 2021.01.24 [14:29:06] - Added trace origin type to output structure. % 2021.03.20 [20:20:27] - extractedSignalsType, extractedSignalsEstType struct update. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.07.29 [20:16:13] - Updated to add option to ignore remove_false_positives. % TODO % @@ -30,10 +31,14 @@ % OVERALL % turn on parallel options.nonCNMF.parallel = 1; + % Binary: 1 = classify components, + options.nonCNMF.classifyComponents = 1; % Binary: 1 = run merging algorithms options.runMerge = 1; % Binary: 1 = remove false positives using CNMF-E algorithm options.runRemoveFalsePositives = 1; + % Str: HDF5 dataset name + options.nonCNMF.inputDatasetName = '/1'; % ===COMPUTATION % Float: GB, memory space you allow to use in MATLAB options.memory_size_to_use = 8; % @@ -309,7 +314,7 @@ %% update spatial components %% pick neurons from the residual - [center_res, Cn_res, PNR_res] =neuron.initComponents_residual_parallel([], save_initialization, use_parallel, min_corr_res, min_pnr_res, seed_method_res); + [center_res, Cn_res, PNR_res] = neuron.initComponents_residual_parallel([], save_initialization, use_parallel, min_corr_res, min_pnr_res, seed_method_res); if show_init axes(ax_init); plot(center_res(:, 2), center_res(:, 1), '.g', 'markersize', 10); @@ -331,8 +336,10 @@ % update temporal neuron.update_temporal_parallel(use_parallel); - % delete bad neurons - neuron.remove_false_positives(); + if options.runRemoveFalsePositives==1 + % delete bad neurons + neuron.remove_false_positives(); + end % merge neurons based on temporal correlation + distances neuron.merge_neurons_dist_corr(show_merge); @@ -350,6 +357,7 @@ % delete neurons tags = neuron.tag_neurons_parallel(); % find neurons with fewer nonzero pixels than min_pixel and silent calcium transients + ids = find(tags>0); if ~isempty(ids) neuron.viewNeurons(ids, neuron.C_raw); @@ -363,14 +371,18 @@ K = size(neuron.A,2); tags = neuron.tag_neurons_parallel(); % find neurons with fewer nonzero pixels than min_pixel and silent calcium transients - neuron.remove_false_positives(); + if options.runRemoveFalsePositives==1 + neuron.remove_false_positives(); + end neuron.merge_neurons_dist_corr(show_merge); neuron.merge_high_corr(show_merge, merge_thr_spatial); if K~=size(neuron.A,2) neuron.update_spatial_parallel(use_parallel); neuron.update_temporal_parallel(use_parallel); - neuron.remove_false_positives(); + if options.runRemoveFalsePositives==1 + neuron.remove_false_positives(); + end end %% save the workspace for future analysis diff --git a/+ciapkg/+signal_processing/computePeakStatistics.m b/+ciapkg/+signal_processing/computePeakStatistics.m index a53d763..011ff8f 100644 --- a/+ciapkg/+signal_processing/computePeakStatistics.m +++ b/+ciapkg/+signal_processing/computePeakStatistics.m @@ -1,49 +1,70 @@ function [peakOutputStat] = computePeakStatistics(inputSignals,varargin) - % Get slope ratio, the average trace from detected peaks, and other peak-related statistics + % [peakOutputStat] = computePeakStatistics(inputSignals,varargin) + % + % Get slope ratio, the average trace from detected peaks, and other peak-related statistics. + % % Biafra Ahanonu % started: 2013.12.09 - % inputs - % inputSignals = [n m] matrix where n={1....N} - % outputs - % slopeRatio - % traceErr - % fwhmSignal - % avgPeakAmplitude - % spikeCenterTrace - % pwelchPxx - % pwelchf + % + % Inputs + % inputSignals = [n m] matrix where n = {1....N} and m = frames. + % + % Outputs + % peakOutputStat - structure containing the following fields, where N = number of signals, P = # of peaks, W = peak window (frames): + % fwhmSignal - Cell array {1 N} of [1 P] vectors: Full width at half maximum for each peak. + % slopeRatio - Vector [1 N]: slope ratio (e.g. area of rise vs. decay) of peaks for each signal. + % avgSpikeTrace - Vector [N W]: average peak for each signal. + % avgSpikeVar - Vector [1 N]: average variance of the signal after the peak across all peaks. + % avgSpikeCorr - Vector [1 N]: average correlation of all post-peak signals. + % traceErr - Vector [1 N]: deviation in the error across all peaks within W window. + % avgFwhm - Vector [1 N]: mean Full width at half maximum across all peaks for each signal. + % fwhmSignalSignals - Cell array {1 N} of [1 P] vectors: Full width at half maximum for each peak. + % avgPeakAmplitude - Vector [1 N]: mean peak amplitude across all peaks for each signal. + % traceSkewness - Vector [1 N]: the skewness of each signal. + % traceKurtosis - Vector [1 N]: the kurtosis of each signal. + % traceFanoFactor - Vector [1 N]: the fano factor of each signal. + % traceAutoCorr - Vector [1 N]: the auto-correlation of each signal defined at set frame shift (see options.frameShiftAmt). + % spikeCenterTrace - Cell array {1 N} of [P W] vectors: matrix containing signal sliced around all peaks. + % pwelchPxx - Cell array {1 N}: PSDs for each signal. + % pwelchf - Cell array {1 N}: frequency corresponding to the output PSDs for each signal. + % TODO % - Use the SNR signal calculated to determine when to end the S ratio calculation % changelog % 2013.12.24 - changed output so that it is a structure, allows more flexibility when adding new statistics. % 2019.09.10 [11:30:07] - Added fano factor, trace frame lagged auto-correlation, parallelization, and misc improvements. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.03.14 [01:31:51] - Update comments along with code standard improvements. + % 2022.04.26 [00:12:03] - options.testpeaksArray can be used regardless of vector (with peak frames) orientation. import ciapkg.api.* % import CIAtah functions in ciapkg package API. %======================== - % + % Char cell array: cell array of strings for different features to include in the output. options.featureList = {'avgSpikeTrace','spikeCenterTrace','avgSpikeVar','avgSpikeCorr','avgPeakAmplitude','slopeRatio','fwhmTrace'}; + % Binary: 1 = only detect and output the slope ratio. 0 = perform full analysis/output. options.onlySlopeRatio = 0; - % - options.spikeROI = [-40:40]; - % + % Int vector: window (in frames) around each peak to perform the various peak statistics analyses. + options.spikeROI = -40:40; + % Int: number of frames before and after peak to use for certain statistics, e.g. slopeRatio. options.slopeFrameWindow = 10; - % + % Binary: 1 = show the wait bar. 0 = do not show wait bar. options.waitbarOn = 1; - % determine whether power-spectral density should be calculated + % Binary: 1 = calculate power-spectral density. options.psd = 0; - % should fwhm analysis be plotted? + % Binary: 1 = plot fwhm analysis. 0 = do not plot analysis. options.fwhmPlot = 0; - % save time if already computed peaks + % Matrix: save time if already computed peaks. [nSignals frame] matrix. Binary matrix with 1 = peaks, 0 = non-peaks. options.testpeaks = []; + % Cell array: save time if already computed peaks. {1 nSignals} cell array. Each cell contains [1 nPeaks] vector that stores the frame locations of each peak. options.testpeaksArray = []; - % Median filter the data + % Binary: 1 = median filter the data. options.medianFilter = 1; - % number of frames to calculate median filter + % Int: number of frames to calculate rolling median filter. options.medianFilterLength = 200; + % Int: the size of the moving average to use on the input signals, to smooth out noise. options.movAvgFiltSize = []; - % Int: how many frames to shift when calculating auto-correlation + % Int: number of frames to shift when calculating auto-correlation. options.frameShiftAmt = 2; % get options options = getOptions(options,varargin); @@ -64,24 +85,6 @@ end end - % peakOutputStat.fwhmSignal = []; - % peakOutputStat.slopeRatio = []; - % peakOutputStat.avgSpikeTrace = []; - % peakOutputStat.avgSpikeVar = []; - % peakOutputStat.avgSpikeCorr = []; - % peakOutputStat.traceErr = []; - % peakOutputStat.fwhmSignal = []; - % peakOutputStat.avgFwhm = []; - % peakOutputStat.fwhmSignalSignals = []; - % peakOutputStat.avgPeakAmplitude = []; - % peakOutputStat.traceSkewness = []; - % peakOutputStat.traceKurtosis = []; - % peakOutputStat.traceFanoFactor = []; - % peakOutputStat.traceAutoCorr = []; - % peakOutputStat.spikeCenterTrace = []; - % peakOutputStat.pwelchPxx = []; - % peakOutputStat.pwelchf = []; - % get a list of all indices to pull out nSignals = size(inputSignals,1); % reverseStr = ''; @@ -98,7 +101,7 @@ afterEach(D, @nUpdateParforProgress); p = 0; N = nSignals; - nInterval = round(nSignals/30);%25 + nInterval = round(N/30);%25 options_waitbarOn = options.waitbarOn; end @@ -128,12 +131,12 @@ avgSpikeCorr(i) = peakStat.avgSpikeCorr; traceErr(i) = peakStat.traceErr; fwhmSignal{i} = peakStat.fwhmTrace(:); - avgFwhm(i) = nanmean(peakStat.fwhmTrace(:)); + avgFwhm(i) = mean(peakStat.fwhmTrace(:),'omitnan'); fwhmSignalSignals{i} = peakStat.fwhmTrace(:); avgPeakAmplitude(i) = peakStat.avgPeakAmplitude; traceSkewness(i) = skewness(thisSignal(:)); traceKurtosis(i) = kurtosis(thisSignal(:)); - traceFanoFactor(i) = (nanstd(thisSignal(:))^2)/nanmean(thisSignal(:)); + traceFanoFactor(i) = (std(thisSignal(:),'omitnan')^2)/mean(thisSignal(:),'omitnan'); traceAutoCorr(i) = corr(thisSignal(:),circshift(thisSignal(:),options_frameShiftAmt)); spikeCenterTrace{i} = peakStat.spikeCenterTrace; if options_psd==1 @@ -217,8 +220,10 @@ function nUpdateParforProgress(~) % [testpeaks dummyVar] = computeSignalPeaks(inputSignal); % if peaks exists, do statistics else return NaNs if ~isempty(testpeaks) + % Force peaks to be correct dimensions. + testpeaks = testpeaks(:); % get a list of indices around which to extract spike signals - extractMatrix = bsxfun(@plus,testpeaks',spikeROI); + extractMatrix = bsxfun(@plus,testpeaks,spikeROI); extractMatrix(extractMatrix<=0)=1; extractMatrix(extractMatrix>=size(inputSignal,2))=size(inputSignal,2); peakStat.spikeCenterTrace = reshape(inputSignal(extractMatrix),size(extractMatrix)); @@ -239,7 +244,7 @@ function nUpdateParforProgress(~) if size(spikeCenterTrace,1)==1 peakStat.avgSpikeTrace = spikeCenterTrace; else - peakStat.avgSpikeTrace = nanmean(spikeCenterTrace); + peakStat.avgSpikeTrace = mean(spikeCenterTrace,'omitnan'); end % or get correlation @@ -250,7 +255,7 @@ function nUpdateParforProgress(~) corrHere = corr(spikeCenterTrace(:,round(end/2):end)',spikeCenterTrace(:,round(end/2):end)','type','Spearman'); corrHere(logical(eye(size(corrHere)))) = NaN; - peakStat.avgSpikeCorr = nanmean(corrHere(:)); + peakStat.avgSpikeCorr = mean(corrHere(:),'omitnan'); if 0 % size(spikeCenterTrace) @@ -259,29 +264,29 @@ function nUpdateParforProgress(~) corrHere = corr(spikeCenterTrace',spikeCenterTrace'); imagesc(corrHere) corrHere(logical(eye(size(corrHere)))) = NaN; - title(num2str(nanmean(corrHere(:)))) + title(num2str(mean(corrHere(:),'omitnan'))) subplot(1,2,2) corrHere = corr(spikeCenterTrace(:,round(end/2):end)',spikeCenterTrace(:,round(end/2):end)'); imagesc(corrHere) corrHere(logical(eye(size(corrHere)))) = NaN; - title(num2str(nanmean(corrHere(:)))) + title(num2str(mean(corrHere(:),'omitnan'))) pause(0.01); % size(corr(spikeCenterTrace',spikeCenterTrace')) end % peakStat.avgSpikeVar = nanmean(squeeze(nanvar(spikeCenterTrace(:,round(end/2):end),[],1))); % Change to index of dispersion - varH = nanvar(spikeCenterTrace(:,round(end/2):end),[],1); - meanH = nanmean(spikeCenterTrace(:,round(end/2):end),1); - peakStat.avgSpikeVar = nanmean(squeeze(varH)); - peakStat.avgSpikeVMR = nanmean(squeeze(varH./meanH)); + varH = var(spikeCenterTrace(:,round(end/2):end),[],1,'omitnan'); + meanH = mean(spikeCenterTrace(:,round(end/2):end),1,'omitnan'); + peakStat.avgSpikeVar = mean(squeeze(varH),'omitnan'); + peakStat.avgSpikeVMR = mean(squeeze(varH./meanH),'omitnan'); % get the peak amplitude peakStat.avgPeakAmplitude = peakStat.avgSpikeTrace(find(spikeROI==0)); % slopeRatio = (peakDfof-avgSpikeTrace(find(spikeROI==-slopeFrameWindow)))/(peakDfof-avgSpikeTrace(find(spikeROI==slopeFrameWindow))); % get the deviation in the error - peakStat.traceErr = sum(nanstd(spikeCenterTrace))/sqrt(size(spikeCenterTrace,1)); + peakStat.traceErr = sum(std(spikeCenterTrace,'omitnan'))/sqrt(size(spikeCenterTrace,1)); % % get a ratio metric (normalized between 1 and -1) for the asymmetry in the peaks % prePeakIdx = find(spikeROI==-(slopeFrameWindow)):find(spikeROI==-1); @@ -314,7 +319,7 @@ function nUpdateParforProgress(~) if options_psd==1 % get the power-spectrum - [peakStat.pwelchPxx peakStat.pwelchf] = pwelch(inputSignal,100,25,512,5); + [peakStat.pwelchPxx, peakStat.pwelchf] = pwelch(inputSignal,100,25,512,5); end else peakStat.avgSpikeTrace = nan(1,length(spikeROI)); @@ -331,7 +336,7 @@ function nUpdateParforProgress(~) end end end -function plotStatistics() +% function plotStatistics() % figure(92929) % hist(fwhmSignal,[0:nanmax(fwhmSignal)]); box off; % xlabel('FWHM (frames)'); ylabel('count'); @@ -352,4 +357,24 @@ function plotStatistics() % errorbar(spikeROI, avgSpikeTrace, traceErr); % t=1:length(traceErr); % fill([spikeROI fliplr(spikeROI)],[avgSpikeTrace+traceErr fliplr(avgSpikeTrace-traceErr)],[4 4 4]/8, 'FaceAlpha', 0.4, 'EdgeColor','none') -end \ No newline at end of file +% end +% function localfxn_createOutput() + + % peakOutputStat.fwhmSignal = []; + % peakOutputStat.slopeRatio = []; + % peakOutputStat.avgSpikeTrace = []; + % peakOutputStat.avgSpikeVar = []; + % peakOutputStat.avgSpikeCorr = []; + % peakOutputStat.traceErr = []; + % peakOutputStat.fwhmSignal = []; + % peakOutputStat.avgFwhm = []; + % peakOutputStat.fwhmSignalSignals = []; + % peakOutputStat.avgPeakAmplitude = []; + % peakOutputStat.traceSkewness = []; + % peakOutputStat.traceKurtosis = []; + % peakOutputStat.traceFanoFactor = []; + % peakOutputStat.traceAutoCorr = []; + % peakOutputStat.spikeCenterTrace = []; + % peakOutputStat.pwelchPxx = []; + % peakOutputStat.pwelchf = []; +% end \ No newline at end of file diff --git a/+ciapkg/+signal_processing/computeSignalPeaks.m b/+ciapkg/+signal_processing/computeSignalPeaks.m index b368a9f..6f16c4a 100644 --- a/+ciapkg/+signal_processing/computeSignalPeaks.m +++ b/+ciapkg/+signal_processing/computeSignalPeaks.m @@ -1,35 +1,43 @@ -function [signalPeaks, signalPeaksArray, signalSigmas] = computeSignalPeaks(signalMatrix, varargin) +function [signalPeaks, signalPeaksArray, signalSigmas, signalStruct] = computeSignalPeaks(signalMatrix, varargin) + % [signalPeaks, signalPeaksArray, signalSigmas] = computeSignalPeaks(signalMatrix, varargin) + % % Binarize [0,1] input analog signals based on peaks in the signal. + % % Biafra Ahanonu % started: 2013.10.28 + % % inputs - % signalMatrix: [nSignals frame] matrix + % signalMatrix: [nSignals frame] matrix containing analog input signals. % outputs - % signalPeaks: [nSignals frame] matrix. Binary matrix with 1 = peaks. - % signalPeaksArray: {1 nSignals} cell array. Each cell contains [1 nPeaks] vector that stores the frame locations of each peak. + % signalPeaks: [nSignals frame] matrix. Binary matrix with 1 = peaks, 0 = non-peaks. + % signalPeaksArray: {1 nSignals} cell array. Each cell contains [1 nPeaks] vector that stores the frame locations of each peak. + % signalSigmas: [nSignals 1] - std of each signal. + % signalStruct: structure containing signalPeaks and signalPeaksArray if multiple thresholds requested. % options - % See below. - % % make a plot? - % options.makePlots = 0; - % % show waitbar? - % options.waitbarOn = 1; - % % make summary plots of spike information - % options.makeSummaryPlots = 0; - % % number of standard deviations above the threshold to count as spike - % options.numStdsForThresh = 3; - % % minimum number of time units between events - % options.minTimeBtEvents = 8; - % % shift peak detection - % options.nFramesShift = 0; - % % should diff and fast oopsi be done? - % options.addedAnalysis = 0; - % % use simulated oopsi data - % options.oopsiSimulated = 0; + % See below. + % % make a plot? + % options.makePlots = 0; + % % show waitbar? + % options.waitbarOn = 1; + % % make summary plots of spike information + % options.makeSummaryPlots = 0; + % % number of standard deviations above the threshold to count as spike + % options.numStdsForThresh = 3; + % % minimum number of time units between events + % options.minTimeBtEvents = 8; + % % shift peak detection + % options.nFramesShift = 0; + % % should diff and fast oopsi be done? + % options.addedAnalysis = 0; + % % use simulated oopsi data + % options.oopsiSimulated = 0; % changelog % 2015.10.06 [00:14:09] Changed computePeakForSignal to shift the signal to the actual nearby peak since findpeak is sometimes off by a frame or two, should also improve the S-ratio. % 2016.07.05 [14:52:43] Made changes to computePeakForSignal to improve diff based peak detection. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.04.20 [21:29:09] - Better comments for options. % TODO: + % Add option to obtain multiple signal peak outputs from different thresholds in the same run, would save time. % allow input of options file (e.g. for different GCaMP variants, brain regions, etc.) % integrate nearest neighbor into analysis if there is a lot of cross-talk % possibly integrate into identifySpikes? @@ -42,47 +50,43 @@ %======================== % Binary: 1 = show plots with found events and other information for each signal. 0 = do not show signal plot GUI. options.makePlots = 0; - % show waitbar? + % Binary: 1 = show wait bar, 0 = do not show wait bar. options.waitbarOn = 1; - % make summary plots of spike information + % Binary: 1 = make summary plots of spike information, 0 = no summary plots. options.makeSummaryPlots = 0; % === - % number of standard deviations above the threshold to count as spike - % options.numStdsForThresh = 3; - % options.numStdsForThresh = 0.5; - options.numStdsForThresh = 3; - % alternative for display purposes + % Int: number of standard deviations above the threshold to count as spike + options.numStdsForThresh = 3; % 0.5 + % DEPRECIATED - Int: alternative to options.numStdsForThresh for display purposes options.numStdsForThreshTwo = 2; - % minimum number of time units between events + % Int: minimum number of time units between events. options.minTimeBtEvents = 8; - % detect on differential ('diff') or raw ('raw') trace - % options.detectMethod = 'raw'; - options.detectMethod = 'diff'; - % the size of the moving average + % Str: detect on differential ('diff') or raw ('raw') trace + options.detectMethod = 'diff'; % 'raw' + % Int: the size of the window to use to ignore smaller peaks near larger peaks. options.movAvgReqSize = 2; + % Int: the size of the moving average to use on the input signals, to smooth out noise. options.movAvgFiltSize = 3; - % decide whether to have a moving average + % Binary: 1 = use moving average as specified in options.movAvgFiltSize. options.doMovAvg = 1; - % subtract median calculated over a filter of some range. + % Binary: 1 = subtract median calculated over a filter of some range. options.doMedianFilter = 1; - % number of frames to calculate median filter + % Int: number of frames to calculate rolling median filter. options.medianFilterLength = 201; - % report the midpoint of the rise - options.reportMidpoint=0; - % shift peak detection - options.nFramesShift = 0; - % region around each peak to look for a maximum to adjust the test peak by + % Binary: 1 = leave report peaks as determined by findpeaks (e.g. normally midpoint of the peak rise if using 'diff'). 0 = adjust peak location to the maximum value found within a options.peakMaxLook window. + options.reportMidpoint = 0; + % Int vector: frames before and after region around each peak to look for a maximum to adjust the test peak by. options.peakMaxLook = -6:6; + % Int: number of frames to shift detected peaks. + options.nFramesShift = 0; % === - % should diff and fast oopsi be done? + % Binary: 1 = perform diff and fast oopsi. options.addedAnalysis = 0; - % use simulated oopsi data + % Binary: 1 = use simulated oopsi data. options.oopsiSimulated = 0; - % decide whether to have a moving average - % options.doMovAvg = 0; - % 1 = open workers, 0 = do not open workers + % Binary: 1 = open workers, 0 = do not open workers options.parallel = 1; - % display output + % Binary: 1 = display output information. 0 = suppress most output (e.g. when running in batch for certain applications). options.outputInfo = 1; % Binary: 1 = convert input inputSignals matrix to cell array options.convertSignalsToCell = 1; @@ -120,6 +124,8 @@ end end + signalStruct = struct; + % this matrix will contain binarized version of signalMatrix signalPeaks = zeros(size(signalMatrix)); % contains a list for each signal of locations of peaks diff --git a/+ciapkg/+signal_processing/computeSignalSnr.m b/+ciapkg/+signal_processing/computeSignalSnr.m index 86c9c8e..686e91f 100644 --- a/+ciapkg/+signal_processing/computeSignalSnr.m +++ b/+ciapkg/+signal_processing/computeSignalSnr.m @@ -1,26 +1,34 @@ function [inputSnr, inputMse, inputSnrSignal, inputSnrNoise, outputSignal, outputNoise] = computeSignalSnr(inputSignals,varargin) - % Obtains an approximate SNR for an input signal + % [inputSnr, inputMse, inputSnrSignal, inputSnrNoise, outputSignal, outputNoise] = computeSignalSnr(inputSignals,varargin) + % + % Calculates the SNR for an input signal. Multiple algorithms available. + % % Biafra Ahanonu % started: 2013.11.04 [11:54:09] - % inputs - % inputSignals: [nSignals frame] matrix - % outputs - % inputSnr: [1 nSignals] vector of calculated SNR. NaN used where SNR is not calculated. - % inputMse: [1 nSignals] vector of MSE. NaN used where MSE is not calculated. + % + % Inputs + % inputSignals: [nSignals frame] matrix + % + % Outputs + % inputSnr: [1 nSignals] vector of calculated SNR. NaN used where SNR is not calculated. + % inputMse: [1 nSignals] vector of MSE. NaN used where MSE is not calculated. + % % options - % % type of SNR to calculate - % options.SNRtype = 'mean(signal)/std(noise)'; - % % frames around which to remove the signal for noise estimation - % options.timeSeq = [-10:10]; - % % show the waitbar - % options.waitbarOn = 1; - % % save time if already computed peaks - % options.testpeaks = []; - % options.testpeaksArray = []; + % % type of SNR to calculate + % options.SNRtype = 'mean(signal)/std(noise)'; + % % frames around which to remove the signal for noise estimation + % options.timeSeq = [-10:10]; + % % show the waitbar + % options.waitbarOn = 1; + % % save time if already computed peaks + % options.testpeaks = []; + % options.testpeaksArray = []; + % changelog % 2013.12.08 now uses RMS to calculate the SNR after removing the signal to get an estimated noise trace. % 2018.03.25 - Added iterative method to determine when signal ends. also added mean centering of trace to correct for offset traces causing problems. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.03.14 [01:42:21] - Better comments for options. import ciapkg.api.* % import CIAtah functions in ciapkg package API. @@ -37,8 +45,9 @@ options.waitbarOn = 1; % whether to display output information options.displayOutput = 1; - % save time if already computed peaks + % Matrix: save time if already computed peaks. [nSignals frame] matrix. Binary matrix with 1 = peaks, 0 = non-peaks. options.testpeaks = []; + % Cell array: save time if already computed peaks. {1 nSignals} cell array. Each cell contains [1 nPeaks] vector that stores the frame locations of each peak. options.testpeaksArray = []; % alternative if want to use non-shared peaks options.testpeaksArrayAlt = []; diff --git a/+ciapkg/+video/createMovieFromVector.m b/+ciapkg/+video/createMovieFromVector.m index a082ac7..3f48850 100644 --- a/+ciapkg/+video/createMovieFromVector.m +++ b/+ciapkg/+video/createMovieFromVector.m @@ -11,6 +11,7 @@ % changelog % 2021.05.04 [09:29:19] - Users can now manually change value assigned to center line or signal. % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.08.02 [08:38:38] - Fixed issue where the min 2nd dimension would always be options.windowSize, leading to errors. % TODO % @@ -55,7 +56,7 @@ reverseStr = ''; % amount to downsample the second dimension movieDimY = round(movieDim(1)/options.secondDimDownsample); - + size(vectorMovie) for frameNo = 1:nFrames frameVectorIdx = windowSize+frameNo; @@ -65,6 +66,8 @@ frameVectorIdx(frameVectorIdx>nFrames) = 0; % frameVectorIdx(frameVectorIdx==0) = frameVectorIdx(find(frameVectorIdx,1,'last')); + thisFrame = vectorMovie(:,:,frameNo); + % add each time point in vector to movie for thisFrameVectorNo = 1:length(frameVectorIdx) if frameVectorIdx(thisFrameVectorNo)==0 @@ -73,11 +76,13 @@ relativeStimValue = round(inputVector(frameVectorIdx(thisFrameVectorNo))*movieDimY); end % add relative (to max) value of vector to movie - vectorMovie(1:relativeStimValue,thisFrameVectorNo,frameNo) = options.signalValue; + % vectorMovie(1:relativeStimValue,thisFrameVectorNo,frameNo) = options.signalValue; + thisFrame(1:relativeStimValue,thisFrameVectorNo) = options.signalValue; end % resize vector movie to match movie dimensions given - vectorMovie(:,:,frameNo) = imresize(vectorMovie(:,1:length(frameVectorIdx),frameNo),[movieDimY movieDim(2)],'bilinear'); + % vectorMovie(:,:,frameNo) = imresize(vectorMovie(:,1:length(frameVectorIdx),frameNo),[movieDimY movieDim(2)],'bilinear'); + vectorMovie(:,:,frameNo) = imresize(thisFrame(:,1:length(frameVectorIdx)),[movieDimY movieDim(2)],'bilinear'); vectorMovie(:,round(end/2),frameNo) = options.centerLineValue; reverseStr = cmdWaitbar(frameNo,nFrames,reverseStr,'inputStr','creating matrix: ','waitbarOn',1,'displayEvery',50); diff --git a/+ciapkg/+view/changeFont.m b/+ciapkg/+view/changeFont.m index 7ed24ec..c4f2aeb 100644 --- a/+ciapkg/+view/changeFont.m +++ b/+ciapkg/+view/changeFont.m @@ -14,6 +14,7 @@ % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2021.11.18 [09:00:28] - Updated so can update font size, name, color more independent of one another. % 2022.01.14 [05:53:37] - Updated so doesn't change Axes backgroundcolor when changing font color, only Axes text. + % 2022.03.14 [04:06:10] - Also check for matlab.ui.control.UIControl when conducting font color changes and ignore to not cause errors. % TODO % Add support for changing other font aspects, e.g. figure Font family, command window font, etc. @@ -60,7 +61,7 @@ tmpList = findall(gcf,'-property','FontSize'); rmIdx = zeros([1 length(tmpList)]); for i = 1:length(tmpList) - if strcmp(class(tmpList(i)),'matlab.graphics.axis.Axes')==1 + if any(strcmp(class(tmpList(i)),{'matlab.graphics.axis.Axes','matlab.ui.control.UIControl'})) rmIdx(i) = 1; end end diff --git a/+ciapkg/+view/createGroupColorMaps.m b/+ciapkg/+view/createGroupColorMaps.m index 889117b..6efa474 100644 --- a/+ciapkg/+view/createGroupColorMaps.m +++ b/+ciapkg/+view/createGroupColorMaps.m @@ -10,8 +10,9 @@ % changelog % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.07.20 [14:47:26] - Added fast thresholding option. % TODO - % + % import ciapkg.api.* % import CIAtah functions in ciapkg package API. @@ -20,6 +21,8 @@ options.dilateOutlinesFactor = 0; % Float: threshold for thresholding images, fraction of maximum image value. options.threshold = 0.4; + % Binary: 1 = fast thresholding (vectorized), 0 = normal thresholding + options.fastThresholding = 1; % get options options = getOptions(options,varargin); % display(options) @@ -35,7 +38,7 @@ % inputImages = pcaicaAnalysisOutput.IcaFilters; % Get boundary indices (for outline of cell locations) - [inputImagesThresholded, boundaryIndices] = thresholdImages(inputImages,'binary',0,'threshold',options.threshold,'imageFilter','none','getBoundaryIndex',1,'imageFilterBinary','none'); + [inputImagesThresholded, boundaryIndices] = thresholdImages(inputImages,'binary',0,'threshold',options.threshold,'imageFilter','none','getBoundaryIndex',1,'imageFilterBinary','none','fastThresholding',options.fastThresholding); dilateOutlinesFactor = options.dilateOutlinesFactor; diff --git a/+ciapkg/+view/playMovie.m b/+ciapkg/+view/playMovie.m index 9002186..3d6ad8f 100644 --- a/+ciapkg/+view/playMovie.m +++ b/+ciapkg/+view/playMovie.m @@ -1,12 +1,17 @@ function [exitSignal, ostruct] = playMovie(inputMovie, varargin) + % [exitSignal, ostruct] = playMovie(inputMovie, varargin) + % % Plays a movie that is either a 3D xyt matrix or path to file. Additional inputs to view multiple movies or sync'd signal data and can also save the resulting figure as a movie. + % % Biafra Ahanonu % started 2013.11.09 [10:39:50] % % inputs - % inputMovie - either grayscale or color movie matrix: - % grayscale: [x y t] matrix of x,y height/width and t frames - % RGB: [x y C t] matrix of x,y height/width, t frames, and C color channel (3 as RGB) + % inputMovie - either: + % grayscale: [x y t] matrix of x,y height/width and t frames + % RGB: [x y C t] matrix of x,y height/width, t frames, and C color channel (3 as RGB) + % Path: full file path to a movie containing a [x y t] or [x y C t] matrix. + % Path (MAT-file): full path to a MAT-file with a variable containing a [x y t] or [x y C t] matrix. % options % fps - % extraMovie - extra movie to play, [X Y Z] matrix of X,Y height/width and Z frames @@ -35,7 +40,12 @@ % 2021.07.03 [08:16:32] - Added feature to input line overlays on input movie (e.g. to overlay cell extraction outputs). % 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2021.10.07 [15:05:52] - Update to avoid caxis with rgb movies, making them hard to view. - % 2022.02.24 [10:24:28] - AVI now read(...,'native') is faster. Also add `primaryTrackingPointNback` option to allow a trailing number of points from prior frames to appear. + % 2022.02.24 [10:24:28] - AVI now read(...,'native') is faster. Also add `primaryTrackingPointNback` option to allow a trailing number of points from prior frames to appear. + % 2022.03.14 [02:13:44] - Added support for read from disk (minimal RAM use) matfile reading of a MAT-file with data in a specified variable name. + % 2022.03.14 [04:21:16] - Default background and theme is now black. + % 2022.03.15 [01:33:56] - By default use 'painters' renderer as that can produce smoother rendering than opengl. + % 2022.07.26 [09:43:59] - Sub-sampling now has option to downsample instead of just taking every X pixel, slower but better quality. + % 2022.09.14 [08:55:37] - Add renderer option to allow users to choose painters or opengl. import ciapkg.api.* % import CIAtah functions in ciapkg package API. @@ -43,6 +53,8 @@ % options % Int: figure number to open options.figNo = 42; + % Binary: 1 = do not close figure before loading GUI. 0 = close figure. + options.keepFig = 1; % Int: set the frame rate to display the movie. options.fps = 20; % Int: set the min/max frames per second to display the movie. @@ -51,6 +63,8 @@ options.fpsMin = 1/10; % Int: By what amount to sub-sample the frames in spatial dimensions. 1 = no sub-sampling. >1 = sub-sampling enabled. options.subSampleMovie = 1; + % Str: Type of sub-sampling to apply when user request subSampleMovie>1. 'Downsample' (imresize to downsample movie bi-linearly) or 'subsample' (take every X pixels). + options.subSampleMovieType = 'downsample'; % Matrix: [X Y Z], additional movie to show. X,Y height/width and Z frames. options.extraMovie = []; % 2D matrix: [signals frames] signals related to inputMovie. @@ -118,6 +132,16 @@ options.contrastFixMultiplier = 1e2; % Int: Multiplier to set contrast to a range usable by GUI elements. options.contrastFixMultiplierGUI = 1e5; + % Str: name of variable in MAT-file to use for loading data. + options.matfileVarname = ''; + % Vector: [R G B] vector for color of background for slider. + options.sliderBkgdColor = [1 1 1]*0.3; + % Vector: [R G B] vector for color of text in the menu. + options.menuFontColor = [1 1 1]; + % Vector: [R G B] vector for color of menu background. + options.menuBkgdColor = [0 0 0]+0.2; + % Str: 'painters' or 'opengl' + options.renderer = 'opengl'; % get options options = getOptions(options,varargin); % options @@ -137,35 +161,52 @@ % Obtain movie information and connect to file if user gives a path to a movie. if ischar(inputMovie)==1 inputMovieIsChar = 1; - % inputMovieName = inputMovie; - inputMovieDims = loadMovieList(inputMovie,'inputDatasetName',options.inputDatasetName,'displayInfo',1,'getMovieDims',1,'displayWarnings',0); - inputMovieDims = [inputMovieDims.one inputMovieDims.two inputMovieDims.three]; - options.nFrames = inputMovieDims(3); - nFramesOriginal = options.nFrames; - - readMovieChunks = 1; [movieType, supported, movieTypeSpecific] = ciapkg.io.getMovieFileType(inputMovie); if ~isempty(options.extraMovie) [movieTypeExtra, supported, movieTypeSpecificExtra] = ciapkg.io.getMovieFileType(options.extraMovie); end - % If NWB, change dataset name to NWB default - if strcmp(movieTypeSpecific,'nwb') - options.inputDatasetName = options.defaultNwbDatasetName{1}; - end - if supported==0 disp('Unsupported movie type provided.') return; + else + disp(['Supported movie type provided: ' movieType]) end - % Setup connection to file to reduce I/O for file types that need it. - [~,movieFileID,inputMovieDims] = ciapkg.io.readFrame(inputMovie,1,'inputDatasetName',options.inputDatasetName); + readMovieChunks = 1; - if ~isempty(options.extraMovie) - [~,movieFileIDExtra,inputMovieDimsExtra] = ciapkg.io.readFrame(options.extraMovie,1,'inputDatasetName',options.inputDatasetName); + if strcmp(movieType,'mat')==1 + disp(['Opening connection to MAT-file: ' inputMovie]) + matObj = matfile(inputMovie); + + % If user has not input a variable name, show a list + if isempty(options.matfileVarname) + matfileVarList = who('-file', inputMovie); + [userMatIdx,~] = listdlg('PromptString','Select variable in MAT-file to play.','ListString',matfileVarList); + options.matfileVarname = matfileVarList{userMatIdx}; + end + inputMovieDims = size(matObj,options.matfileVarname); + else + % inputMovieName = inputMovie; + inputMovieDims = loadMovieList(inputMovie,'inputDatasetName',options.inputDatasetName,'displayInfo',1,'getMovieDims',1,'displayWarnings',0); + inputMovieDims = [inputMovieDims.one inputMovieDims.two inputMovieDims.three]; + + % If NWB, change dataset name to NWB default + if strcmp(movieTypeSpecific,'nwb') + options.inputDatasetName = options.defaultNwbDatasetName{1}; + end + + % Setup connection to file to reduce I/O for file types that need it. + [~,movieFileID,~] = ciapkg.io.readFrame(inputMovie,1,'inputDatasetName',options.inputDatasetName); + + if ~isempty(options.extraMovie) + [~,movieFileIDExtra,inputMovieDimsExtra] = ciapkg.io.readFrame(options.extraMovie,1,'inputDatasetName',options.inputDatasetName); + end end + options.nFrames = inputMovieDims(3); + nFramesOriginal = options.nFrames; + else inputMovieIsChar = 0; inputMovieDims = size(inputMovie); @@ -218,13 +259,19 @@ % pass to calling functions, in case you want to exit an upper-level loop exitSignal = 0; - try - close(options.figNo) - catch - end + try + if options.keepFig==0 + close(options.figNo) + end + catch + end fig1 = figure(options.figNo); % fig = figure(42,'KeyPressFcn',@detectKeyPress); + % Set the default renderer + % set(fig1,'Renderer','painters'); % 'opengl' + set(fig1,'Renderer',options.renderer); % 'opengl' + clf set(findobj(gcf,'type','axes'),'hittest','off') @@ -287,9 +334,13 @@ % axHandle = fig1; end - if ischar(inputMovie)==1 + if inputMovieIsChar==1 + if strcmp(movieType,'mat')==1 + [tmpFrame] = matObj.(options.matfileVarname)(:,:,1); + else + [tmpFrame] = ciapkg.io.readFrame(inputMovie,1,'movieFileID',movieFileID,'inputMovieDims',inputMovieDims,'inputDatasetName',options.inputDatasetName); + end % tmpFrame = subfxn_readMovieDisk(inputMovie,1,movieType); - [tmpFrame] = ciapkg.io.readFrame(inputMovie,1,'movieFileID',movieFileID,'inputMovieDims',inputMovieDims,'inputDatasetName',options.inputDatasetName); % tmpFrame = loadMovieList(inputMovie,'inputDatasetName',options.inputDatasetName,'displayInfo',0,'displayDiagnosticInfo',0,'displayWarnings',0,'frameList',1); else if length(size(inputMovie))==4 @@ -305,7 +356,7 @@ imagesc(tmpFrame) axHandle = gca; - mainTitleHandle = title(options.extraTitleText); + % mainTitleHandle = title(options.extraTitleText); % axHandle.Toolbar.Visible = 'off'; box off; if ~isempty(options.extraLinePlot) @@ -329,7 +380,8 @@ sliderStepF = [1/(nFrames*0.1) 0.2]; end frameSlider = uicontrol('style','slider','Units', 'normalized','position',[15 1 80 2]/100,... - 'min',1,'max',nFrames,'Value',1,'SliderStep',sliderStepF,'callback',@frameCallback,'Enable','inactive','ButtonDownFcn',@pauseLoopCallback); + 'min',1,'max',nFrames,'Value',1,'SliderStep',sliderStepF,'callback',@frameCallback,... + 'BackgroundColor',options.sliderBkgdColor,'Enable','inactive','ButtonDownFcn',@pauseLoopCallback); % set(gcf,'WindowButtonDownFcn',@pauseLoopCallback); % addlistener(frameSlider,'Value','PostSet',@pauseLoopCallback); % waitfor(source,'Value') @@ -340,6 +392,12 @@ % TITLE AND COMMANDS % close(fig1);fig1 = figure(42); [supTitleStr, suptitleHandle, conMenu] = subfxn_createShortcutInfo(); + if ~isempty(options.extraTitleText) + % mainTitleHandle = title([supTitleStr 10 options.extraTitleText]); + mainTitleHandle = title([supTitleStr]); + % currTmpTitle = get(suptitleHandle,'String'); + % set(suptitleHandle,'String',[currTmpTitle 10 options.extraTitleText]); + end % ========================================== % SETUP FIGURE STATES @@ -349,7 +407,7 @@ % keydown = 0; % set(fig1,'KeyPressFcn', '1;'); set(fig1,'KeyPressFcn', @detectKeyPress); - set(gcf, 'WindowScrollWheelFcn', @mouseWheelChange); + set(gcf, 'WindowScrollWheelFcn', @mouseWheelChange); set(gcf,'currentch','3'); keyIn = get(gcf,'CurrentCharacter'); set(gcf,'SelectionType','normal'); @@ -395,7 +453,8 @@ % options.extraLinePlotLegend = options.labelLegend; end % use for references to keep contrast stable across frames - firstFrame = squeeze(inputMovie(:,:,1)); + % firstFrame = squeeze(inputMovie(:,:,1)); + maxAdjFactor = 1; if ~isempty(options.movieMinMax) minMovie(1) = double(options.movieMinMax(1)); @@ -409,14 +468,18 @@ extraMovieFrame = inputMovie(find(options.extraMovie)); end else - if ischar(inputMovie)==1 + if inputMovieIsChar==1 % tmpFrame = loadMovieList(inputMovie,'inputDatasetName',options.inputDatasetName,'displayInfo',0,'displayDiagnosticInfo',0,'displayWarnings',0,'frameList',1:100); % inputMovieFrame = subfxn_readMovieDisk(inputMovie,1,movieType); % if ~isempty(options.extraMovie) % extraMovieFrame = subfxn_readMovieDisk(options.extraMovie,1,movieType,1); % end - [inputMovieFrame] = ciapkg.io.readFrame(inputMovie,1,'movieFileID',movieFileID,'inputMovieDims',inputMovieDims,'inputDatasetName',options.inputDatasetName); + if strcmp(movieType,'mat')==1 + [inputMovieFrame] = matObj.(options.matfileVarname)(:,:,1); + else + [inputMovieFrame] = ciapkg.io.readFrame(inputMovie,1,'movieFileID',movieFileID,'inputMovieDims',inputMovieDims,'inputDatasetName',options.inputDatasetName); + end if ~isempty(options.extraMovie) extraMovieFrame = ciapkg.io.readFrame(options.extraMovie,1,'movieFileID',movieFileIDExtra,'inputMovieDims',inputMovieDimsExtra,'inputDatasetName',options.inputDatasetName); end @@ -455,9 +518,11 @@ % guiMinMax % pause contrastSliderLow = uicontrol('style','slider','Units', 'normalized','position',[21 3 37 2]/100,... - 'min',guiMinMax(1),'max',guiMinMax(2),'Value',guiMinMax(1),'SliderStep',sliderStepC,'callback',@contrastCallback,'Enable','on','ButtonDownFcn',@contrastButtonActivateCallback); + 'min',guiMinMax(1),'max',guiMinMax(2),'Value',guiMinMax(1),'SliderStep',sliderStepC,... + 'BackgroundColor',options.sliderBkgdColor,'callback',@contrastCallback,'Enable','on','ButtonDownFcn',@contrastButtonActivateCallback); contrastSliderHigh = uicontrol('style','slider','Units', 'normalized','position',[58 3 37 2]/100,... - 'min',guiMinMax(1),'max',guiMinMax(2),'Value',guiMinMax(2),'SliderStep',sliderStepC,'callback',@contrastCallback,'Enable','on','ButtonDownFcn',@contrastButtonActivateCallback); + 'min',guiMinMax(1),'max',guiMinMax(2),'Value',guiMinMax(2),'SliderStep',sliderStepC,... + 'BackgroundColor',options.sliderBkgdColor,'callback',@contrastCallback,'Enable','on','ButtonDownFcn',@contrastButtonActivateCallback); contrastFrameText = uicontrol('style','edit','Units', 'normalized','position',[1 3 20 2]/100,'FontSize',9,'string',sprintf('Contrast: %0.3f, %0.3f',options.movieMinMax(1), options.movieMinMax(2))); % ================================================= @@ -499,6 +564,10 @@ colorbarSwitchTwo = 1; pauseLoop = 0; + set(gcf,'color',[0 0 0]); + ciapkg.view.changeFont(9,'fontColor','w') + set(gca,'color',[0 0 0]); + try while loopSignal==1 % [figHandle figNo] = openFigure(42, ''); @@ -507,13 +576,13 @@ % set(frameText,'string',['Frame ' num2str(frame) '/' num2str(nFrames)]) % ===================== - if options.runImageJ==1&~ischar(inputMovie) + if options.runImageJ==1&&~ischar(inputMovie) subfxn_imageJ(inputMovie); % Run once else loops without exit options.runImageJ = 0; end if ~isempty(options.extraMovie) - subplotNum = [1]; + subplotNum = 1; end if ~isempty(options.extraLinePlot) subplotNum = [1 3]; @@ -521,9 +590,12 @@ % Display an image from the movie if readMovieChunks==1 - % - % thisFrame = subfxn_readMovieDisk(inputMovie,frame,movieType); - [thisFrame] = ciapkg.io.readFrame(inputMovie,frame,'movieFileID',movieFileID,'inputMovieDims',inputMovieDims,'inputDatasetName',options.inputDatasetName); + if strcmp(movieType,'mat')==1 + [thisFrame] = matObj.(options.matfileVarname)(:,:,frame); + else + % thisFrame = subfxn_readMovieDisk(inputMovie,frame,movieType); + [thisFrame] = ciapkg.io.readFrame(inputMovie,frame,'movieFileID',movieFileID,'inputMovieDims',inputMovieDims,'inputDatasetName',options.inputDatasetName); + end else if length(size(inputMovie))==4 thisFrame = squeeze(inputMovie(:,:,:,frame)); @@ -600,7 +672,19 @@ montageHandle = findobj(axHandle,'Type','image'); if options.subSampleMovie>1 ssm = options.subSampleMovie; - set(montageHandle,'Cdata',thisFrame(1:ssm:end,1:ssm:end),'AlphaData',imAlpha(1:ssm:end,1:ssm:end)); + + % Spatially downsample movie, slower but improved quality + thisFrame2 = thisFrame; + thisFrame2(isnan(thisFrame2)) = 0; + thisFrame2 = imresize(thisFrame,1/ssm,'bilinear'); + % imAlpha2 = imresize(imAlpha,ssm,'bilinear'); + switch options.subSampleMovieType + case 'downsample' + set(montageHandle,'Cdata',thisFrame2,'AlphaData',imAlpha(1:ssm:end,1:ssm:end)); + case 'subsample' + set(montageHandle,'Cdata',thisFrame(1:ssm:end,1:ssm:end),'AlphaData',imAlpha(1:ssm:end,1:ssm:end)); + otherwise + end else if length(size(thisFrame))==3 % set(montageHandle,'Cdata',squeeze(thisFrame(:,:,1)),'AlphaData',imAlpha); @@ -859,11 +943,11 @@ % if strcmp(thisKey,'f'); break; end; % if strcmp(thisKey,'p'); pause; thisKey=[]; end; % end - % [frame pauseLoop] + % [frame pauseLoop] if pauseLoop==0 frame = frame+round(dirChange); - subfxn_updateSliderInfo(); - elseif pauseLoop==1 + subfxn_updateSliderInfo(); + elseif pauseLoop==1 end if frame>nFrames if options.recordMovie~=0 @@ -903,14 +987,14 @@ function detectKeyPress(H,E) keyIn = get(H,'CurrentCharacter'); % drawnow % keyIn - % if double(keyIn)==112&pauseLoop==1 - % pauseLoop = 0; - % end + % if double(keyIn)==112&pauseLoop==1 + % pauseLoop = 0; + % end end function pauseLoopCallback(source,eventdata) % disp([num2str(frame) ' - pause loop']) - % keyIn = get(gcf,'CurrentCharacter'); - % disp(keyIn) + % keyIn = get(gcf,'CurrentCharacter'); + % disp(keyIn) set(frameSlider,'Enable','on') addlistener(frameSlider,'Value','PostSet',@frameCallbackChange); % pauseLoop = 1; @@ -923,8 +1007,8 @@ function pauseLoopCallback(source,eventdata) end function contrastButtonActivateCallback(source,eventdata) % disp([num2str(frame) ' - pause loop']) - % keyIn = get(gcf,'CurrentCharacter'); - % disp(keyIn) + % keyIn = get(gcf,'CurrentCharacter'); + % disp(keyIn) set(contrastSliderLow,'Enable','on') @@ -965,9 +1049,9 @@ function contrastCallback(source,eventdata) addlistener(contrastSliderLow,'Value','PostSet',@blankCallback); addlistener(contrastSliderHigh,'Value','PostSet',@blankCallback); - % set(contrastSliderLow,'Enable','off') - % set(contrastSliderHigh,'Enable','off') - drawnow update; + % set(contrastSliderLow,'Enable','off') + % set(contrastSliderHigh,'Enable','off') + drawnow update; % set(contrastSliderLow,'Enable','inactive') % set(contrastSliderHigh,'Enable','inactive') @@ -978,19 +1062,19 @@ function blankCallback(source,eventdata) function frameCallbackChange(source,eventdata) frame = max(1,round(get(frameSlider,'value'))); set(frameText,'visible','on','string',['Frame ' num2str(frame) '/' num2str(nFrames)]) - end - function mouseWheelChange(hObject, callbackdata, handles) - if callbackdata.VerticalScrollCount > 0 - frame = frame + 1; - dirChange = 1; - elseif callbackdata.VerticalScrollCount < 0 - frame = frame - 1; - dirChange = -1; - end - subfxn_updateSliderInfo(); - end + end + function mouseWheelChange(hObject, callbackdata, handles) + if callbackdata.VerticalScrollCount > 0 + frame = frame + 1; + dirChange = 1; + elseif callbackdata.VerticalScrollCount < 0 + frame = frame - 1; + dirChange = -1; + end + subfxn_updateSliderInfo(); + end function frameCallback(source,eventdata) - originalPauseState = pauseLoop; + originalPauseState = pauseLoop; pauseLoop = 1; frame = max(1,round(get(frameSlider,'value'))); % disp(num2str(frame)) @@ -999,26 +1083,26 @@ function frameCallback(source,eventdata) set(frameText,'visible','on','string',['Frame ' num2str(frame) '/' num2str(nFrames)]) % pauseLoop addlistener(frameSlider,'Value','PostSet',@blankCallback); - set(frameSlider,'Enable','off') - drawnow update; + set(frameSlider,'Enable','off') + drawnow update; set(frameSlider,'Enable','inactive') - pauseLoop = originalPauseState; + pauseLoop = originalPauseState; % breakLoop = 1; % Update the frame line indicator % set(mainFig,'CurrentAxes',signalAxes) % frameLineHandle.XData = [frameNo frameNo]; - end - function subfxn_updateSliderInfo() - if frame<=0 - frame = nFrames; - elseif frame>nFrames - frame = 1; - end - set(frameSlider,'Value',frame) + end + function subfxn_updateSliderInfo() + if frame<=0 + frame = nFrames; + elseif frame>nFrames + frame = 1; + end + set(frameSlider,'Value',frame) set(frameText,'string',['Frame ' num2str(frame) '/' num2str(nFrames)]) - end + end function thisFrame = subfxn_readMovieDisk(inputMoviePath,frameNo,movieTypeT,extraMovieSwitch) % Fast reading of frame from disk, bypass loadMovieList if possible due to overhead. if nargin==4 @@ -1064,26 +1148,26 @@ function subfxn_updateSliderInfo() end end function subfxn_respondUserInput(keyInTmp) - if nargin>0 - keyIn = keyInTmp; - else - keyIn = get(gcf,'CurrentCharacter'); - if isempty(double(keyIn)) - keyIn = '/'; - elseif double(keyIn)~=51 - figure(fig1) - set(gcf,'CurrentCharacter','3'); - pause(1/options.fps); - drawnow - else - % double(keyIn) - end - end - - % inputdlgcol options - AddOpts.Resize='on'; - AddOpts.WindowStyle='normal'; - AddOpts.Interpreter='tex'; + if nargin>0 + keyIn = keyInTmp; + else + keyIn = get(gcf,'CurrentCharacter'); + if isempty(double(keyIn)) + keyIn = '/'; + elseif double(keyIn)~=51 + figure(fig1) + set(gcf,'CurrentCharacter','3'); + pause(1/options.fps); + drawnow + else + % double(keyIn) + end + end + + % inputdlgcol options + AddOpts.Resize='on'; + AddOpts.WindowStyle='normal'; + AddOpts.Interpreter='tex'; mousePressState = get(gcf,'SelectionType'); switch mousePressState @@ -1104,7 +1188,7 @@ function subfxn_respondUserInput(keyInTmp) end % Ignore these commands if input movie is character - if inputMovieIsChar&any(double(keyIn)==[105 100 97 110 99]) + if inputMovieIsChar&&any(double(keyIn)==[105 100 97 110 99]) disp([keyIn ' not valid for movies input as path.']) return; end @@ -1165,12 +1249,12 @@ function subfxn_respondUserInput(keyInTmp) frame = frame+dirChange*round(options.fps); case 112 %'p' %pause % dirChange = 0; - if pauseLoop==1 - pauseLoop = 0; - else - pauseLoop = 1; - end - %ginput(1); + if pauseLoop==1 + pauseLoop = 0; + else + pauseLoop = 1; + end + %ginput(1); % while waitforbuttonpress~=0 % end case 31 % down arrow @@ -1202,7 +1286,7 @@ function subfxn_respondUserInput(keyInTmp) case 106 %'j' %change contrast [usrIdxChoice, ok] = getUserMovieChoice({'Adjust 1st movie contrast','Adjust 2nd movie contrast','optimal dF/F','Copy contrast from 1st->2nd','Copy contrast from 2nd->1st'}); - if ok==0; return; end + if ok==0; return; end if usrIdxChoice==4 maxMovie(2) = maxMovie(1); @@ -1215,7 +1299,7 @@ function subfxn_respondUserInput(keyInTmp) minMovie(1) = -0.01; else [sel, ok] = listdlg('ListString',{'Adjustable contrast GUI','Contrast input dialog'},'ListSize',[300 300]); - if ok==0; return; end + if ok==0; return; end fixMultiplier = options.contrastFixMultiplierGUI; if usrIdxChoice==1 @@ -1295,9 +1379,9 @@ function subfxn_respondUserInput(keyInTmp) case 108 %'l' %label frame if ~isempty(options.labelLegend) [labelID, ok] = listdlg('ListString',options.labelLegend,'PromptString','toggle label(s), can select multiple to switch','ListSize' ,[400 350]); - if ok==0; return; end + if ok==0; return; end [impulseState, ok] = listdlg('ListString',{'continuous','single frame'},'PromptString','continuous state or single frame?','ListSize' ,[400 350]); - if ok==0; return; end + if ok==0; return; end % usrIdxChoice = options.labelLegend{sel}; % labelID = inputdlg('enter label');labelID = labelID{1}; % labelID = num2str(labelID{1}); @@ -1319,18 +1403,18 @@ function subfxn_respondUserInput(keyInTmp) colormap(options.colormapColor); case 100 %'d' %dfof [usrIdxChoice, ok] = getUserMovieChoice({'1st movie','2nd movie'}); - if ok==0; return; end + if ok==0; return; end % make sure selection chosen, else return if ok~=0 switch usrIdxChoice case 1 inputMovie = dfofMovie(inputMovie); - maxMovie(1) = nanmax(inputMovie(:)); - minMovie(1) = nanmin(inputMovie(:)); + maxMovie(1) = max(inputMovie(:),'omitnan'); + minMovie(1) = min(inputMovie(:),'omitnan'); case 2 options.extraMovie = dfofMovie(options.extraMovie); - maxMovie(2) = nanmax(options.extraMovie(:)); - minMovie(2) = nanmin(options.extraMovie(:)); + maxMovie(2) = max(options.extraMovie(:),'omitnan'); + minMovie(2) = min(options.extraMovie(:),'omitnan'); otherwise % nothing end @@ -1345,7 +1429,7 @@ function subfxn_respondUserInput(keyInTmp) nFrames = size(inputMovie,3); case 110 %'n' %normalize [usrIdxChoice , ok] = getUserMovieChoice({'1st movie','2nd movie'}); - if ok==0; return; end + if ok==0; return; end [usrExtraChoice, ok] = getUserMovieChoice({'keep original','duplicate'}); % make sure selection chosen, else return if ok~=0 @@ -1359,8 +1443,8 @@ function subfxn_respondUserInput(keyInTmp) case 1 if usrExtraChoice==2 options.extraMovie = normalizeMovie(inputMovie,'options',ioptions); - maxMovie(2) = nanmax(inputMovie(:)); - minMovie(2) = nanmin(inputMovie(:)); + maxMovie(2) = max(inputMovie(:),'omitnan'); + minMovie(2) = min(inputMovie(:),'omitnan'); else inputMovie = normalizeMovie(inputMovie,'options',ioptions); end @@ -1371,11 +1455,11 @@ function subfxn_respondUserInput(keyInTmp) end end case 99 %'c' %crop - [movieChoice ok] = getUserMovieChoice({'1st movie','2nd movie'}); + [movieChoice, ok] = getUserMovieChoice({'1st movie','2nd movie'}); [cropChoice] = getUserMovieChoice({'NaN border crop','full crop'}); dirChange = 1; - [coords] = getCropCoords(thisFrame) + [coords] = getCropCoords(thisFrame); sp = coords; switch movieChoice case 1 @@ -1420,11 +1504,11 @@ function subfxn_respondUserInput(keyInTmp) end switch movieChoice case 1 - maxMovie(1) = nanmax(inputMovie(:)); - minMovie(1) = nanmin(inputMovie(:)); + maxMovie(1) = max(inputMovie(:),'omitnan'); + minMovie(1) = min(inputMovie(:),'omitnan'); case 2 - maxMovie(2) = nanmax(options.extraMovie(:)); - minMovie(2) = nanmin(options.extraMovie(:)); + maxMovie(2) = max(options.extraMovie(:),'omitnan'); + minMovie(2) = min(options.extraMovie(:),'omitnan'); otherwise end figure(42); @@ -1433,7 +1517,7 @@ function subfxn_respondUserInput(keyInTmp) frameChange = inputdlgcol('goto | Enter frame # to skip to:','',1,{''},AddOpts,2); if ~isempty(frameChange) frameChange = str2num(frameChange{1}); - if frameChange>nFrames|frameChange<1 + if frameChange>nFrames||frameChange<1 % do nothing, invalid command else frame = frameChange; @@ -1463,16 +1547,17 @@ function subfxn_displayShortcuts(src,event) function [supTitleStr, suptitleHandle, conMenu] = subfxn_createShortcutInfo() titleSep = '\hspace{1em}'; sepVar = ['}' 10 '\texttt{']; - if isempty(options.extraTitleText)|1 + % if isempty(options.extraTitleText)||1 + if isempty(options.extraTitleText) options.extraTitleText = ''; else - options.extraTitleText = strrep(options.extraTitleText,'\','\char`\\'); - options.extraTitleText = strrep(options.extraTitleText,'#','\char`\#'); + options.extraTitleText = strrep(options.extraTitleText,'\','\char`\\'); + options.extraTitleText = strrep(options.extraTitleText,'#','\char`\#'); options.extraTitleText = [10 '\texttt{' options.extraTitleText '}']; end % S.fh = figure; - shortcutMenuHandle = uicontrol('style','pushbutton','Units','normalized','position',[0 97 50 3]/100,'FontSize',9,'string','Shortcuts menu (or right-click GUI)','callback',@subfxn_displayShortcuts); + shortcutMenuHandle = uicontrol('style','pushbutton','Units','normalized','position',[0 97 20 3]/100,'FontSize',9,'string','Shortcuts menu (or right-click GUI)','BackgroundColor',options.menuBkgdColor,'ForegroundColor',options.menuFontColor,'HorizontalAlignment','left','callback',@subfxn_displayShortcuts); sepMenuLins = {'','',''}; menuListInfo = {... @@ -1507,9 +1592,9 @@ function subfxn_displayShortcuts(src,event) mitemAll = {}; for mNo = 1:length(menuListInfo) if isempty([menuListInfo{mNo}{1}]) - labelStr = ['']; + labelStr = ['']; else - labelStr = ['
' menuListInfo{mNo}{1} ':   ' menuListInfo{mNo}{3} '
']; + labelStr = ['
' menuListInfo{mNo}{1} ':   ' menuListInfo{mNo}{3} '
']; end mitemAll{mNo} = uimenu(conMenu,'label',labelStr,'MenuSelectedFcn',@(src,evnt) subfxn_respondUserInput(menuListInfo{mNo}{2})); end @@ -1555,9 +1640,12 @@ function subfxn_displayShortcuts(src,event) end disp(strrep(supTitleStr,titleSep, 10)) % suptitleHandle = ciapkg.overloaded.suptitle(strrep(strrep('/','\',supTitleStr),'_','\_')); - suptitleHandle = ciapkg.overloaded.suptitle(supTitleStr,'titleypos',0.93); - set(suptitleHandle,'FontName',get(0,'DefaultAxesFontName')); - set(suptitleHandle,'FontSize',12,'FontWeight','normal') + + suptitleHandle = []; + + % suptitleHandle = ciapkg.overloaded.suptitle(supTitleStr,'titleypos',0.93); + % set(suptitleHandle,'FontName',get(0,'DefaultAxesFontName')); + % set(suptitleHandle,'FontSize',12,'FontWeight','normal') hold off; % imcontrast end diff --git a/+ciapkg/VERSION b/+ciapkg/VERSION index b78bccc..9c36fd2 100644 --- a/+ciapkg/VERSION +++ b/+ciapkg/VERSION @@ -1,2 +1,2 @@ -v4.4.2 -2022.06.27 [13:51:31] \ No newline at end of file +v4.5.7 +2022.09.14 [08:55:22] \ No newline at end of file diff --git a/+ciapkg/exampleFxn.m b/+ciapkg/exampleFxn.m index f0305b7..0984c2a 100644 --- a/+ciapkg/exampleFxn.m +++ b/+ciapkg/exampleFxn.m @@ -1,20 +1,25 @@ function [output1,output2] = exampleFxn(input1,input2,varargin) - % EXAMPLEFXN(input1,input2,varargin) + % [output1,output2] = EXAMPLEFXN(input1,input2,varargin) % % DESCRIPTION. % % Biafra Ahanonu % started: INSERT_DATE % - % inputs + % Inputs % input1 % input2 - % outputs + % + % Outputs % output1 % output2 + % + % Options (input as Name-Value with Name = options.(Name)) + % % DESCRIPTION + % options.exampleOption = ''; - % changelog - % + % Changelog + % 2022.03.14 [01:47:04] - Added nested and local functions to the example function. % TODO % @@ -23,7 +28,7 @@ options.exampleOption = ''; % get options options = ciapkg.io.getOptions(options,varargin); - % display(options) + % disp(options) % unpack options into current workspace % fn=fieldnames(options); % for i=1:length(fn) @@ -38,7 +43,16 @@ disp(getReport(err,'extended','hyperlinks','on')); disp(repmat('@',1,7)) end + + function [outputs] = nestedfxn_exampleFxn(arg) + % Always start nested functions with "nestedfxn_" prefix. + % outputs = ; + end end +function [outputs] = localfxn_exampleFxn(arg) + % Always start local functions with "localfxn_" prefix. + % outputs = ; +end % CIAtah method function obj = functionName(obj,varargin) diff --git a/+ciapkg/loadBatchFxns.m b/+ciapkg/loadBatchFxns.m index 371a948..cca59f8 100644 --- a/+ciapkg/loadBatchFxns.m +++ b/+ciapkg/loadBatchFxns.m @@ -20,7 +20,7 @@ function loadBatchFxns(varargin) % 2020.05.09 [16:40:13] - Updates to remove additional specific repositories that should not be loaded by default. Add support for overriding this feature. % 2020.06.05 [23:35:43] - If user doesn't have Parallel Toolbox, still works % 2020.07.21 [14:11:42] - Fix to make sure all sub-folders (not just the root) are also removed in the case of certain external_programs. - % 2021.02.01 [??‎15:19:40] - Update `_external_programs` to call ciapkg.getDirExternalPrograms() to standardize call across all functions. + % 2021.02.01 [15:19:40] - Update `_external_programs` to call ciapkg.getDirExternalPrograms() to standardize call across all functions. % 2021.06.20 [00:22:38] - Added manageMiji('startStop','closeAllWindows'); support. % 2021.07.16 [13:38:55] - Remove redundant loading and unloading of external programs via additional checks. % 2021.07.22 [19:51:50] - Moved loadBatchFxns into ciapkg package. Use ciapkg.getDir to get directory as standard IO. @@ -28,11 +28,47 @@ function loadBatchFxns(varargin) % 2021.08.09 [12:06:32] - Do not add docs and data folders or sub-folders to the path. % 2021.08.24 [13:46:13] - Update to fullfile call using filesep to make platform neutral. % 2021.11.09 [19:14:49] - Improved handling of external programs both in adding and removing from path. Additional support for removing specific packages that are not always needed. + % 2022.03.04 [07:03:22] - Added patchwarp to list of excluded by default folders for external programs. + % 2022.03.08 [15:17:16] - Directly allow use of ciapkg.io.getOptions since it is not dependent on loadBatchFxns due to being inside a package. Also allow option for user to specify which special packages that are not normally loaded to be loaded rather than loading everything if they want a specific package loaded. + % 2022.07.11 [14:22:30] - Speed up removal of directories not needed by default by directly locating the root path of that algorithm rather than searching for the M-file within the entire external programs directory, saves time. Users must have downloaded dependencies using loadDependencies. + % 2022.07.14 [20:19:47] - Add SlideBook JAR to the path for Bio-Formats support. % TODO % import ciapkg.api.* % import CIAtah functions in ciapkg package API. + % ======================== + % Cell array of strings: M-files pointing to root directories that should be excluded from loading. + options.removeDirFxnToFind = {... + 'extractor.m'; + 'tenrandblk.m'; + 'patchwarp.m'; + }; + % 'runCELLMax.m'; + options.removeDirFxnToFindDirs = {... + 'extract'; + 'tensor_toolbox'; + 'patchwarp'; + }; + % 'cellmax'; + % 'normcorre.m'; + % Cell array of strings: M-files to overload options.removeDirFxnToFind. + options.removeDirFxnToFindExclude = {}; + % get options + options = ciapkg.io.getOptions(options,varargin); + % display(options) + % unpack options into current workspace + % fn=fieldnames(options); + % for i=1:length(fn) + % eval([fn{i} '=options.' fn{i} ';']); + % end + % ======================== + + % Backwards compatibility for prior functions that just call loadBatchFxns with single input argument. + if iscell(varargin)&&length(varargin)>1 + varargin = ''; + end + % Disable the handle graphics warning "The DrawMode property will be removed in a future release. Use the SortMethod property instead." from being displayed. Comment out this line for debugging purposes as needed. warning('off','MATLAB:hg:WillBeRemovedReplaceWith') @@ -40,7 +76,12 @@ function loadBatchFxns(varargin) externalProgramsDir = ciapkg.getDirExternalPrograms(); % List of M-files where if found, that programs directory should by default be removed from the path. - removeDirFxnToFind = {'runCELLMax.m','extractor.m','normcorre.m','tenrandblk.m'}; + removeDirFxnToFind = options.removeDirFxnToFind; + removeDirFxnToFindDirs = options.removeDirFxnToFindDirs; + if ~isempty(options.removeDirFxnToFindExclude) + [removeDirFxnToFind, idx1] = setdiff(removeDirFxnToFind,options.removeDirFxnToFindExclude); + removeDirFxnToFindDirs = removeDirFxnToFindDirs(idx1); + end % 'cellmax.runCELLMax', 'CELLMax_Wrapper.m' % Add calciumImagingAnalysis directory and subdirectories to path, use dbstack to ensure only add in the root directory regardless of where user has current MATLAB folder. @@ -73,30 +114,30 @@ function loadBatchFxns(varargin) % pathListArray = subfxnRemoveDirs(0,pathListArray); pathListArrayOriginal = pathListArray; - + % ================================================= % Remove paths that are already in MATLAB path to save time pathFilter = cellfun(@isempty,pathListArray); pathListArray = pathListArray(~pathFilter); pathFilter = ismember(pathListArray,strsplit(path,pathsep)); pathListArray = pathListArray(~pathFilter); - + % Remove 'docs' and 'data', don't need to be in the path. matchIdxD = contains(pathListArray,[functionDir filesep 'docs']); pathListArray = pathListArray(~matchIdxD); matchIdxD = contains(pathListArray,[functionDir filesep 'data']); pathListArray = pathListArray(~matchIdxD); - + matchIdxD = contains(pathListArray,[externalProgramsDir filesep '_downloads']); - pathListArray = pathListArray(~matchIdxD); + pathListArray = pathListArray(~matchIdxD); % If going to remove directories in removeDirFxnToFind then do so now to prevent redundant calls to addpath, etc. findListFlag = []; if isempty(varargin) [pathListArray,findListFlag] = subfxnRemoveDirs(0,pathListArray); end - + if strcmp(varargin,'excludeExternalPrograms') disp(['Excluding ' externalProgramsDir ' from PATH adding.']) matchIdx2 = contains(pathListArray,externalProgramsDir); @@ -104,8 +145,16 @@ function loadBatchFxns(varargin) end % ================================================= - % Add paths as needed and remove any paths that should not be present. + % Add SlideBook reader to the path + slideBookPath = fullfile(ciapkg.getDirExternalPrograms(),'bfmatlab_readers','SlideBook6Reader.jar'); + if isfile(slideBookPath)==1 + disp(['Adding to JAVA path: ' slideBookPath]) + javaaddpath(slideBookPath); + end + % ================================================= + % Add paths as needed and remove any paths that should not be present. + skipRemovePath = 0; if isempty(pathListArray)&isempty(varargin) disp('Folders still need to be removed.') @@ -253,8 +302,26 @@ function loadBatchFxns(varargin) pathToRmCell = {}; findListFlag = []; matchIdxAll = []; + if rmPathFlag==0 + % extDir = dir([functionDir filesep externalProgramsDir]); + extDir = dir([externalProgramsDir]); + extDir = extDir([extDir.isdir]); + if length(extDir)<3 + disp('No external programs!') + return; + end + extDir = extDir(3:end); + end + + if rmPathFlag==1 + pathFull = strsplit(path,pathsep); + end + for iNo = 1:length(fxnRootFolder) thisFxn = fxnRootFolder{iNo}; + thisFxnFolder = removeDirFxnToFindDirs{iNo}; + + mfileLocFlag = 0; fileLoc = which(thisFxn); if isempty(fileLoc) @@ -263,28 +330,28 @@ function loadBatchFxns(varargin) findListFlag(iNo) = 1; end if rmPathFlag==1 - [pathToRm,~,~] = fileparts(fileLoc); + % [pathToRm,~,~] = fileparts(fileLoc); + pathToRm = fullfile(externalProgramsDir,thisFxnFolder); else - % extDir = dir([functionDir filesep externalProgramsDir]); - extDir = dir([externalProgramsDir]); - extDir = extDir([extDir.isdir]); - if length(extDir)<3 - disp('No external programs!') - return; - end - extDir = extDir(3:end); - foundFiles = dir(fullfile([externalProgramsDir], ['**' filesep thisFxn ''])); - if isempty(foundFiles) - pathToRm = []; - else - pathToRm = foundFiles.folder; + pathToRm = fullfile(externalProgramsDir,thisFxnFolder); + + if ~isdir(pathToRm) + foundFiles = dir(fullfile([externalProgramsDir], ['**' filesep thisFxn ''])); + if isempty(foundFiles) + pathToRm = []; + else + pathToRm = foundFiles.folder; + end + mfileLocFlag = 1; end end if ~isempty(pathToRm) - % extractor now in sub-directory - if strcmp(thisFxn,'extractor.m') - [pathToRm,~,~] = fileparts(pathToRm); + if mfileLocFlag==1 + % extractor now in sub-directory + if strcmp(thisFxn,'extractor.m') + [pathToRm,~,~] = fileparts(pathToRm); + end end if strcmp(thisFxn,'CELLMax_Wrapper.m')|strcmp(thisFxn,'cellmax.runCELLMax') @@ -294,20 +361,33 @@ function loadBatchFxns(varargin) else thisFxnStr = thisFxn; end + matchIdx = contains(pathListArray,pathToRm); - if rmPathFlag==1&any(matchIdx)==1 - % if rmPathFlag==1 - fprintf('Removing unneeded directory from path: %s.\n',thisFxnStr); - % pathToRmCell{end+1} = pathToRm; - pathToRmCell = [pathToRmCell{:} pathListArray(matchIdx)]; + if rmPathFlag==1 + matchIdx = matchIdx&contains(pathListArray,pathToRm); + + if any(matchIdx)==1 + fprintf('Removing unneeded directory from path: %s.\n',thisFxnStr); + % pathToRmCell{end+1} = pathToRm; + pathToRmCell = [pathToRmCell{:} pathListArray(matchIdx)]; + end + + matchIdx_pathFull = contains(pathFull,pathToRm); + if any(matchIdx_pathFull)==1 + pathToRmCell = [pathToRmCell{:} pathFull(matchIdx_pathFull)]; + end + pathToRmCell = unique(pathToRmCell); + + filterIdx22 = ismember(pathToRmCell,pathFull); + pathToRmCell = pathToRmCell(filterIdx22); % rmpath(pathToRm); elseif rmPathFlag==0 fprintf('Removing unneeded directory from "to add" path list: %s.\n',thisFxnStr); % pathListArray = pathListArray(~matchIdx); if isempty(matchIdxAll) - matchIdxAll = ~matchIdx; + matchIdxAll = matchIdx; elseif ~isempty(matchIdx) - matchIdxAll = matchIdxAll|~matchIdx; + matchIdxAll = matchIdxAll|matchIdx; end end % pathToRm @@ -318,11 +398,15 @@ function loadBatchFxns(varargin) end if rmPathFlag==1 + % pathToRmCell' % Only remove path if user requests - rmpath(strjoin(pathToRmCell,pathsep)); + if isempty(pathToRmCell) + else + rmpath(strjoin(pathToRmCell,pathsep)); + end elseif rmPathFlag==0&~isempty(matchIdxAll) % Remove from list of folders to add to path - pathListArray = pathListArray(matchIdxAll); + pathListArray = pathListArray(~matchIdxAll); end catch err disp(repmat('@',1,7)) diff --git a/@ciatah/ciatahMainGui.m b/@ciatah/ciatahMainGui.m index 811bebc..a570a87 100644 --- a/@ciatah/ciatahMainGui.m +++ b/@ciatah/ciatahMainGui.m @@ -24,6 +24,13 @@ % 2022.01.04 [12:15:56] - Additional check only for movie previews of supported movie files. "Folder files" list selection additional regexp support. % 2022.01.25 [19:30:58] - Changed so that 'Start selected method' button selects the correct folders like occurs when pressing enter. % 2022.02.25 [13:06:09] - The folder list will no longer reset when selecting certain GUI elements that should not impact folder list. + % 2022.03.14 [13:31:02] - Improve speed of movie display by better passing of movie information to ciapkg.io.readFrame. + % 2022.03.15 [01:14:23] - If processed and raw movie are the same length, sliders move in sync. By default use 'painters' renderer as that can produce smoother rendering than opengl. + % 2022.07.27 [13:49:59] - Added file sizes to the folder files preview. Reset movie dimension on folder select change so movie previews run without outputting initial error (movies would still preview, users just might be confused by the warning). + % 2022.07.27 [14:23:56] - Feature to allow preview of HDF5 datasets. Expanding to Bio-Formats and other types of data with internal structures for data. + % 2022.07.28 [04:48:16] - Added ability to open folder in OS GUI, e.g. Windows Explorer. + % 2022.07.30 [10:54:53] - If manual input for filtering folders set back to no filter to avoid GUI loops. + % 2022.09.14 [09:51:55] - Make sure preview data and open folder buttons are associated with correct previewDataHandle and openFoldersHandle handles. % TODO % @@ -54,6 +61,7 @@ colorStruct = struct; colorStruct.red = [255 153 153]/255; colorStruct.yellow = [255, 255, 153]/255; + colorStruct.gray = [153, 153, 153]/255; defaultFontSize = obj.fontSizeGui; @@ -94,6 +102,17 @@ selectList = obj.inputFolders(:); end + % Dimensions for each movie + inputMovieDims1 = []; + inputMovieDims2 = []; + + % Folder files cell array + currentFolder = ''; + currentFolderFilesList = {}; + + % 1 = sliders are clicked, 0 = not clicked. + sliderLockState = 0; + %% ========================================== % SETUP FIGURE useAltValid = {'no additional filter','manually sorted folders','not manually sorted folders','manual classification already in obj',['has ' obj.signalExtractionMethod ' extracted cells'],['missing ' obj.signalExtractionMethod ' extracted cells'],'fileFilterRegexp','valid auto',['has ' obj.fileFilterRegexp ' movie file'],'manual index entry'}; @@ -110,6 +129,10 @@ uicontrol('Style','text','String',[ciapkg.pkgName],'Units','normalized','Position',[1 96 20 3]/100,'BackgroundColor',figBackgroundColor,'HorizontalAlignment','Left','ForegroundColor',figTextColor,'FontWeight','bold','FontAngle','italic','FontSize',defaultFontSize*fontScale); uicontrol('Style','text','String',[inputTxt ' Press TAB to select next section, ENTER to continue, and ESC to exit.'],'Units','normalized','Position',[10 96 90 3]/100,'BackgroundColor',figBackgroundColor,'HorizontalAlignment','Left','ForegroundColor',figTextColor,'FontSize',9*fontScale); + % Set the default renderer + % set(hFig,'Renderer','painters'); % 'opengl' + set(hFig,'Renderer','opengl'); % 'opengl' + %% ========================================== % SETUP MAIN GUI ELEMENTS @@ -152,9 +175,14 @@ % [x0 y0 width height] addFoldersLoc = hListboxT.folders.Position; - addFoldersLoc(3) = 0.30; - addFoldersLoc(1) = 0.69; - addFoldersHandle = uicontrol('style','pushbutton','Units', 'normalized','position',addFoldersLoc,'FontSize',9*fontScale,'string','Click to add folders.','BackgroundColor',[153 153 153]/255,'callback',@callback_addFolders); + addFoldersLoc(3) = 0.15; + addFoldersLoc(1) = 0.63; + addFoldersHandle = uicontrol('style','pushbutton','Units', 'normalized','position',addFoldersLoc,'FontSize',9*fontScale,'string','Add folders.','BackgroundColor',[153 153 153]/255,'callback',@callback_addFolders); + + loadFoldersLoc = hListboxT.folders.Position; + loadFoldersLoc(3) = 0.20; + loadFoldersLoc(1) = 0.79; + loadFoldersHandle = uicontrol('style','pushbutton','Units', 'normalized','position',loadFoldersLoc,'FontSize',9*fontScale,'string','Load cell extraction.','BackgroundColor',[153 153 153]/255,'callback',@callback_loadFolders); %% ========================================== @@ -261,7 +289,7 @@ subfxn_setOutputVars() close(hFig) end - validFoldersIdx + disp(validFoldersIdx) return; % idNumIdxArray = get(hListboxS.folders,'Value'); catch err @@ -407,10 +435,22 @@ function onKeyPressRelease(src, evnt, pressRelease,hFig) [validFoldersIdx] = pipelineFolderFilter(obj,useAltValid,validFoldersIdx); + % If manual input set back to no filter to avoid GUI loops + if strcmp(useAltValid,'manual index entry')==1 + set(hListboxS.folderFilt,'Value',1) + end + % validFoldersIdx = hListboxStruct.ValueFolder; % if strcmp(get(src,'Tag'),'folders')~=1 + % If folder is changed, reset the movie dimensions to avoid errors. + if any(strcmp({'folders'},get(src,'Tag')))==1 + % Dimensions for each movie + inputMovieDims1 = []; + inputMovieDims2 = []; + end + if any(strcmp({'methodBox','cellExtractionBox','cellExtractFiletypeBox','guiEnabled','folders'},get(src,'Tag')))==0 if length(get(hListboxS.folders,'Value'))==1 else @@ -539,11 +579,17 @@ function exitCallback(source,eventdata) % uiresume(hFig); end function subfxn_setOutputVars() - hListboxStruct.ValueFolder = get(hListboxS.folders,'Value'); - hListboxStruct.Value = hListbox.Value; - hListboxStruct.guiIdx = get(hListboxS.guiEnabled,'Value'); - hListboxStruct.nwbLoadFiles = get(hListboxS.cellExtractFiletype,'Value'); - if hListboxStruct.nwbLoadFiles==1;hListboxStruct.nwbLoadFiles=0;else;hListboxStruct.nwbLoadFiles=1;end + if isvalid(hFig) + hListboxStruct.ValueFolder = get(hListboxS.folders,'Value'); + hListboxStruct.Value = hListbox.Value; + hListboxStruct.guiIdx = get(hListboxS.guiEnabled,'Value'); + hListboxStruct.nwbLoadFiles = get(hListboxS.cellExtractFiletype,'Value'); + if hListboxStruct.nwbLoadFiles==1 + hListboxStruct.nwbLoadFiles=0; + else + hListboxStruct.nwbLoadFiles=1; + end + end end function startMethodCallback(source,eventdata) breakMovieLoop = 1; @@ -575,10 +621,68 @@ function runMoviesToggleCallback(source,eventdata) elseif enableMoviePreview==1 returnStr = 'Preview movies by selecting one folder in "Loaded folders" (click to disable).'; end + end + function subfxn_previewDataInfo(source,eventdata) + disp('Listing out internal data for select file types.') + % folderIdx = get(hListboxS.folders,'Value'); + currentFolder + selectFiles = get(hListboxS.folderFiles,'Value'); + % folderFilesList = get(hListboxS.folderFiles,'string'); + folderFilesList = currentFolderFilesList(selectFiles); + % cellfun(@disp,folderFilesList) + for iz3 = 1:length(folderFilesList) + disp(repmat('==',1,7)) + thisFullFile = fullfile(currentFolder,folderFilesList{iz3}); + if isfile(thisFullFile)~=1 + continue; + end + disp(folderFilesList{iz3}) + disp(thisFullFile) + [~,~,extH] = fileparts(thisFullFile); + + [movieTypeTmp, supported] = ciapkg.io.getMovieFileType(thisFullFile); + + switch movieTypeTmp + case 'tiff' + case 'hdf5' + % Display only HDF5 dataset names, no additional metadata. + h5disp(thisFullFile,'/','min') + case 'avi' + case 'isxd' + case 'bioformats' + % Just display series of names in the file + [bfSeriesNo] = ciapkg.bf.dispFileSeries(thisFullFile); + otherwise + end + end + disp('Done!') + end + function subfxn_openFolderGui(source,eventdata) + if ~isempty(currentFolder) + if (ispc) + winopen(currentFolder) + elseif ismac + cmdRun = ['open ' currentFolder]; + [status1, cmdout1] = system(cmdRun); %#ok + else + % xdg-open opens folder in Linux, if no xdg-open, display an error. + cmdRun = ['xdg-open ' currentFolder]; + [status1, cmdout1] = system(cmdRun); %#ok + end + end end function callback_addFolders(source,eventdata) % Set current method to modelAddNewFolders and start method. - hListbox.Value = 2; + hListbox.Value = find(strcmp('modelAddNewFolders',obj.methodsList)); + obj.currentMethod = hListbox.String{hListbox.Value}; + % breakMovieLoop = 1; + % exitFlag = 1; + % close(hFig) + subfxn_returnNormal(); + end + function callback_loadFolders(source,eventdata) + % Set current method to modelAddNewFolders and start method. + hListbox.Value = find(strcmp('modelVarsFromFiles',obj.methodsList),1); obj.currentMethod = hListbox.String{hListbox.Value}; % breakMovieLoop = 1; % exitFlag = 1; @@ -641,9 +745,11 @@ function pauseLoopCallback(source,eventdata) switch switchTag case 'First' set(frameSlider,'Enable','on'); + sliderLockState = 1; % addlistener(frameSlider,'Value','PostSet',@frameCallbackChange); case 'Second' set(frameSliderTwo,'Enable','on'); + sliderLockState = 2; % addlistener(frameSliderTwo,'Value','PostSet',@frameCallbackChange); otherwise return; @@ -672,19 +778,37 @@ function frameCallback(source,eventdata) originalPauseState = breakMovieLoop; breakMovieLoop = 1; switchTag = get(source,'Tag'); + switch switchTag case 'First' i = max(1,round(get(frameSlider,'value'))); + % If movies are equal length, keep both sliders in sync. + if length(inputMovieDims1)==3&&length(inputMovieDims2)==3 + if inputMovieDims1(3)==inputMovieDims2(3) + i2 = i; + end + end case 'Second' i2 = max(1,round(get(frameSliderTwo,'value'))); + % If movies are equal length, keep both sliders in sync. + if length(inputMovieDims1)==3&&length(inputMovieDims2)==3 + if inputMovieDims1(3)==inputMovieDims2(3) + i = i2; + end + end otherwise return; end addlistener(frameSlider,'Value','PostSet',@blankCallback); set(frameSlider,'Enable','off') + set(frameSliderTwo,'Enable','off') drawnow update; set(frameSlider,'Enable','inactive') + set(frameSliderTwo,'Enable','inactive') breakMovieLoop = originalPauseState; + sliderLockState = 0; + end + function blankCallback(source,eventdata) end function [selBoxInfo] = subfxn_guiElementSetupInfo() % set(hFig,'Color',[0,0,0]); @@ -764,7 +888,11 @@ function frameCallback(source,eventdata) bOff = 1; fontSizeH = 11*fontScale; - runMoviesToggleHandle = uicontrol('style','pushbutton','Units', 'normalized','position',[bOff 45 98 2]/100,'FontSize',fontSizeH,'string',subfxn_previewMovieState(),'BackgroundColor',buttonBackgroundColor,'callback',@runMoviesToggleCallback); + runMoviesToggleHandle = uicontrol('style','pushbutton','Units', 'normalized','position',[bOff 45 50 2]/100,'FontSize',fontSizeH,'string',subfxn_previewMovieState(),'BackgroundColor',buttonBackgroundColor,'callback',@runMoviesToggleCallback); + + previewDataHandle = uicontrol('style','pushbutton','Units', 'normalized','position',[bOff+50 45 24 2]/100,'FontSize',fontSizeH,'string','Preview file internal structure (HDF5, BioFormats)','BackgroundColor',colorStruct.gray,'callback',@subfxn_previewDataInfo); + + openFoldersHandle = uicontrol('style','pushbutton','Units', 'normalized','position',[bOff+50+24 45 24 2]/100,'FontSize',fontSizeH,'string','Open folder OS gui','BackgroundColor',colorStruct.gray,'callback',@subfxn_openFolderGui); fileFilterRegexpHandle = uicontrol('style','edit','Units', 'normalized','position',[bOff dy txtW 2]/100,'FontSize',fontSizeH,'string',obj.fileFilterRegexp,'callback',@movieSettingsCallback,'KeyReleaseFcn',@movieSettingsCallback); uicontrol('Style','text','String','Processed movie regular expression:','Units','normalized','Position',[bOff dy+dz txtW dz]/100,'BackgroundColor',figBackgroundColor,'ForegroundColor',figTextColor,'HorizontalAlignment','Left','FontWeight','normal','FontSize',fontSizeH); @@ -790,12 +918,32 @@ function movieCallback(source,eventdata) end thisFolderPath = obj.inputFolders{folderIdx}; - + currentFolder = thisFolderPath; % Update file list try folderFileListHere = ciapkg.api.getFileList(thisFolderPath,'.','addInputDirToPath',0); folderFileListHere = folderFileListHere(3:end); + currentFolderFilesList = folderFileListHere; + for iz2 = 1:length(folderFileListHere) + thisFileTmp = folderFileListHere{iz2}; + thisFileFullPath = fullfile(thisFolderPath,thisFileTmp); + if isfile(thisFileFullPath)==1 + fileInfo2 = dir(thisFileFullPath); + fileSize = fileInfo2.bytes*9.53674e-7; + % Show in GB or Mb + if fileSize>2^10 + thisFileTmp = sprintf('%s (%0.2f GB)',thisFileTmp,round(fileSize/2^10,2)); + else + thisFileTmp = sprintf('%s (%0.2f MB)',thisFileTmp,round(fileSize,2)); + end + elseif isfolder(thisFileFullPath)==1 + thisFileTmp = ['->' thisFileTmp]; + end + folderFileListHere{iz2} = thisFileTmp; + end set(hListboxS.folderFiles,'string',folderFileListHere); + + % Update selected files set(hListboxS.folderFiles,'Value',1); folderFilesHighlight = cellfun(@(x) ~isempty(cell2mat(regexp(x,{obj.fileFilterRegexp,obj.fileFilterRegexpRaw}))),folderFileListHere,'UniformOutput',0); folderFilesHighlight = cell2mat(folderFilesHighlight); @@ -996,8 +1144,16 @@ function movieCallback(source,eventdata) while breakMovieLoop==0 if movieCheck{1}==1 if isvalid(movieImgHandle) + try + if inputMovieDims1(3)==inputMovieDims2(3)&&sliderLockState==2 + set(frameSlider,'Value',i2); + end + catch + + end + i = round(get(frameSlider,'Value')); - [thisFrame,~,~] = ciapkg.io.readFrame(inputMoviePath{1},i,'inputDatasetName',obj.inputDatasetName); + [thisFrame,~,inputMovieDims1] = ciapkg.io.readFrame(inputMoviePath{1},i,'inputDatasetName',obj.inputDatasetName,'inputMovieDims',inputMovieDims1); if ~isempty(boundaryIndices)&opts.overlayCellExtraction==1 thisFrame([boundaryIndices{:}]) = NaN; @@ -1016,8 +1172,16 @@ function movieCallback(source,eventdata) if movieCheck{2}==1 if isvalid(movieImgHandleTwo) + try + if inputMovieDims1(3)==inputMovieDims2(3)&&sliderLockState==1 + set(frameSliderTwo,'Value',i); + end + catch + + end + i2 = round(get(frameSliderTwo,'Value')); - [thisFrame2,~,~] = ciapkg.io.readFrame(inputMoviePath{2},i2,'inputDatasetName',obj.inputDatasetName); + [thisFrame2,~,inputMovieDims2] = ciapkg.io.readFrame(inputMoviePath{2},i2,'inputDatasetName',obj.inputDatasetName,'inputMovieDims',inputMovieDims2); if ~isempty(boundaryIndices)&opts.overlayCellExtraction==1 thisFrame2([boundaryIndices{:}]) = NaN; @@ -1041,6 +1205,8 @@ function movieCallback(source,eventdata) if breakMovieLoop==1 break; end + % drawnow + drawnow limitrate pause(1/FPShere); end warning on; diff --git a/@ciatah/getRegistrationSettings.m b/@ciatah/getRegistrationSettings.m index a8beb3e..4ff0e7c 100644 --- a/@ciatah/getRegistrationSettings.m +++ b/@ciatah/getRegistrationSettings.m @@ -26,6 +26,8 @@ % 2021.07.22 [12:10:10] - Added movie detrend options. % 2021.08.10 [09:57:36] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2021.12.01 [19:21:00] - Handle "User selects specific value" going into infinite loop. Allow cancel option to prevent loop. + % 2022.06.28 [15:28:13] - Add additional black background support. + % 2022.07.10 [17:21:21] - Added largeMovieLoad setting. % TODO % DONE: Allow user to input prior settings, indicate those changed from default by orange or similar color. @@ -110,6 +112,10 @@ tS.REGISTRATION______________.val = {{'====================='}}; tS.REGISTRATION______________.str = {{'====================='}}; tS.REGISTRATION______________.tooltip = {{'====================='}}; + tS.mcMethod = []; + tS.mcMethod.val = {{'turboreg','normcorre'}}; + tS.mcMethod.str = {{'Turboreg motion correction','NoRMCorre (non-rigid) motion correction'}}; + tS.mcMethod.tooltip = {{'Method to use for motion correction.'}}; tS.parallel = []; tS.parallel.val = {{1,0}}; tS.parallel.str = {{'parallel processing','NO parallel processing'}}; @@ -303,6 +309,10 @@ tS.nParallelWorkers.val = {{nWorkersDefault, userSelectVal, nWorkersDefault*2}}; tS.nParallelWorkers.str = {{nWorkersDefault, userSelectStr, nWorkersDefault*2}}; tS.nParallelWorkers.tooltip = {{defaultTooltips}}; + tS.largeMovieLoad = []; + tS.largeMovieLoad.val = {{0,1}}; + tS.largeMovieLoad.str = {{'No','Yes'}}; + tS.largeMovieLoad.tooltip = {{'Whether to load movie without pre-allocating matrix, useful for single large movie in each folder that needs to be processed.'}}; % =================================== tS.STRIPE_REMOVAL______________ = []; @@ -404,6 +414,7 @@ nGuiSubsetsTrue = (nGuiSubsets-1); % nGuiSubsetsTrue = 1; figure(figNoDefault); + set(gcf,'color',[0 0 0]); for thisSet = 1:nGuiSubsetsTrue % 1:nPropertiesToChange % subsetStartTime = tic; @@ -421,8 +432,8 @@ [figHandle figNo] = openFigure(figNoDefault, ''); clf - uicontrol('Style','Text','String',inputTitleStr,'Units','normalized','Position',[uiXOffset uiYOffset+0.05 0.8 0.05],'BackgroundColor','white','HorizontalAlignment','Left','FontSize',uiFontSize); - uicontrol('Style','Text','String',sprintf('Options page %d/%d: Mouse over each options for tips. To continue, press enter. Orange = previous non-default settings.\n>>>On older MATLAB versions, select the command window before pressing enter.',thisSet,nGuiSubsets-1),'Units','normalized','Position',[uiXOffset uiYOffset+0.02 uiTxtSize+uiBoxSize 0.05],'BackgroundColor','white','HorizontalAlignment','Left','FontSize',uiFontSize); + uicontrol('Style','Text','String',inputTitleStr,'Units','normalized','Position',[uiXOffset uiYOffset+0.05 0.8 0.05],'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Left','FontSize',uiFontSize); + uicontrol('Style','Text','String',sprintf('Options page %d/%d: Mouse over each options for tips. To continue, press enter. Orange = previous non-default settings.\n>>>On older MATLAB versions, select the command window before pressing enter.',thisSet,nGuiSubsets-1),'Units','normalized','Position',[uiXOffset uiYOffset+0.02 uiTxtSize+uiBoxSize 0.05],'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Left','FontSize',uiFontSize); propNoDisp = 1; for propertyNo = propertySubsetList @@ -465,7 +476,7 @@ listPos = [uiXOffset+uiTxtSize+colNo uiYOffset-uiXIncrement*propNoDisp uiBoxSize 0.05]; uiListHandles{propertyNo} = uicontrol('Style', 'popup','String', propertySettingsStr.(property),'Units','normalized',... - 'Position', listPos,... + 'Position', listPos,'BackgroundColor','black','ForegroundColor','white',... 'Callback',@subfxnInterfaceCallback,'FontSize',uiFontSize,'Tag',property); % jEdit = findjobj(uiTextHandles{propertyNo}); @@ -482,15 +493,16 @@ % If property is non-default, set to orange to alert user. if any(ismember(nonDefaultProperties,property)) - set(uiListHandles{propertyNo},'Backgroundcolor',[254 216 177]/255); + set(uiListHandles{propertyNo},'Backgroundcolor',[254 216 177]/255/2); end propNoDisp = propNoDisp+1; end startMethodHandle = uicontrol('style','pushbutton','Units', 'normalized','position',[80 94 20 2]/100,'FontSize',9,'string','Click when done.','BackgroundColor',[153 255 153]/255,'callback',@subfxn_closeOptions); - emptyBox = uicontrol('Style','Text','String',[''],'Units','normalized','Position',[1 1 1 1]/100,'BackgroundColor','white','HorizontalAlignment','Left','FontWeight','bold','FontSize',7); + emptyBox = uicontrol('Style','Text','String',[''],'Units','normalized','Position',[1 1 1 1]/100,'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Left','FontWeight','bold','FontSize',7); set(gcf,'KeyPressFcn', @subfxn_closeOptionsKey); + ciapkg.view.changeFont('none','fontColor','w') % waitfor(gcf,'Tag'); waitfor(emptyBox); % pause @@ -528,7 +540,7 @@ % preprocessSettingStruct.refCropFrame = str2num(movieSettings{1}); % end function subfxnInterfaceCallback(hObject,callbackdata) - set(hObject, 'Backgroundcolor', [208,229,180]/255); + set(hObject, 'Backgroundcolor', [208,229,180]/255/2); % De-select the current option, allows user to press enter to continue. set(hObject, 'Enable', 'off'); diff --git a/@ciatah/modelAddNewFolders.m b/@ciatah/modelAddNewFolders.m index 3e67ba7..c0f4cfe 100644 --- a/@ciatah/modelAddNewFolders.m +++ b/@ciatah/modelAddNewFolders.m @@ -11,6 +11,8 @@ % 2021.02.02 [11:27:21] - 'Add CIAtah example folders.' now adds the absolute path to avoid path errors if user changes Matlab current directory. % 2021.06.01 [??15:43:11] - Add 'next' button to initial menu, in part to add MATLAB Online support. % 2021.08.10 [09:57:36] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.04.10 [09:23:32] - Add additional check for when user adds blank folder paths (NOT empty folders) and remove them. + % 2022.07.05 [19:27:24] - Empty folder check update. import ciapkg.api.* % import CIAtah functions in ciapkg package API. @@ -173,6 +175,16 @@ nNewFolders = length(newFolderListCell); end + newFolderListCell + disp('Checking and removing blank folders') + emptyFolderIdx = cellfun(@isempty,newFolderListCell,'UniformOutput',false); + emptyFolderIdx = cell2mat(emptyFolderIdx); + if any(emptyFolderIdx) + newFolderListCell = newFolderListCell(~emptyFolderIdx); + end + newFolderListCell + nNewFolders = length(newFolderListCell); + fileIdxArray = (nExistingFolders+1):(nExistingFolders+nNewFolders); % obj.foldersToAnalyze = fileIdxArray; nFolders = length(fileIdxArray); diff --git a/@ciatah/modelExtractSignalsFromMovie.m b/@ciatah/modelExtractSignalsFromMovie.m index ff2784a..f3857a7 100644 --- a/@ciatah/modelExtractSignalsFromMovie.m +++ b/@ciatah/modelExtractSignalsFromMovie.m @@ -30,6 +30,9 @@ % 2021.11.08 [12:42:12] - Add nwbpkg support. % 2021.11.09 [15:29:01] - Updated EXTRACT support. % 2022.06.27 [15:33:57] - matlab.desktop.editor.openDocument no longer uses pwd since options.settingsPrivateSaveFolder is based on an absolute path. + % 2022.06.29 [11:25:57] - CELLMax support for loading prior settings. + % 2022.07.05 [20:12:34] - Update to EXTRACT support: added additional options, do not automatically eliminate summary section, and more. + % 2022.09.14 [10:52:43] - Switch order of mergeStructs and supplying cell radius to EXTRACT, else empty vector can be passed depending on user input. % TODO % @@ -490,6 +493,16 @@ function getAlgorithmRootPath(algorithmFile,algorithmName,obj,rootFlag) import ciapkg.api.* % import CIAtah functions in ciapkg package API. try + if exist(algorithmFile,'file')==2 + fprintf('Found: %s\n',algorithmFile) + return; + elseif length(which('cellmax.runCELLMax'))>0 + fprintf('Found: %s\n',algorithmFile) + return; + else + fprintf('Did not find: %s\n',algorithmFile) + end + % foundFiles = dir(fullfile([obj.defaultObjDir filesep obj.externalProgramsDir], ['**\' algorithmFile ''])); foundFiles = dir(fullfile([obj.externalProgramsDir], ['**' filesep algorithmFile ''])); pathToAdd = foundFiles.folder; @@ -752,85 +765,121 @@ function getAlgorithmOptions() options.PCAICA.max_iter = str2num(movieSettings{setNo});setNo = setNo+1; options.PCAICA case {'EM','CELLMax'} + % 'CELLMax | readMovieChunks | read movie chunks from disk? (1 = yes, 0 = no)',... + + % Check if prior settings available. + options.CELLMax.readMovieChunks = '1'; + options.CELLMax.percentFramesPerIteration = '0.3'; + options.CELLMax.minIters = '50'; + options.CELLMax.maxIters = '120'; + options.CELLMax.gridSpacing = ''; + options.CELLMax.gridWidth = ''; + options.CELLMax.maxSqSize = '150'; + options.CELLMax.percentFramesPCAICA = '0.5'; + options.CELLMax.useGPU = '0'; + options.CELLMax.subsampleMethod = 'random'; + options.CELLMax.sizeThresh = '5'; + options.CELLMax.sizeThreshMax = '250'; + options.CELLMax.areaOverlapThresh = '0.65'; + options.CELLMax.removeCorrProbs = '1'; + options.CELLMax.distanceThresh = '3'; + options.CELLMax.corrRemovalAreaOverlapThresh = '0.3'; + options.CELLMax.threshForElim = '0.005'; + options.CELLMax.scaledPhiCorrThresh = '0.3'; + options.CELLMax.runMovieImageCorrThreshold = '1'; + options.CELLMax.movieImageCorrThreshold = '0.15'; + options.CELLMax.loadPreviousChunks = '0'; + options.CELLMax.saveIterMovie = '0'; + options.CELLMax.sqOverlap = '16'; + options.CELLMax.downsampleFactorTime = '1'; + options.CELLMax.downsampleFactorSpace = '1'; + options.CELLMax.spatialFilterMovie = '0'; + options.CELLMax.useSparseImageMatrix = '0'; + options.CELLMax.exitEarlySaveSparse = '0'; + options.CELLMax.numFramesSampleFitNoiseSigma = '1000'; + options.CELLMax.recalculateFinalTraces = '1'; + options.CELLMax.dsInitializeThreshold = '0.01'; + options.CELLMax.numSigmasThresh = '0'; + options.CELLMax.numPhotonsPerSigma = '10'; + options.CELLMax.upsampleFullIters = '2'; + options.CELLMax.removeAutoCorrThres = '0.65'; + options.CELLMax.removeAutoCorrThres = '0.65'; + options.CELLMax.saveChunksToRam = '1'; + options.CELLMax.eccentricityThreshold = '0.99'; + options.CELLMax.numObjThreshold = '3'; + + optsDefault = options.CELLMax; + try + optionsLoaded = obj.functionSettings.modelExtractSignalsFromMovie.options.CELLMax; + optsFn = fieldnames(optionsLoaded); + for iz = 1:length(optsFn) + options.CELLMax.(optsFn{iz}) = optionsLoaded.(optsFn{iz}); + end + disp('Loaded prior settings!') + catch + end + + % movieSettingsStrs = {... + mOpt = struct; + mOpt.readMovieChunks = 'read movie chunks from disk? (1 = yes, 0 = no)'; + mOpt.percentFramesPerIteration = 'fraction of total frames subset each iteration? (Float 0->1)'; + mOpt.minIters = 'number of min iterations? (Int)'; + mOpt.maxIters = 'number of max iterations? (Int)'; + mOpt.gridSpacing = '? (Int, leave blank for manual)'; + mOpt.gridWidth = '? (Int, leave blank for manual)'; + mOpt.maxSqSize = 'max square tile size? (Int)'; + mOpt.percentFramesPCAICA = 'percent frames for PCA-ICA? (Float 0->1)'; + mOpt.useGPU = 'use GPU? (1 = yes, 0 = no)'; + mOpt.subsampleMethod = 'subsample method? (Str: random, resampleRemaining)'; + mOpt.sizeThresh = '? (Int)'; + mOpt.sizeThreshMax = '? (Int)'; + mOpt.areaOverlapThresh = '?'; + mOpt.removeCorrProbs = '? (1 = yes, 0 = no)'; + mOpt.distanceThresh = '?'; + mOpt.corrRemovalAreaOverlapThresh = '?'; + mOpt.threshForElim = '? (elimination threshold scaled phi)'; + mOpt.scaledPhiCorrThresh = '?'; + mOpt.runMovieImageCorrThreshold = '?'; + mOpt.movieImageCorrThreshold = '?'; + mOpt.loadPreviousChunks = '?'; + mOpt.saveIterMovie = '?'; + mOpt.sqOverlap = '?'; + mOpt.downsampleFactorTime = '?'; + mOpt.downsampleFactorSpace = '?'; + mOpt.spatialFilterMovie = ' (0 = no, 1 = yes, after loading)?'; + mOpt.useSparseImageMatrix = ' (0 = no, 1 = yes)?'; + mOpt.exitEarlySaveSparse = ' (0 = no, 1 = yes)?'; + mOpt.numFramesSampleFitNoiseSigma = ' (Int, frames)?'; + mOpt.recalculateFinalTraces = ' (0 = no, 1 = yes)?'; + mOpt.dsInitializeThreshold = ' (Float, range 0:1)?'; + mOpt.numSigmasThresh = ' (Float)?'; + mOpt.numPhotonsPerSigma = ' (Int)?'; + mOpt.upsampleFullIters = ' (Int)?'; + mOpt.removeAutoCorrThres = ' (Float, range 0:1)?'; + mOpt.saveChunksToRam = ' (0 = no, 1 = yes)?'; + mOpt.eccentricityThreshold = 'Float: value 0 to 1, eccentricity (0 = more circular). By default is 0.99 so that dendrites are not excluded, lower if interested more in cells.'; + mOpt.numObjThreshold = 'Int: Filter kept if number of objects is <= numObjThreshold. This generally filters for noise that does not produce a single object containing the signal.'; + % }; + + % optsTmp = cellfun(@num2str,struct2cell(optsDefault),'UniformOutput',false); + % for iz = 1:length(movieSettingsStrs) + % movieSettingsStrs{iz} = [movieSettingsStrs{iz} ' | default: ' num2str(optsTmp{iz})]; + % end + + fnTmp = fieldnames(options.CELLMax); + movieSettingsStrs = {}; + for iz = 1:length(fnTmp) + movieSettingsStrs{iz} = [fnTmp{iz} ' | ' mOpt.(fnTmp{iz}) ' | default: ' optsDefault.(fnTmp{iz})]; + end + AddOpts.Resize='on'; AddOpts.WindowStyle='normal'; AddOpts.Interpreter='tex'; - % 'CELLMax | readMovieChunks | read movie chunks from disk? (1 = yes, 0 = no)',... - movieSettings = inputdlgcol({... - 'readMovieChunks | read movie chunks from disk? (1 = yes, 0 = no)',... - 'percentFramesPerIteration | fraction of total frames subset each iteration? (Float 0->1)',... - 'minIters | number of min iterations? (Int)',... - 'maxIters | number of max iterations? (Int)',... - 'gridSpacing? (Int, leave blank for manual)',... - 'gridWidth? (Int, leave blank for manual)',... - 'maxSqSize | max square tile size? (Int)',... - 'percentFramesPCAICA | percent frames for PCA-ICA? (Float 0->1)',... - 'useGPU | use GPU? (1 = yes, 0 = no)',... - 'subsampleMethod | subsample method? (Str: random, resampleRemaining)',... - 'sizeThresh? (Int)',... - 'sizeThreshMax? (Int)',... - 'areaOverlapThresh?',... - 'removeCorrProbs? (1 = yes, 0 = no)',... - 'distanceThresh?',... - 'corrRemovalAreaOverlapThresh?',... - 'threshForElim? (elimination threshold scaled phi)',... - 'scaledPhiCorrThresh?',... - 'runMovieImageCorrThreshold?',... - 'movieImageCorrThreshold?',... - 'loadPreviousChunks?',... - 'saveIterMovie?',... - 'sqOverlap?',... - 'downsampleFactorTime?',... - 'downsampleFactorSpace?',... - 'spatialFilterMovie (0 = no, 1 = yes, after loading)?',... - 'useSparseImageMatrix (0 = no, 1 = yes)?',... - 'exitEarlySaveSparse (0 = no, 1 = yes)?',... - 'numFramesSampleFitNoiseSigma (Int, frames)?',... - 'recalculateFinalTraces (0 = no, 1 = yes)?',... - 'dsInitializeThreshold (Float, range 0:1)?',... - 'numSigmasThresh (Float)?',... - 'numPhotonsPerSigma (Int)?',... - 'upsampleFullIters (Int)?',... - 'removeAutoCorrThres (Float, range 0:1)?',... - },... + movieSettings = inputdlgcol(movieSettingsStrs,... dlgStr,1,... - {... - '1',... - '0.3',... - '50',... - '120',... - '',... - '',... - '101',... - '0.5',... - '0',... - 'random',... - '5',... - '250',... - '0.65',... - '1',... - '3',... - '0.3',... - '0.005',... - '0.3',... - '1',... - '0.15',... - '0',... - '0',... - '16',... - '1',... - '1',... - '0',... - '0',... - '0',... - '1000',... - '1',... - '0.01',... - '0',... - '10',... - '2',... - '0.65'... - },AddOpts,2); + cellfun(@num2str,struct2cell(options.CELLMax),'UniformOutput',false),... + AddOpts,2); + setNo = 1; options.CELLMax.readMovieChunks = str2num(movieSettings{setNo});setNo = setNo+1; options.CELLMax.percentFramesPerIteration = str2num(movieSettings{setNo});setNo = setNo+1; @@ -868,39 +917,118 @@ function getAlgorithmOptions() options.CELLMax.numPhotonsPerSigma = str2num(movieSettings{setNo});setNo = setNo+1; options.CELLMax.upsampleFullIters = str2num(movieSettings{setNo});setNo = setNo+1; options.CELLMax.removeAutoCorrThres = str2num(movieSettings{setNo});setNo = setNo+1; + options.CELLMax.saveChunksToRam = str2num(movieSettings{setNo});setNo = setNo+1; + options.CELLMax.eccentricityThreshold = str2num(movieSettings{setNo});setNo = setNo+1; + options.CELLMax.numObjThreshold = str2num(movieSettings{setNo});setNo = setNo+1; + + obj.functionSettings.modelExtractSignalsFromMovie.options.CELLMax = options.CELLMax; case 'EXTRACT' - movieSettings = inputdlg({... - 'EXTRACT | Use GPU (''gpu'') or CPU (''cpu'')?',... - 'EXTRACT | avg_cell_radius (Avg. cell radius, also controls cell elimination)? Leave blank for GUI to estimate radius.',... - 'EXTRACT | cellfind_min_snr (threshold on the max instantaneous per-pixel SNR in the movie for searching for cells)?',... - 'EXTRACT | preprocess? (1 = yes, 0 = no, [ONLY SELECT 1 on raw, motion-corrected movies])',... - 'EXTRACT | num_partitions? Int: number of partitions in x and y.',... - 'EXTRACT | compact_output? (1 = yes, 0 = no)',... - 'EXTRACT | trace_output_option? Trace output type("nonneg" or "raw")',... - 'EXTRACT | use_sparse_arrays? (1 = yes, 0 = no)'... - },... + + % Check if prior settings available. + options.EXTRACT.use_gpu = '0'; + options.EXTRACT.parallel_cpu = '1'; + options.EXTRACT.multi_gpu = '0'; + options.EXTRACT.avg_cell_radius = ''; + options.EXTRACT.cellfind_min_snr = '1'; + options.EXTRACT.preprocess = '0'; + options.EXTRACT.num_partitions_x = '2'; + options.EXTRACT.num_partitions_y = '2'; + options.EXTRACT.compact_output = '1'; + options.EXTRACT.trace_output_option = 'nonneg'; + options.EXTRACT.use_sparse_arrays = '0'; + options.EXTRACT.T_min_snr = '10'; + options.EXTRACT.cellfind_max_steps = '1000'; + options.EXTRACT.temporal_corrupt_thresh = '0.7'; + options.EXTRACT.spatial_corrupt_thresh = '0.7'; + options.EXTRACT.size_upper_limit = '10'; + options.EXTRACT.low_ST_index_thresh = '1e-2'; + options.EXTRACT.low_ST_corr_thresh = '-Inf'; + options.EXTRACT.init_with_gaussian = '0'; + options.EXTRACT.downsample_time_by = '1'; + options.EXTRACT.downsample_space_by = '1'; + options.EXTRACT.T_dup_corr_thresh = '0.95'; + options.EXTRACT.max_iter = '6'; + options.EXTRACT.cellfind_filter_type = 'butter'; + + optsDefault = options.EXTRACT; + try + optionsLoaded = obj.functionSettings.modelExtractSignalsFromMovie.options.EXTRACT; + optsFn = fieldnames(optionsLoaded); + for iz = 1:length(optsFn) + options.EXTRACT.(optsFn{iz}) = optionsLoaded.(optsFn{iz}); + end + disp('Loaded prior settings!') + catch + end + + movieSettingsStrs = {... + 'use_gpu | Use GPU (1, ''gpu'') or CPU (0, ''cpu'')?',... + 'parallel_cpu | parallel processing in CPU mode? (1 = yes, 0 = no)',... + 'multi_gpu | parallel processing on multiple GPUs (1 = yes, 0 = no)',... + 'avg_cell_radius | Avg. cell radius (also controls cell elimination)? Leave blank for GUI to estimate radius.',... + 'cellfind_min_snr | Threshold on the max instantaneous per-pixel SNR in the movie for searching for cells?',... + 'preprocess | Preprocess movie? (1 = yes, 0 = no - ONLY SELECT 1 on raw, motion-corrected movies)',... + 'num_partitions | Int: number of partitions in x.',... + 'num_partitions | Int: number of partitions in y.',... + 'compact_output | Do not include bad components in output? (1 = yes, 0 = no)',... + 'trace_output_option | Trace output type ("nonneg" or "raw")',... + 'use_sparse_arrays | Save output as sparse arrays, to save memory for large FOV movies (1 = yes, 0 = no)',... + 'T_min_snr | Cells with temporal trace SNR below this value are eliminated (Int, e.g. 10)',... + 'cellfind_max_steps | Maximum number of cell candidate initialization during cell finding step. (Int, e.g. 100)',... + 'temporal_corrupt_thresh | Traces with index > value are eliminated (0 to 1, e.g. 0.7)',... + 'spatial_corrupt_thresh | Images with index > value are eliminated (0 to 1, e.g. 0.7)',... + 'size_upper_limit | any cell with an area outside of these will be eliminated during cell refinement',... + 'low_ST_index_thresh | threshold the ROIs, where the inferred traces do not explain the activity inside the filter well',... + 'low_ST_corr_thresh | threshold the ROIs, where the inferred traces do not explain the activity inside the filter well',... + 'init_with_gaussian | Initialize with a Gaussian shape prior to robust estimation? (1 = yes, 0 = no)',... + 'downsample_time_by | Downsampling factor time',... + 'downsample_space_by | Downsampling factor space',... + 'T_dup_corr_thresh | Duplicate removal correlation threshold',... + 'max_iter | Max iterations during cell finding step',... + 'cellfind_filter_type | Type of the spatial smoothing filter used for cell finding. Options: "butter" (IIR butterworth filter), "gauss" (FIR filter with a gaussian kernel), "wiener" (wiener filter), "movavg" (moving average in space), "median" (median filtering in 3D), "none" (no filtering).',... + }; + + optsTmp = cellfun(@num2str,struct2cell(optsDefault),'UniformOutput',false); + for iz = 1:length(movieSettingsStrs) + movieSettingsStrs{iz} = strrep([movieSettingsStrs{iz} ' | default: ' num2str(optsTmp{iz})],'_','\_'); + end + + AddOpts.Resize='on'; + AddOpts.WindowStyle='normal'; + AddOpts.Interpreter='tex'; + movieSettings = inputdlgcol(movieSettingsStrs,... dlgStr,1,... - {... - 'cpu',... - '',... - '1'... - '0',... - '2',... - '0',... - 'nonneg',... - '0',... - }... - );setNo = 1; - options.EXTRACT.gpuOrCPU = movieSettings{setNo};setNo = setNo+1; + cellfun(@num2str,struct2cell(options.EXTRACT),'UniformOutput',false),... + AddOpts,2); + + setNo = 1; + options.EXTRACT.use_gpu = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.parallel_cpu = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.multi_gpu = str2num(movieSettings{setNo});setNo = setNo+1; options.EXTRACT.avg_cell_radius = str2num(movieSettings{setNo});setNo = setNo+1; options.EXTRACT.cellfind_min_snr = str2num(movieSettings{setNo});setNo = setNo+1; options.EXTRACT.preprocess = str2num(movieSettings{setNo});setNo = setNo+1; options.EXTRACT.num_partitions_x = str2num(movieSettings{setNo});setNo = setNo+1; - options.EXTRACT.num_partitions_y = options.EXTRACT.num_partitions_x; + options.EXTRACT.num_partitions_y = str2num(movieSettings{setNo});setNo = setNo+1; options.EXTRACT.compact_output = str2num(movieSettings{setNo});setNo = setNo+1; options.EXTRACT.trace_output_option = movieSettings{setNo};setNo = setNo+1; options.EXTRACT.use_sparse_arrays = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.T_min_snr = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.cellfind_max_steps = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.temporal_corrupt_thresh = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.spatial_corrupt_thresh = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.size_upper_limit = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.low_ST_index_thresh = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.low_ST_corr_thresh = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.init_with_gaussian = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.downsample_time_by = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.downsample_space_by = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.T_dup_corr_thresh = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.max_iter = str2num(movieSettings{setNo});setNo = setNo+1; + options.EXTRACT.cellfind_filter_type = movieSettings{setNo};setNo = setNo+1; + + obj.functionSettings.modelExtractSignalsFromMovie.options.EXTRACT = options.EXTRACT; case 'CNMF' movieSettings = inputdlg({... @@ -957,7 +1085,8 @@ function getAlgorithmOptions() 'CNMF-E | iterate over parameter space? (1 = yes, 0 = no)',... 'CNMF-E | only run initialization algorithm? (1 = yes, 0 = no)',... 'CNMF-E | Spatial down-sampling factor (scalar >= 1)',... - 'CNMF-E | Temporal down-sampling factor (scalar >= 1)'... + 'CNMF-E | Temporal down-sampling factor (scalar >= 1)',... + 'CNMF | Run CNMF output classifier (1 = yes, 0 = no)',... },... dlgStr,1,... {... @@ -968,7 +1097,8 @@ function getAlgorithmOptions() '0',... '0',... '1',... - '1'... + '1',... + '1',... }... );setNo = 1; options.CNMFE.openEditor = str2num(movieSettings{setNo});setNo = setNo+1; @@ -979,6 +1109,7 @@ function getAlgorithmOptions() options.CNMFE.onlyRunInitialization = str2num(movieSettings{setNo});setNo = setNo+1; options.CNMFE.ssub = str2num(movieSettings{setNo});setNo = setNo+1; options.CNMFE.tsub = str2num(movieSettings{setNo});setNo = setNo+1; + options.CNMFE.classifyComponents = str2num(movieSettings{setNo});setNo = setNo+1; if options.CNMFE.deleteTempFolders==1 display(repmat('*',1,21)) @@ -1293,6 +1424,9 @@ function runPCAICASignalFinder() emOptions.CELLMaxoptions.numFramesSampleFitNoiseSigma = options.CELLMax.numFramesSampleFitNoiseSigma; emOptions.CELLMaxoptions.recalculateFinalTraces = options.CELLMax.recalculateFinalTraces; + emOptions.CELLMaxoptions.eccentricityThreshold = options.CELLMax.eccentricityThreshold; + emOptions.CELLMaxoptions.numObjThreshold = options.CELLMax.numObjThreshold; + if options.defaultOptions==0 emOptions.CELLMaxoptions.localICimgs = []; emOptions.CELLMaxoptions.localICtraces = []; @@ -1408,115 +1542,42 @@ function runPCAICASignalFinder() ciapkg.loadBatchFxns('loadEverything'); - movieList = getFileList(obj.inputFolders{obj.fileNum}, fileFilterRegexp); - [inputMovie thisMovieSize Npixels Ntime] = loadMovieList(movieList,'convertToDouble',0,'inputDatasetName',obj.inputDatasetName,'treatMoviesAsContinuous',1); - inputMovie(isnan(inputMovie)) = 0; - - % opts.movie_dataset = obj.inputDatasetName; - % % opts.save_to_movie_dir = 1; - % % % make larger if using 2x downsampled movie - % % opts.spat_linfilt_halfwidth = 2; - % % opts.ss_cell_size_threshold = 5; - % % opts.spat_medfilt_enabled = 0; - % % opts.trim_pixels = 0.4; - % % opts.verbos = 2; - % % opts.disableGPU = 1; - - % % options.turboreg = getFxnSettings(); - % % options.turboreg - % % options.datasetName = options.turboreg.datasetName; - - % % settingDefaults = struct(... - % % 'movie_dataset',{{'/1','/Movie','/movie'}},... - % % 'save_to_movie_dir', {{1,0}},... - % % 'spat_linfilt_halfwidth', {{2,5}},... - % % 'ss_cell_size_threshold', {{5,10}},... - % % 'spat_medfilt_enabled', {{0,1}},... - % % 'trim_pixels', {{0.4,0.6}},... - % % 'verbos', {{0,1}},... - % % 'disableGPU', {{1,0}}... - % % ); - % % settingStr = struct(... - % % 'movie_dataset',{{'/1','/Movie','/movie'}},... - % % 'save_to_movie_dir', {{1,0}},... - % % 'spat_linfilt_halfwidth', {{2,5}},... - % % 'ss_cell_size_threshold', {{5,10}},... - % % 'spat_medfilt_enabled', {{0,1}},... - % % 'trim_pixels', {{0.4,0.6}},... - % % 'verbos', {{0,1}},... - % % 'disableGPU', {{1,0}}... - % % ); - - % [h,w,t] = size(inputMovie); - - % opts.max_cell_radius=30; - % opts.min_cell_spacing=5; - % opts.remove_duplicate_cells = 0; - % % Use GPU - % opts.compute_device='gpu'; - - % % This is how to call the function 'partition_helper()' to find out how many partitions are necessary: - % num_parts = partition_helper(h,w,t,opts.min_cell_spacing,opts.max_cell_radius); - - % % Below call returned num_parts=20. We decide to partition x axis to 4, and y axis to 5. This makes 20 parititions overall. - % nPlotsRoot = sqrt(num_parts); - % if nPlotsRoot<2 - % nPlotsRoot = 2; - % end - % integ = fix(nPlotsRoot); - % fract = abs(nPlotsRoot - integ); - % opts.num_partition_y = ceil(nPlotsRoot); - % opts.num_partition_x = floor(nPlotsRoot)+round(fract) - - % min_cell_spacing=3; - % max_cell_radius=10; - % num_partition_x=3; - % num_partitiony=3; - % cell_keep_tolerance=5; - % subtract_background=1; - - % opts.config.diffuse_F=1; - % opts.config.smooth_T = 0; - % opts.config.smooth_F = 0; - % opts.config.cell_keep_tolerance - - % [filters,traces,info,opts] = extractor(movieList{1},opts); - % [filters,traces,info,opts] = extractor(inputMovie,opts); - % outStruct = extractor(inputMovie,opts); + % Load default configuration + extractConfig = get_defaults([]); - % switch pcaicaPCsICsSwitchStr - % case 'Subject' - % nPCsnICs = obj.numExpectedSignals.(obj.signalExtractionMethod).(obj.subjectStr{obj.fileNum}) - % case 'Folder' - % nPCsnICs = obj.numExpectedSignals.(obj.signalExtractionMethod).Folders{obj.fileNum} + % switch options.EXTRACT.gpuOrCPU + % case 'gpu' + % extractConfig.use_gpu = 1; + % case 'cpu' + % extractConfig.use_gpu = 0; + % extractConfig.parallel_cpu = 1; % otherwise % % body % end - % extractConfig.num_estimated_cells = nPCsnICs(1); - - % Load default configuration - extractConfig = get_defaults([]); - - extractConfig.avg_cell_radius = gridWidth.(obj.subjectStr{obj.fileNum}); - extractConfig.preprocess = options.EXTRACT.preprocess; - switch options.EXTRACT.gpuOrCPU - case 'gpu' - extractConfig.use_gpu = 1; - case 'cpu' - extractConfig.use_gpu = 0; - extractConfig.parallel_cpu = 1; - otherwise - % body - end % extractConfig.remove_static_background = false; % extractConfig.skip_dff = true; - extractConfig.cellfind_min_snr = options.EXTRACT.cellfind_min_snr; extractConfig.num_partitions_x = options.EXTRACT.num_partitions_x; extractConfig.num_partitions_y = options.EXTRACT.num_partitions_y; - extractConfig.trace_output_option = options.EXTRACT.trace_output_option; - extractConfig.use_sparse_arrays = options.EXTRACT.use_sparse_arrays; + + % Merge user options and EXTRACT options + [extractConfig] = ciapkg.io.mergeStructs(extractConfig,options.EXTRACT,'showStack',0); + [extractConfig.thresholds] = ciapkg.io.mergeStructs(extractConfig.thresholds,options.EXTRACT,'showStack',0); + + extractConfig.avg_cell_radius = gridWidth.(obj.subjectStr{obj.fileNum}); + + fn_structdisp(extractConfig); + + % extractConfig.preprocess = options.EXTRACT.preprocess; + % extractConfig.cellfind_min_snr = options.EXTRACT.cellfind_min_snr; + % extractConfig.trace_output_option = options.EXTRACT.trace_output_option; + % extractConfig.use_sparse_arrays = options.EXTRACT.use_sparse_arrays; % extractConfig.compact_output = options.EXTRACT.compact_output; + % extractConfig.T_min_SNR = options.EXTRACT.T_min_SNR; + % extractConfig.cellfind_max_steps = options.EXTRACT.cellfind_max_steps; + % extractConfig.temporal_corrupt_thresh = options.EXTRACT.temporal_corrupt_thresh; + % extractConfig.spatial_corrupt_thresh = options.EXTRACT.spatial_corrupt_thresh; + % extractConfig.multi_gpu = options.EXTRACT.multi_gpu; % extractConfig.thresholds.T_min_snr = 3; % multiply with noise_std % extractConfig.thresholds.size_lower_limit = 1/5; % multiply with avg_cell_area @@ -1529,6 +1590,14 @@ function runPCAICASignalFinder() % extractConfig.thresholds.low_ST_index_thresh = 1e-2; % extractConfig.thresholds.high_ST_index_thresh = 0.8; + % disp(extractConfig) + % pause + + % Load movie + movieList = getFileList(obj.inputFolders{obj.fileNum}, fileFilterRegexp); + [inputMovie thisMovieSize Npixels Ntime] = loadMovieList(movieList,'convertToDouble',0,'inputDatasetName',obj.inputDatasetName,'treatMoviesAsContinuous',1); + inputMovie(isnan(inputMovie)) = 0; + startTime = tic; outStruct = extractor(inputMovie,extractConfig); outStruct @@ -1543,9 +1612,11 @@ function runPCAICASignalFinder() try extractAnalysisOutput.info = outStruct.info; extractAnalysisOutput.config = outStruct.config; - extractAnalysisOutput.info = outStruct.info; + % extractAnalysisOutput.info = outStruct.info; + % Remove the large summary field since takes up unnecessary space - extractAnalysisOutput.info.summary = []; + % extractAnalysisOutput.info.summary = []; + extractAnalysisOutput.file = movieList{1}; extractAnalysisOutput.userInputConfig = extractConfig; % for backwards compatibility @@ -1844,7 +1915,8 @@ function runPCAICASignalFinder() cnmfeOptions = cnmfeOpts; end - cnmfOptions.nonCNMF.inputDatasetName = obj.inputDatasetName; + cnmfeOptions.nonCNMF.inputDatasetName = obj.inputDatasetName; + cnmfeOptions.nonCNMF.classifyComponents = options.CNMFE.classifyComponents; try display(repmat('*',1,14)) @@ -2211,4 +2283,89 @@ function runPCAICASignalFinder() turboregSettingStruct.(property) = turboregSettingDefaults.(property){uiListHandleData.Value}; end close(1337) -end \ No newline at end of file +end + +% function [extractAnalysisOutput] = runEXTRACTSignalFinder() + + % opts.movie_dataset = obj.inputDatasetName; + % % opts.save_to_movie_dir = 1; + % % % make larger if using 2x downsampled movie + % % opts.spat_linfilt_halfwidth = 2; + % % opts.ss_cell_size_threshold = 5; + % % opts.spat_medfilt_enabled = 0; + % % opts.trim_pixels = 0.4; + % % opts.verbos = 2; + % % opts.disableGPU = 1; + + % % options.turboreg = getFxnSettings(); + % % options.turboreg + % % options.datasetName = options.turboreg.datasetName; + + % % settingDefaults = struct(... + % % 'movie_dataset',{{'/1','/Movie','/movie'}},... + % % 'save_to_movie_dir', {{1,0}},... + % % 'spat_linfilt_halfwidth', {{2,5}},... + % % 'ss_cell_size_threshold', {{5,10}},... + % % 'spat_medfilt_enabled', {{0,1}},... + % % 'trim_pixels', {{0.4,0.6}},... + % % 'verbos', {{0,1}},... + % % 'disableGPU', {{1,0}}... + % % ); + % % settingStr = struct(... + % % 'movie_dataset',{{'/1','/Movie','/movie'}},... + % % 'save_to_movie_dir', {{1,0}},... + % % 'spat_linfilt_halfwidth', {{2,5}},... + % % 'ss_cell_size_threshold', {{5,10}},... + % % 'spat_medfilt_enabled', {{0,1}},... + % % 'trim_pixels', {{0.4,0.6}},... + % % 'verbos', {{0,1}},... + % % 'disableGPU', {{1,0}}... + % % ); + + % [h,w,t] = size(inputMovie); + + % opts.max_cell_radius=30; + % opts.min_cell_spacing=5; + % opts.remove_duplicate_cells = 0; + % % Use GPU + % opts.compute_device='gpu'; + + % % This is how to call the function 'partition_helper()' to find out how many partitions are necessary: + % num_parts = partition_helper(h,w,t,opts.min_cell_spacing,opts.max_cell_radius); + + % % Below call returned num_parts=20. We decide to partition x axis to 4, and y axis to 5. This makes 20 parititions overall. + % nPlotsRoot = sqrt(num_parts); + % if nPlotsRoot<2 + % nPlotsRoot = 2; + % end + % integ = fix(nPlotsRoot); + % fract = abs(nPlotsRoot - integ); + % opts.num_partition_y = ceil(nPlotsRoot); + % opts.num_partition_x = floor(nPlotsRoot)+round(fract) + + % min_cell_spacing=3; + % max_cell_radius=10; + % num_partition_x=3; + % num_partitiony=3; + % cell_keep_tolerance=5; + % subtract_background=1; + + % opts.config.diffuse_F=1; + % opts.config.smooth_T = 0; + % opts.config.smooth_F = 0; + % opts.config.cell_keep_tolerance + + % [filters,traces,info,opts] = extractor(movieList{1},opts); + % [filters,traces,info,opts] = extractor(inputMovie,opts); + % outStruct = extractor(inputMovie,opts); + + % switch pcaicaPCsICsSwitchStr + % case 'Subject' + % nPCsnICs = obj.numExpectedSignals.(obj.signalExtractionMethod).(obj.subjectStr{obj.fileNum}) + % case 'Folder' + % nPCsnICs = obj.numExpectedSignals.(obj.signalExtractionMethod).Folders{obj.fileNum} + % otherwise + % % body + % end + % extractConfig.num_estimated_cells = nPCsnICs(1); +% end \ No newline at end of file diff --git a/@ciatah/modelGetSignalsImages.m b/@ciatah/modelGetSignalsImages.m index d32faee..74eea38 100644 --- a/@ciatah/modelGetSignalsImages.m +++ b/@ciatah/modelGetSignalsImages.m @@ -14,6 +14,7 @@ % 2020.12.08 [01:14:09] - Reorganized returnType to be outside raw signals flag, so common filtering mechanism regardless of variables loaded into RAM or not. This fixes if a user loads variables into RAM then uses cross-session alignment, the viewMatchObjBtwnSessions method may not display registered images (cross-session alignment is still fine and valid). % 2021.06.30 [14:52:23] - Updated to allow loading raw files without calling modelVarsFromFiles. % 2021.08.10 [09:57:36] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.04.09 [20:30:52] - Ensure when loading cell extraction CIAtah-style that only MAT files are loaded. % TODO % Give a user a warning() output if there are no or empty cell-extraction outputs @@ -256,7 +257,7 @@ end else while isempty(filesToLoad) - filesToLoad = getFileList(obj.dataPath{thisFileNum},strrep(regexPairs{fileToLoadNo},'.mat','')); + filesToLoad = getFileList(obj.dataPath{thisFileNum},strrep(regexPairs{fileToLoadNo},'.mat','.*.mat$')); fileToLoadNo = fileToLoadNo+1; if fileToLoadNo>nRegExps break; @@ -264,7 +265,7 @@ end end if isempty(filesToLoad) - else + else [inputImages,inputSignals,infoStruct,algorithmStr,inputSignals2] = ciapkg.io.loadSignalExtraction(filesToLoad{1}); end signalPeaks = []; @@ -300,7 +301,7 @@ if loadCiaFiles==1 while isempty(filesToLoad) - filesToLoad = getFileList(obj.dataPath{thisFileNum},strrep(regexPairs{fileToLoadNo},'.mat','')); + filesToLoad = getFileList(obj.dataPath{thisFileNum},strrep(regexPairs{fileToLoadNo},'.mat','.*.mat$')); fileToLoadNo = fileToLoadNo+1; if fileToLoadNo>nRegExps break; @@ -642,7 +643,11 @@ % if exist('inputSignals','var') if ~isempty(inputSignals) - if isempty(obj.signalPeaksArray{thisFileNum}) + if isempty(obj.signalPeaksArray) + disp('Please run modelVarsFromFiles') + signalPeaks = []; + signalPeaksArray = []; + elseif isempty(obj.signalPeaksArray{thisFileNum}) signalPeaks = []; signalPeaksArray = []; else diff --git a/@ciatah/modelModifyRegionAnalysis.m b/@ciatah/modelModifyRegionAnalysis.m index ceed7e6..f03705c 100644 --- a/@ciatah/modelModifyRegionAnalysis.m +++ b/@ciatah/modelModifyRegionAnalysis.m @@ -11,7 +11,8 @@ % 2017.01.14 [20:06:04] - support switched from [nSignals x y] to [x y nSignals] % 2021.06.18 [21:41:07] - added modelVarsFromFilesCheck() to check and load signals if user hasn't already. % 2021.08.10 [09:57:36] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. - % 2022.02.28 [‏‎13:01:20] - Fix modelVarsFromFilesCheck and modelVarsFromFiles recursion edge case. + % 2022.02.28 [13:01:20] - Fix modelVarsFromFilesCheck and modelVarsFromFiles recursion edge case. + % 2022.08.26 [15:00:28] - Misc code standard updates. % TODO % @@ -26,7 +27,7 @@ if obj.guiEnabled==1 scnsize = get(0,'ScreenSize'); - [fileIdxArray idNumIdxArray nFilesToAnalyze nFiles] = obj.getAnalysisSubsetsToAnalyze(); + [fileIdxArray, idNumIdxArray, nFilesToAnalyze, nFiles] = obj.getAnalysisSubsetsToAnalyze(); % [fileIdxArray, ok] = listdlg('ListString',obj.fileIDNameArray,'ListSize',[scnsize(3)*0.2 scnsize(4)*0.25],'Name','which folders to analyze?'); scnsize = get(0,'ScreenSize'); @@ -59,12 +60,12 @@ regionFile = getFileList(obj.inputFolders{obj.fileNum},obj.regionModSaveStr); % if no file, skip if isempty(regionFile) - display('No region analysis to load!') + disp('No region analysis to load!') continue; end regionFile = regionFile{1}; fprintf('loading: %s',regionFile) - loadFile = load(regionFile,'roipolyRegion','roipolyCoords') + loadFile = load(regionFile,'roipolyRegion','roipolyCoords'); obj.analysisROIArray{obj.fileNum} = loadFile.roipolyRegion; obj.validRegionModPoly{obj.fileNum} = loadFile.roipolyCoords; % NOT DONE!!!!!!!! @@ -73,14 +74,14 @@ regionFile = getFileList(obj.inputFolders{obj.fileNum},obj.regionModSaveStr); % if no file, skip if isempty(regionFile) - display('No region analysis to load!') + disp('No region analysis to load!') continue; end - loadFile = load(regionFile,'roipolyRegion','roipolyCoords') + loadFile = load(regionFile,'roipolyRegion','roipolyCoords'); obj.analysisROIArray{obj.fileNum} = loadFile.roipolyRegion; obj.validRegionModPoly{obj.fileNum} = loadFile.roipolyCoords; end - [inputSignals inputImages signalPeaks signalPeaksArray] = modelGetSignalsImages(obj,'returnType','raw'); + [inputSignals, inputImages, signalPeaks, signalPeaksArray] = modelGetSignalsImages(obj,'returnType','raw'); % inputImages = thresholdImages(inputImages,'binary',0)/3; % [inputSignals inputImages signalPeaks signalPeaksArray] = modelGetSignalsImages(obj); @@ -147,9 +148,9 @@ h = impoly(gca,polyCoords); h.wait polyCoords = h.getPosition; - [obj.analysisROIArray{obj.fileNum} xpoly ypoly] = roipoly(thisCellmap+0.1,polyCoords(:,1),polyCoords(:,2)); + [obj.analysisROIArray{obj.fileNum}, xpoly, ypoly] = roipoly(thisCellmap+0.1,polyCoords(:,1),polyCoords(:,2)); else - [obj.analysisROIArray{obj.fileNum} xpoly ypoly] = roipoly; + [obj.analysisROIArray{obj.fileNum}, xpoly, ypoly] = roipoly; end obj.validRegionModPoly{obj.fileNum} = [xpoly ypoly]; end @@ -158,13 +159,13 @@ drawnow; end if strcmp(analysisToRun,'runAlreadySelectedRegions') - display('Using previous ROI') + disp('Using previous ROI') end inputImagesROI = obj.analysisROIArray{obj.fileNum}; inputImagesThres = thresholdImages(inputImages,'waitbarOn',1,'binary',1); % signalInROI = squeeze(nansum(nansum(bsxfun(@times,inputImages,permute(inputImages,[2 3 1])),1),2)); - display('finding ROIs inside region...') - signalInROI = squeeze(nansum(nansum(bsxfun(@times,inputImagesThres,inputImagesROI),1),2)); + disp('finding ROIs inside region...') + signalInROI = squeeze(sum(sum(bsxfun(@times,inputImagesThres,inputImagesROI),1,'omitnan'),2,'omitnan')); % signalInROI = applyImagesToMovie(inputImages,permute(inputImages,[2 3 1]), 'alreadyThreshold',1); signalsToKeep = signalInROI~=0; diff --git a/@ciatah/modelPreprocessMovieFunction.m b/@ciatah/modelPreprocessMovieFunction.m index 3fae594..1286f89 100644 --- a/@ciatah/modelPreprocessMovieFunction.m +++ b/@ciatah/modelPreprocessMovieFunction.m @@ -45,6 +45,14 @@ % 2022.01.25 [16:08:49] - Add `modelPreprocessMovieFunction` settings saved in CIAtah class to saved output file for later retrieval. % 2022.01.26 [08:31:06] - For selecting turboreg crop coordinates, switched to ciapkg.io.readFrame when single frame is requested as this is faster and avoids long read times of all frame information as occurs in certain types of TIFF files or hard drives. % 2022.02.09 [23:51:18] - Misc code fixes to conform to better Matlab language standards. + % 2022.03.03 [19:18:54] - Change all nested and local functions to start with "nestedfxn_" or "localfxn_" prefixes for clarity. Added option to fix "broken" frames, e.g. frames in which the camera gives out garbled or improperly formatted data. Fix with mean of the movie to reduce effect on downstream analysis. Disable local function "getMovieFileType" in favor or CIAtah package function. + % 2022.03.09 [16:50:36] - Added option to place prefix on all output files. + % 2022.03.12 [19:19:41] - Detrending now does everything within nested function instead of calling normalizeMovie() to reduce memory overhead. + % 2022.03.13 [23:20:08] - Added support for MAT-file based processing of movies. + % 2022.06.28 [15:28:13] - Add additional black background support. + % 2022.07.10 [17:21:21] - Added largeMovieLoad setting support, improve memory allocation for select movies. + % 2022.07.27 [12:45:08] - Make sure readFrame is compatible with all HDF5 dataset names. + % 2022.07.18 [10:30:01] - NormCorre integrated into the pipeline for end users. % TODO % Allow users to save out analysis options order and load it back in. % Insert NaNs or mean of the movie into dropped frame location, see line 260 @@ -59,6 +67,12 @@ % load necessary functions and defaults % loadBatchFxns(); %======================== + % Str: path to MAT-file that will be used to temporarily store processed movie, to avoid Out of Memory errors for certain movies. + options.matfilePath = ''; + % Str: Variable name for movie to store MAT-file in. DO NOT CHANGE for the moment. + options.matfileVarname = 'thisMovie'; + % INTERNAL | Binary: 1 = running MAT-file based analysis, 0 = run normal analysis load in disk. + options.runMatfile = 0; % Binary: 1 = show figures, 0 = disable showing figures options.showFigures = 1; % set the options, these can be modified by varargin @@ -128,6 +142,12 @@ % Str: daset options.nwbSettingsDatasetname = '/general/optophysiology/imaging_plane/description'; options.h5SettingsDatasetname = '/movie/preprocessingSettingsAll'; + % Str: prefix to add to all saved files. + options.saveFilePrefix = ''; + % Str: suffix to add to all saved files. + options.saveFileSuffix = ''; + % Str: suffix to add to all saved files. + options.videoPlayer = 'matlab'; % ==== % get options options = getOptions(options,varargin); @@ -245,7 +265,10 @@ 'str','Manually register movie images. (ignore)'),... 'turboreg',struct(... 'save','treg',... - 'str','TurboReg (motion correction with option to spatially filter)'),... + 'str','Motion correction - TurboReg or NoRMCorre (with option to spatially filter)'),... + 'normcorre',struct(... + 'save','normcorre',... + 'str','NoRMCorre (motion correction with option to spatially filter)'),... 'fft_highpass',struct(... 'save','fftHp',... 'str','High-pass FFT (ignore most cases).'),... @@ -296,6 +319,12 @@ % Do nothing end + % Load existing settings + try + options.videoPlayer = obj.functionSettings.modelPreprocessMovieFunction.options.videoPlayer; + catch + end + analysisOptionListStr = analysisOptionList; for optNoS3 = 1:length(analysisOptionListStr) analysisOptionListStr{optNoS3} = analysisOptsInfo.(analysisOptionListStr{optNoS3}).str; @@ -307,6 +336,8 @@ try ok = 1; [~, ~] = openFigure(1776, '');clf; + + set(gcf,'color',[0 0 0]); instructTextPos = [1 80 70 20]/100; listTextPos = [1 40 98 28]/100; listTextPos2 = [1 1 98 28]/100; @@ -314,29 +345,30 @@ figNoList = struct; figNoList.detrend = 102030; - shortcutMenuHandle = uicontrol('style','pushbutton','Units','normalized','position',[1 75 30 3]/100,'FontSize',9,'string','Reset to default list order','callback',@subfxn_resetSetpsMenu); - shortcutMenuHandle2 = uicontrol('style','pushbutton','Units','normalized','position',[32 75 30 3]/100,'FontSize',9,'string','Select default options (e.g. post-dragging)','callback',@subfxn_highlightDefault); - shortcutMenuHandle3 = uicontrol('style','pushbutton','Units','normalized','position',[63 75 30 3]/100,'FontSize',9,'string','Finished','callback',@subfxn_closeOptions); - % shortcutMenuHandle = uicontrol('style','pushbutton','Units','normalized','position',[32 62 30 3]/100,'FontSize',9,'string','Next screen','callback',@subfxn_highlightDefault); + shortcutMenuHandle = uicontrol('style','pushbutton','Units','normalized','position',[1 75 30 3]/100,'FontSize',9,'string','Reset to default list order','callback',@nestedfxn_resetSetpsMenu); + shortcutMenuHandle2 = uicontrol('style','pushbutton','Units','normalized','position',[32 75 30 3]/100,'FontSize',9,'string','Select default options (e.g. post-dragging)','callback',@nestedfxn_highlightDefault); + shortcutMenuHandle3 = uicontrol('style','pushbutton','Units','normalized','position',[63 75 30 3]/100,'FontSize',9,'string','Finished','callback',@nestedfxn_closeOptions); + % shortcutMenuHandle = uicontrol('style','pushbutton','Units','normalized','position',[32 62 30 3]/100,'FontSize',9,'string','Next screen','callback',@nestedfxn_highlightDefault); - uicontrol('Style','Text','String','Analysis steps to perform.','Units','normalized','Position',[1 68 90 3]/100,'BackgroundColor','white','HorizontalAlignment','Left','FontWeight','bold'); + uicontrol('Style','Text','String','Analysis steps to perform.','Units','normalized','Position',[1 68 90 3]/100,'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Left','FontWeight','bold'); [hListbox, jListbox, jScrollPane, jDND] = reorderableListbox('String',analysisOptionListStr,'Units','normalized','Position',listTextPos,'Max',Inf,'Min',0,'Value',defaultChoiceIdx,... - 'MousePressedCallback',@subfxn_analysisOutputMenuChange,... - 'MouseReleasedCallback',@subfxn_analysisOutputMenuChange,... - 'DragOverCallback',@subfxn_analysisOutputMenuChange2,... - 'DropCallback',@subfxn_analysisOutputMenuChange... + 'MousePressedCallback',@nestedfxn_analysisOutputMenuChange,... + 'MouseReleasedCallback',@nestedfxn_analysisOutputMenuChange,... + 'DragOverCallback',@nestedfxn_analysisOutputMenuChange2,... + 'DropCallback',@nestedfxn_analysisOutputMenuChange... ); - uicontrol('Style','Text','String',['At which analysis step should files be saved to disk?'],'Units','normalized','Position',[1 30 90 3]/100,'BackgroundColor','white','HorizontalAlignment','Left','FontWeight','bold'); + uicontrol('Style','Text','String',['At which analysis step should files be saved to disk?'],'Units','normalized','Position',[1 30 90 3]/100,'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Left','FontWeight','bold'); [hListboxS, jListboxS, jScrollPaneS, jDNDS] = reorderableListbox('String',analysisOptionListStr,'Units','normalized','Position',listTextPos2,'Max',Inf,'Min',0,'Value',defaultSaveIdx); uicontrol('Style','Text','String',['Analysis step selection and ordering' 10 '======='... 10 'Gentlemen, you can not fight in here! This is the War Room.' 10 'We can know only that we know nothing.' 10 'And that is the highest degree of human wisdom.'... - 10 10 '1: Click items to select.' 10 '2: Drag to re-order analysis.' 10 '3: Click command window and press ENTER to continue.'],'Units','normalized','Position',instructTextPos,'BackgroundColor','white','HorizontalAlignment','Left'); + 10 10 '1: Click items to select.' 10 '2: Drag to re-order analysis.' 10 '3: Click command window and press ENTER to continue.'],... + 'Units','normalized','Position',instructTextPos,'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Left'); - emptyBox = uicontrol('Style','Text','String','','Units','normalized','Position',[1 1 1 1]/100,'BackgroundColor','white','HorizontalAlignment','Left','FontWeight','bold','FontSize',7); + emptyBox = uicontrol('Style','Text','String','','Units','normalized','Position',[1 1 1 1]/100,'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Left','FontWeight','bold','FontSize',7); if ismac cmdWinEditorFont = 'Menlo-Regular'; @@ -348,15 +380,17 @@ cmdWinEditorFont = 'FixedWidth'; end - usaFlagPic = @(x) uicontrol('Style','Text','String',x,'Units','normalized','Position',[0.7 0.85 0.28 0.14],'BackgroundColor','white','HorizontalAlignment','Right','FontName',cmdWinEditorFont,'FontSize',5); + usaFlagPic = @(x) uicontrol('Style','Text','String',x,'Units','normalized','Position',[0.7 0.85 0.28 0.14],'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Right','FontName',cmdWinEditorFont,'FontSize',5); usaFlagPic(USAflagStr); % exitHandle = uicontrol('style','pushbutton','Units', 'normalized','position',[5 85 50 3]/100,'FontSize',9,'string','Click here to finish','callback',@subfxnCloseFig,'HorizontalAlignment','Left'); % Wait for user input - % set(gcf, 'WindowScrollWheelFcn', @mouseWheelChange); + % set(gcf, 'WindowScrollWheelFcn', @nestedfxn_mouseWheelChange); % set(gcf,'KeyPressFcn', @(src,event) set(gcf,'Tag','next')); - set(gcf,'KeyPressFcn', @subfxn_closeOptionsKey); + set(gcf,'KeyPressFcn', @nestedfxn_closeOptionsKey); + set(gcf,'color',[0 0 0]); + ciapkg.view.changeFont('none','fontColor','w') % waitfor(gcf,'Tag'); waitfor(emptyBox); % pause @@ -456,7 +490,11 @@ '[optional, if using HDF5] Input HDF5 file dataset name (e.g. "/images" for raw Inscopix or "/1" for example data, sans quotes): ',... '[optional, if using HDF5] Output HDF5 file dataset name (see above): ',... '[optional] (0) Use default preprocessing settings, (1) Save settings or load previously saved settings: ',... - '[optional] (1) Save movie to NWB format, (0) Save to HDF5 format: '... + '[optional] (1) Save movie to NWB format, (0) Save to HDF5 format: ',... + '[optional] Prefix to add to all processed movies (e.g. "concat_", "experiment_"): ',... + '[optional] Suffix to add to all processed movies (e.g. "concat_", "experiment_"): '... + '[optional] Default video player (imagej or matlab): ',... + '[optional] MAT-file path if processing large movie (e.g. getting Out of Memory errors), SSD drive preferred for speed: '... },... 'Preprocessing settings',[1 100],... {... @@ -465,7 +503,11 @@ obj.inputDatasetName,... obj.outputDatasetName,... num2str(obj.saveLoadPreprocessingSettings),... - num2str(obj.nwbLoadFiles)... + num2str(obj.nwbLoadFiles),... + '',... + '',... + options.videoPlayer,... + options.matfilePath... }... ); disp(movieSettings) @@ -479,6 +521,12 @@ obj.outputDatasetName = movieSettings{4}; obj.saveLoadPreprocessingSettings = str2num(movieSettings{5}); obj.nwbLoadFiles = str2num(movieSettings{6}); + options.saveFilePrefix = movieSettings{7}; + options.saveFileSuffix = movieSettings{8}; + options.videoPlayer = movieSettings{9}; + options.matfilePath = movieSettings{10}; + + obj.functionSettings.modelPreprocessMovieFunction.options.videoPlayer = options.videoPlayer; if obj.saveLoadPreprocessingSettings==1 currentDateTimeStr = datestr(now,'yyyymmdd_HHMMSS','local'); @@ -538,9 +586,20 @@ end else end + + % Get movie processing settings, NOT just for motion correction [options.turboreg, preprocessingSettingsAll] = obj.getRegistrationSettings('Processing options','inputSettings',previousPreprocessSettings); fn_structdisp(options.turboreg) + + % Get additional NormCorre settings + switch options.turboreg.mcMethod + case 'normcorre' + optsNoRMCorre = ciapkg.motion_correction.getNoRMCorreParams([400 400 1],'guiDisplay',1); + otherwise + optsNoRMCorre = []; + end + if obj.saveLoadPreprocessingSettings==1 obj.preprocessSettings = preprocessingSettingsAll; try @@ -575,7 +634,7 @@ if sum(strcmp(analysisOptionList(analysisOptionsIdx),'turboreg'))>0 if options.processMovies==1 try - [turboRegCoords] = turboregCropSelection(options,folderList); + [turboRegCoords] = localfxn_turboregCropSelection(options,folderList); catch err disp(repmat('@',1,7)) disp(getReport(err,'extended','hyperlinks','on')); @@ -673,7 +732,7 @@ % Get the list of movies movieList = getFileList(thisDir, options.fileFilterRegexp); - [movieList] = removeUnsupportedFiles(movieList,options); + [movieList] = localfxn_removeUnsupportedFiles(movieList,options); % If there are no files in this folder to analyze, skip to the next folder. if isempty(movieList) @@ -685,7 +744,12 @@ disp(fileInfo) % base string to save as fileInfoSaveStr = [fileInfo.date '_' fileInfo.protocol '_' fileInfo.subject '_' fileInfo.assay]; - thisDirSaveStr = [thisDir filesep fileInfoSaveStr]; + if isempty(options.saveFilePrefix) + thisDirSaveStr = [thisDir filesep fileInfoSaveStr]; + else + thisDirSaveStr = [thisDir filesep options.saveFilePrefix fileInfoSaveStr]; + end + thisProcessingDirFileInfoStr = [thisProcessingDir filesep currentDateTimeStr '_' fileInfoSaveStr]; saveStr = ''; % add the folder to the output structure @@ -734,19 +798,34 @@ % options.frameList options.frameList(options.frameList>sum(movieDims.z)) = []; end - if options.processMoviesSeparately==1 - thisMovie = loadMovieList(movieList{movieNo},'convertToDouble',0,'frameList',options.frameList,'inputDatasetName',options.datasetName,'treatMoviesAsContinuous',0,'loadSpecificImgClass','single'); - % playMovie(thisMovie); - else - % options.turboreg.treatMoviesAsContinuousSwitch = 1; - if isempty(options.frameList)&&options.turboreg.loadMoviesFrameByFrame==1 - movieDims = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList,'inputDatasetName',options.datasetName,'treatMoviesAsContinuous',options.turboreg.treatMoviesAsContinuousSwitch,'loadSpecificImgClass','single','getMovieDims',1); - sum(movieDims.z) - thisFrameList = 1:sum(movieDims.z); + if isempty(options.matfilePath) + if options.processMoviesSeparately==1 + thisMovie = loadMovieList(movieList{movieNo},'convertToDouble',0,'frameList',options.frameList,'inputDatasetName',options.datasetName,'treatMoviesAsContinuous',0,'loadSpecificImgClass','single','largeMovieLoad',options.turboreg.largeMovieLoad); + % playMovie(thisMovie); else - thisFrameList = options.frameList; + % options.turboreg.treatMoviesAsContinuousSwitch = 1; + if isempty(options.frameList)&&options.turboreg.loadMoviesFrameByFrame==1 + movieDims = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList,'inputDatasetName',options.datasetName,'treatMoviesAsContinuous',options.turboreg.treatMoviesAsContinuousSwitch,'loadSpecificImgClass','single','getMovieDims',1,'largeMovieLoad',options.turboreg.largeMovieLoad); + sum(movieDims.z) + thisFrameList = 1:sum(movieDims.z); + else + thisFrameList = options.frameList; + end + thisMovie = loadMovieList(movieList,'convertToDouble',0,'frameList',thisFrameList,'inputDatasetName',options.datasetName,'treatMoviesAsContinuous',options.turboreg.treatMoviesAsContinuousSwitch,'loadSpecificImgClass','single','largeMovieLoad',options.turboreg.largeMovieLoad); end - thisMovie = loadMovieList(movieList,'convertToDouble',0,'frameList',thisFrameList,'inputDatasetName',options.datasetName,'treatMoviesAsContinuous',options.turboreg.treatMoviesAsContinuousSwitch,'loadSpecificImgClass','single'); + else + % Write data to MAT-file to allow read-from-disk processing. + [success] = writeDataToMatfile(movieList{movieNo},options.matfileVarname,options.matfilePath,... + 'chunkSize',options.turboregNumFramesSubset,... + 'saveSpecificImgClass','single'); + matObj = matfile(movieList{movieNo},'Writable',true); + + % Make a dummy thisMovie for use in certain cases. + thisMovie = []; + if success==0 + continue; + end + options.runMatfile = 1; end if options.processMoviesSeparately==1 @@ -756,7 +835,12 @@ end [~, ~] = openFigure(4242, ''); - imagesc(squeeze(thisMovie(:,:,1))) + if options.runMatfile==1 + tmpFrameHere = squeeze(matObj.(options.matfileVarname)(:,:,1)); + else + tmpFrameHere = squeeze(thisMovie(:,:,1)); + end + imagesc() box off; dispStr = [num2str(fileNumToRun) '/' num2str(nFilesToRun) ': ' 10 thisDirDispStr]; axis image; colormap gray; @@ -774,7 +858,9 @@ inputMovieF0 = []; % to improve memory usage, edit the movie in loops, at least until this is made object oriented. for optionIdx = analysisOptionsIdx - thisMovie = single(thisMovie); + if options.runMatfile==0 + thisMovie = single(thisMovie); + end optionName = analysisOptionList{optionIdx}; % Update the save string based on the analysis about to be run. @@ -793,7 +879,7 @@ %% ADD BACK DROPPED FRAMES % try % if strcmp(optionName,'downsampleTime') - % subfxnAddDroppedFrames(); + % nestedfxn_addDroppedFrames(); % end % catch err % % save the location of the downsampled dfof for PCA-ICA identification @@ -804,9 +890,9 @@ try switch optionName case 'fixDropFrames' - subfxnAddDroppedFrames(); + nestedfxn_addDroppedFrames(); case 'turboreg' - subfxnPlotMotionCorrectionMetric('start'); + subfxn_plotMotionCorrectionMetric('start'); pxToCropAll = 0; ResultsOutOriginal = {}; @@ -817,7 +903,7 @@ % Miji; manageMiji('startStop','start'); end - turboregInputMovie(); + subfxn_turboregInputMovie(); % Save output of translation. save([thisProcessingDir filesep currentDateTimeStr '_turboregTranslationOutput.mat'],'ResultsOutOriginal'); @@ -827,16 +913,20 @@ end % playMovie(thisMovie); if options.turboreg.numTurboregIterations>1 - pxToCropTmp = getCropValues(); + pxToCropTmp = subfxn_getCropValues(); pxToCropAll = max([pxToCropAll pxToCropTmp]); fprintf('pxToCropAll: %d\n',pxToCropAll); end end % Get the amount of motion and hence amount to crop, directly from turboreg output - % gg = cellfun(@(z) cell2mat(cellfun(@(y) cell2mat(cellfun(@(x) max(abs(x.Translation)),y,'UniformOutput',false)),z,'UniformOutput',false)),ResultsOutOriginal,'UniformOutput',false); - gg = cellfun(@(z) cell2mat(cellfun(@(y) cell2mat(cellfun(@(x) max(ceil(abs(x.Translation))),y,'UniformOutput',false)),z,'UniformOutput',false)),ResultsOutOriginal,'UniformOutput',false); - pxToCropAllTmp = ceil(max(sum(abs(cat(1,gg{:})),1),[],'omitnan')); + try + % gg = cellfun(@(z) cell2mat(cellfun(@(y) cell2mat(cellfun(@(x) max(abs(x.Translation)),y,'UniformOutput',false)),z,'UniformOutput',false)),ResultsOutOriginal,'UniformOutput',false); + gg = cellfun(@(z) cell2mat(cellfun(@(y) cell2mat(cellfun(@(x) max(ceil(abs(x.Translation))),y,'UniformOutput',false)),z,'UniformOutput',false)),ResultsOutOriginal,'UniformOutput',false); + pxToCropAllTmp = ceil(max(sum(abs(cat(1,gg{:})),1),[],'omitnan')); + catch + pxToCropAllTmp = 0; + end fprintf('pxToCropAllTmp: %d\n',pxToCropAllTmp); if pxToCropAllTmp>pxToCropAll disp('Adjusting pxToCropAll') @@ -918,47 +1008,47 @@ clear tmpCropMovie; end - subfxnPlotMotionCorrectionMetric('end'); + subfxn_plotMotionCorrectionMetric('end'); case 'crop' if exist('pxToCropAll','var')==1&&pxToCropAll~=0 if pxToCropAll~=0 if pxToCropAll500 ostruct.movieFrames{fileNum} = 500; else @@ -1122,7 +1235,7 @@ % if options.inputPCAICA==0 % [ostruct options] = getPcaIcaParams(ostruct,options) % end - [ostruct, options] = playOutputMovies(ostruct,options); + [ostruct, options] = localfxn_playOutputMovies(ostruct,options); toc(startTime) @@ -1132,7 +1245,7 @@ disp(['Changing' ciapkg.pkgName 'input HDF5 dataset name "' obj.inputDatasetName '"->"' obj.outputDatasetName '"']) obj.inputDatasetName = obj.outputDatasetName; - function mouseWheelChange(hObject, callbackdata, handles) + function nestedfxn_mouseWheelChange(hObject, callbackdata, handles) % Change keyIn to force while loop to exit, need for certain commands. keyIn = 0; @@ -1142,7 +1255,7 @@ function mouseWheelChange(hObject, callbackdata, handles) set(gcf,'Tag','next') end end - function subfxn_resetSetpsMenu(src,event) + function nestedfxn_resetSetpsMenu(src,event) % analysisOptionListOrig = analysisOptionList; % defaultChoiceListOrig = defaultChoiceList; analysisOptionListStr = analysisOptionListOrig; @@ -1156,7 +1269,7 @@ function subfxn_resetSetpsMenu(src,event) hListboxS.String = analysisOptionListStr; hListboxS.Value = defaultChoiceIdx(end); end - function subfxn_highlightDefault(src,event) + function nestedfxn_highlightDefault(src,event) % analysisOptionListOrig = analysisOptionList; % defaultChoiceListOrig = defaultChoiceList; % analysisOptionListStr = analysisOptionListOrig; @@ -1179,7 +1292,7 @@ function subfxn_highlightDefault(src,event) hListbox.Value = defaultChoiceIdx; hListboxS.Value = defaultChoiceIdx(end); end - function subfxn_analysisOutputMenuChange(src,event) + function nestedfxn_analysisOutputMenuChange(src,event) % analysisOptionListOrig = analysisOptionList; % defaultChoiceListOrig = defaultChoiceList; % analysisOptionListStr = analysisOptionListOrig; @@ -1210,7 +1323,7 @@ function subfxn_analysisOutputMenuChange(src,event) % defaultSaveList = analysisOptionList{analysisOptionsIdx(end)}; % defaultSaveIdx = find(ismember(analysisOptionList,defaultSaveList)); end - function subfxn_analysisOutputMenuChange2(src,event,PERMORDER) + function nestedfxn_analysisOutputMenuChange2(src,event,PERMORDER) % analysisOptionListOrig = analysisOptionList; % defaultChoiceListOrig = defaultChoiceList; % analysisOptionListStr = analysisOptionListOrig; @@ -1240,16 +1353,42 @@ function subfxn_analysisOutputMenuChange2(src,event,PERMORDER) % defaultSaveList = analysisOptionList{analysisOptionsIdx(end)}; % defaultSaveIdx = find(ismember(analysisOptionList,defaultSaveList)); end - function subfxn_closeOptions(src,event) + function nestedfxn_closeOptions(src,event) delete(emptyBox); end - function subfxn_closeOptionsKey(src,event) + function nestedfxn_closeOptionsKey(src,event) if event.Key=="return" delete(emptyBox) end end + function nestedfxn_fixErrorFrames() + % Fixes frames in which the camera gives out garbled or improperly formatted data. + + nRegions = 4; + nFramesHere = size(thisMovie,3); + metrixMatrix = zeros([nRegions nFrames]); + + for subregionNo = 1:nRegions + % Select sub-regions to use for analysis. + + + % Calculate metric (e.g. difference, correlation, etc.) between + + + % Z-score metric by normalizing across all frames. + end + + + % Use threshold to determine which - function subfxnAddDroppedFrames() + + % Calculate replacement frame, e.g. mean + + + % Replace frames. + + end + function nestedfxn_addDroppedFrames() % TODO: to make this proper, need to verify that the log file names match those of movie files display(repmat('-',1,7)); @@ -1406,7 +1545,7 @@ function subfxnAddDroppedFrames() % end end - function downsampleTimeInputMovie() + function subfxn_downsampleTimeInputMovie() options.downsampleZ = []; options.waitbarOn = 1; % thisMovie = single(thisMovie); @@ -1436,7 +1575,7 @@ function downsampleTimeInputMovie() reverseStr = cmdWaitbar(frame,downY,reverseStr,'inputStr','temporally downsampling matrix'); end end - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); % reverseStr = ''; % for frame = (downZ+1):size(thisMovie,3) % thisMovie(:,:,1) = []; @@ -1449,12 +1588,12 @@ function downsampleTimeInputMovie() clear thisMovie; thisMovie = thisMovieTmp; clear thisMovieTmp; - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); drawnow; % ===================== end - function downsampleSpaceInputMovie() + function subfxn_downsampleSpaceInputMovie() % Nested function to downsample input movie in space options.downsampleZ = []; options.waitbarOn = 1; @@ -1492,7 +1631,7 @@ function downsampleSpaceInputMovie() end end thisMovie = thisMovie(1:downX,1:downY,:); - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); if exist('turboRegCoords','var') % Adjust crop coordinates if downsampling in space takes place before turboreg @@ -1510,7 +1649,7 @@ function downsampleSpaceInputMovie() % ===================== end - function dfofInputMovie() + function subfxn_dfofInputMovie() % dfof must have positive values % thisMovieMin = nanmin(thisMovie(:)); if strcmp(options.turboreg.filterBeforeRegister,'bandpass') @@ -1642,8 +1781,9 @@ function dfofInputMovie() end % ===================== end - function subfxnPlotMotionCorrectionMetric(motionState) + function subfxn_plotMotionCorrectionMetric(motionState) try + disp('Calculating and plotting correlation metric') colorList = hsv(length(obj.inputFolders)); meanG = mean(thisMovie,3); @@ -1651,7 +1791,12 @@ function subfxnPlotMotionCorrectionMetric(motionState) corrMetric2 = NaN([1 size(thisMovie,3)]); cc = turboRegCoords{fileNum}{movieNo}; meanG_cc = meanG(cc(2):cc(4),cc(1):cc(3)); - for i =1:size(thisMovie,3) + fprintf('Calculating correlation metric: ') + nFramesThis = size(thisMovie,3); + for i = 1:nFramesThis + if mod(i,500)==0 + fprintf('%d | ',round((i/nFramesThis)*100)); + end thisFrame_cc = thisMovie(cc(2):cc(4),cc(1):cc(3),i); corrMetric(i) = corr2(meanG_cc,thisFrame_cc); corrMetric2(i) = corr(meanG_cc(:),thisFrame_cc(:),'Type','Spearman'); @@ -1706,15 +1851,17 @@ function subfxnPlotMotionCorrectionMetric(motionState) disp(repmat('@',1,7)) end end - function turboregInputMovie() + function subfxn_turboregInputMovie() + disp(['Motion correction method: ' options.turboreg.mcMethod]) % number of frames to subset subsetSize = options.turboregNumFramesSubset; movieLength = size(thisMovie,3); numSubsets = ceil(movieLength/subsetSize)+1; subsetList = round(linspace(1,movieLength,numSubsets)); - display(['registering sublists: ' num2str(subsetList)]); + disp(['registering sublists: ' num2str(subsetList)]); + disp(options.turboreg.mcMethod) % convert movie to single for turboreg - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); % thisMovie = single(thisMovie); % get reference frame before subsetting, so won't change % thisMovieRefFrame = squeeze(thisMovie(:,:,options.refCropFrame)); @@ -1731,12 +1878,13 @@ function turboregInputMovie() display(repmat('$',1,7)) if thisSet==nSubsets movieSubset = subsetStartIdx:subsetEndIdx; - display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx) ' ' num2str(thisSet) '/' num2str(nSubsets)]) + % display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx) ' ' num2str(thisSet) '/' num2str(nSubsets)]) else movieSubset = subsetStartIdx:(subsetEndIdx-1); - display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx-1) ' ' num2str(thisSet) '/' num2str(nSubsets)]) + % display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx-1) ' ' num2str(thisSet) '/' num2str(nSubsets)]) end - display(repmat('$',1,7)) + disp([num2str(movieSubset(1)) '-' num2str(movieSubset(end)) ' ' num2str(thisSet) '/' num2str(nSubsets)]) + disp(repmat('$',1,7)) %run with altered defaults % ioptions.Levels = 2; % ioptions.Lastlevels = 1; @@ -1744,6 +1892,10 @@ function turboregInputMovie() % ioptions.minGain=0.0; % ioptions.SmoothX = 80; % ioptions.SmoothY = 80; + + % Set the motion correction algorithm + ioptions.mcMethod = options.turboreg.mcMethod; + ioptions.turboregRotation = options.turboreg.turboregRotation; ioptions.RegisType = options.turboreg.RegisType; ioptions.parallel = options.turboreg.parallel; @@ -1785,6 +1937,10 @@ function turboregInputMovie() % If multiple frames requested, only use the 1st ioptions.refFrame = options.refCropFrame(1); ioptions.refFrameMatrix = thisMovieRefFrame; + + ioptions.optsNoRMCorre = optsNoRMCorre; + ioptions.displayOptions = 1; + ioptions % for frameDftNo = movieSubset % refFftFrame = fft2(thisMovieRefFrame); % regFftFrame = fft2(squeeze(thisMovie(:,:,frameDftNo))); @@ -1806,7 +1962,7 @@ function turboregInputMovie() % dt=whos('VARIABLE_YOU_CARE_ABOUT'); MB=dt.bytes*9.53674e-7; % thisMovie(:,:,movieSubset) = turboregMovie(thisMovie(:,:,movieSubset),'options',ioptions); % j = whos('turboregThisMovie');j.bytes=j.bytes*9.53674e-7;j - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); % [thisMovie(:,:,movieSubset), ResultsOutOriginal{thisSet}] = turboregMovie(thisMovie(:,:,movieSubset),'options',ioptions); @@ -1830,7 +1986,7 @@ function turboregInputMovie() % [tmpMovieNoFilter, ~] = turboregMovie(tmpMovieNoFilter,'options',ioptions); % end % Crop movie - tmpMovieNoFilter = cropInputMovieSlice(tmpMovieNoFilter,options,ResultsOutOriginal); + tmpMovieNoFilter = localfxn_cropInputMovieSlice(tmpMovieNoFilter,options,ResultsOutOriginal); % Add filtered movie to overall registered subset thisMovie(:,:,movieSubset) = tmpMovieWithFilter; @@ -1891,7 +2047,7 @@ function turboregInputMovie() end clear ioptions; end - function tmpPxToCrop = getCropValues() + function tmpPxToCrop = subfxn_getCropValues() % Get values to use to add border and eliminate edge movement due to motion correction thisMovieMinMask = zeros([size(thisMovie,1) size(thisMovie,2)]); options.turboreg.registrationFxn @@ -1918,7 +2074,7 @@ function turboregInputMovie() tmpPxToCrop = max([topVal bottomVal leftVal rightVal]); display(['[topVal bottomVal leftVal rightVal]: ' num2str([topVal bottomVal leftVal rightVal])]) end - function cropInputMovie() + function subfxn_cropInputMovie() % turboreg outputs 0s where movement goes off the screen thisMovieMinMask = zeros([size(thisMovie,1) size(thisMovie,2)]); options.turboreg.registrationFxn @@ -1976,17 +2132,17 @@ function cropInputMovie() if tmpPxToCrop~=0 if tmpPxToCrop=size(thisMovie,1) % coords(1) = pxToCropPreprocess; %xmin % coords(2) = pxToCropPreprocess; %ymin @@ -2021,7 +2177,7 @@ function cropMatrixPreProcess(pxToCropPreprocess) % set bottom rows to NaN thisMovie(bottomRowCrop:end,1:end,:) = NaN; end - function medianFilterInputMovie() + function subfxn_medianFilterInputMovie() % number of frames to subset subsetSize = options.turboregNumFramesSubset; movieLength = size(thisMovie,3); @@ -2029,7 +2185,7 @@ function medianFilterInputMovie() subsetList = round(linspace(1,movieLength,numSubsets)); display(['registering sublists: ' num2str(subsetList)]); % convert movie to single for turboreg - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); % get reference frame before subsetting, so won't change nSubsets = (length(subsetList)-1); for thisSet = 1:nSubsets @@ -2045,13 +2201,13 @@ function medianFilterInputMovie() display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx-1) ' ' num2str(thisSet) '/' num2str(nSubsets)]) end display(repmat('$',1,7)) - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); thisMovie(:,:,movieSubset) = normalizeMovie(thisMovie(:,:,movieSubset),'normalizationType','medianFilter','medianFilterNeighborhoodSize',options.turboreg.medianFilterSize); toc(subsetStartTime) end % thisMovie = normalizeMovie(thisMovie,'normalizationType','medianFilter'); end - function detrendInputMovie() + function subfxn_detrendInputMovie() % Plot trend before and after openFigure(figNoList.detrend); @@ -2066,8 +2222,22 @@ function detrendInputMovie() disp(repmat('@',1,7)) end + frameMeanInputMovie = squeeze(mean(thisMovie,[1 2],'omitnan')); + + trendVals = frameMeanInputMovie - detrend(frameMeanInputMovie,options.turboreg.detrendDegree); + + % meanInputMovie = detrend(frameMeanInputMovie,0); + meanInputMovie = mean(frameMeanInputMovie,'omitnan'); + + nFramesToNormalize = size(thisMovie,3); + + parfor frame = 1:nFramesToNormalize + thisFrame = thisMovie(:,:,frame); + thisMovie(:,:,frame) = thisFrame - trendVals(frame) + meanInputMovie; + end + % Takes an input movie and removes an underlying change in mean frame fluorescence. - thisMovie = normalizeMovie(thisMovie,'normalizationType','detrend','detrendDegree',options.turboreg.detrendDegree); + % thisMovie = normalizeMovie(thisMovie,'normalizationType','detrend','detrendDegree',options.turboreg.detrendDegree); openFigure(figNoList.detrend); try @@ -2083,7 +2253,7 @@ function detrendInputMovie() disp(repmat('@',1,7)) end end - function spatialFilterInputMovie() + function subfxn_spatialFilterInputMovie() % number of frames to subset subsetSize = options.turboregNumFramesSubset; movieLength = size(thisMovie,3); @@ -2091,7 +2261,7 @@ function spatialFilterInputMovie() subsetList = round(linspace(1,movieLength,numSubsets)); display(['filtering sublists: ' num2str(subsetList)]); % convert movie to single for turboreg - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); % get reference frame before subsetting, so won't change nSubsets = (length(subsetList)-1); for thisSet = 1:nSubsets @@ -2107,7 +2277,7 @@ function spatialFilterInputMovie() display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx-1) ' ' num2str(thisSet) '/' num2str(nSubsets)]) end display(repmat('$',1,7)) - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); % thisMovie(:,:,movieSubset) = normalizeMovie(thisMovie(:,:,movieSubset),'normalizationType','medianFilter','medianFilterNeighborhoodSize',options.turboreg.medianFilterSize); @@ -2133,7 +2303,7 @@ function spatialFilterInputMovie() end % thisMovie = normalizeMovie(thisMovie,'normalizationType','medianFilter'); end - function stripeRemovalInputMovie() + function subfxn_stripeRemovalInputMovie() % number of frames to subset subsetSize = options.turboregNumFramesSubset; movieLength = size(thisMovie,3); @@ -2141,7 +2311,7 @@ function stripeRemovalInputMovie() subsetList = round(linspace(1,movieLength,numSubsets)); display(['Stripe removal sublists: ' num2str(subsetList)]); % convert movie to single for turboreg - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); % get reference frame before subsetting, so won't change nSubsets = (length(subsetList)-1); for thisSet = 1:nSubsets @@ -2157,7 +2327,7 @@ function stripeRemovalInputMovie() display([num2str(subsetStartIdx) '-' num2str(subsetEndIdx-1) ' ' num2str(thisSet) '/' num2str(nSubsets)]) end display(repmat('$',1,7)) - subfxn_dispMovieSize(thisMovie); + localfxn_dispMovieSize(thisMovie); thisMovie(:,:,movieSubset) = removeStripsFromMovie(single(thisMovie(:,:,movieSubset)),'stripOrientation',options.turboreg.stripOrientationRemove,'meanFilterSize',options.turboreg.stripSize,'freqLowExclude',options.turboreg.stripfreqLowExclude,'bandpassType',options.turboreg.stripfreqBandpassType,'freqHighExclude',options.turboreg.stripfreqHighExclude,'waitbarOn',1); @@ -2165,14 +2335,14 @@ function stripeRemovalInputMovie() end % thisMovie = normalizeMovie(thisMovie,'normalizationType','medianFilter'); end - function movieProjectionsInputMovie() + function subfxn_movieProjectionsInputMovie() % get the max projection % get the mean projection end - function fftHighpassInputMovie() + function subfxn_fftHighpassInputMovie() % do a highpass filter ioptions.normalizationType = 'fft'; ioptions.freqLow = 7; @@ -2192,7 +2362,7 @@ function fftHighpassInputMovie() [thisMovie] = normalizeVector(thisMovie,'normRange','zeroToOne'); clear ioptions; end - function fftLowpassInputMovie() + function subfxn_fftLowpassInputMovie() % do a lowpass filter ioptions.normalizationType = 'fft'; ioptions.freqLow = 1; @@ -2231,7 +2401,7 @@ function fftLowpassInputMovie() end end -function [turboRegCoords] = turboregCropSelection(options,folderList) +function [turboRegCoords] = localfxn_turboregCropSelection(options,folderList) % Biafra Ahanonu % 2013.11.10 [19:28:53] @@ -2267,7 +2437,7 @@ function fftLowpassInputMovie() movieList = regexp(folderList{fileNum},',','split'); % movieList = movieList{1}; movieList = getFileList(movieList, options.fileFilterRegexp); - [movieList] = removeUnsupportedFiles(movieList,options); + [movieList] = localfxn_removeUnsupportedFiles(movieList,options); if isempty(movieList) continue; end @@ -2290,7 +2460,7 @@ function fftLowpassInputMovie() display([num2str(fileNumIdx) '/' num2str(nFilesToRun) ': ' thisDir]) disp(['options.fileFilterRegexp: ' options.fileFilterRegexp]) movieList = getFileList(thisDir, options.fileFilterRegexp); - [movieList] = removeUnsupportedFiles(movieList,options); + [movieList] = localfxn_removeUnsupportedFiles(movieList,options); cellfun(@disp,movieList); inputFilePath = movieList{movieNo}; if nMovies==1 @@ -2306,12 +2476,14 @@ function fftLowpassInputMovie() end if length(frameToGrabHere)==1&&options.turboreg.treatMoviesAsContinuousSwitch==0 - thisFrame = ciapkg.io.readFrame(inputFilePath,frameToGrabHere); + thisFrame = ciapkg.io.readFrame(inputFilePath,frameToGrabHere,'inputDatasetName',options.datasetName); + elseif length(frameToGrabHere)==1&&options.turboreg.treatMoviesAsContinuousSwitch==1&&nMovies==1 + thisFrame = ciapkg.io.readFrame(inputFilePath,frameToGrabHere,'inputDatasetName',options.datasetName); else % for zFrame = 1:length(frameToGrabHere) % end - thisFrame = loadMovieList(inputFilePath,'convertToDouble',0,'frameList',frameToGrabHere,'inputDatasetName',options.datasetName,'treatMoviesAsContinuous',options.turboreg.treatMoviesAsContinuousSwitch,'loadSpecificImgClass','single'); + thisFrame = ciapkg.io.loadMovieList(inputFilePath,'convertToDouble',0,'frameList',frameToGrabHere,'inputDatasetName',options.datasetName,'treatMoviesAsContinuous',options.turboreg.treatMoviesAsContinuousSwitch,'loadSpecificImgClass','single'); end if size(thisFrame,3)>1 @@ -2328,9 +2500,12 @@ function fftLowpassInputMovie() colormap gray; title(titleStr) box off; + set(gcf,'color',[0 0 0]); + set(gca,'color',[0 0 0]); + ciapkg.view.changeFont('none','fontColor','w'); set(0,'DefaultTextInterpreter','none'); % ciapkg.overloaded.suptitle([num2str(fileNumIdx) '\' num2str(nFilesToRun) ': ' 10 strrep(thisDir,'\','/')],'fontSize',12,'plotregion',0.9,'titleypos',0.95); - uicontrol('Style','Text','String',[num2str(fileNumIdx) '\' num2str(nFilesToRun) ': ' strrep(thisDir,'\','/')],'Units','normalized','Position',[0.1 0.9 0.8 0.10],'BackgroundColor','white','HorizontalAlignment','Center'); + uicontrol('Style','Text','String',[num2str(fileNumIdx) '\' num2str(nFilesToRun) ': ' strrep(thisDir,'\','/')],'Units','normalized','Position',[0.1 0.9 0.8 0.10],'BackgroundColor','black','ForegroundColor','white','HorizontalAlignment','Center'); set(0,'DefaultTextInterpreter','latex'); % subplot(1,2,1);imagesc(thisFrame); axis image; colormap gray; title('Click to drag-n-draw region. Double-click region to continue.') @@ -2340,17 +2515,17 @@ function fftLowpassInputMovie() switch applyPreviousTurboreg case -1 %'NO | do not duplicate coords across multiple folders' % p = round(getrect); - h = subfxn_getImRect(titleStr); + h = localfxn_getImRect(titleStr); p = round(wait(h)); case 0 %'YES | duplicate coords across multiple folders' % p = round(getrect); - h = subfxn_getImRect(titleStr); + h = localfxn_getImRect(titleStr); p = round(wait(h)); coordsStructure.(fileInfo.subject) = p; case -2 %'YES | duplicate coords if subject the same' if ~any(strcmp(fileInfo.subject,fieldnames(coordsStructure))) % p = round(getrect); - h = subfxn_getImRect(titleStr); + h = localfxn_getImRect(titleStr); p = round(wait(h)); coordsStructure.(fileInfo.subject) = p; else @@ -2392,7 +2567,15 @@ function fftLowpassInputMovie() % Display the subsetted image with appropriate axis ratio [~, ~] = openFigure(9, ''); - subplot(1,2,2);imagesc(thisFrameCropped); axis image; colormap gray; title('cropped region');drawnow; + subplot(1,2,2); + imagesc(thisFrameCropped); + axis image; + colormap gray; + title('cropped region'); + set(gcf,'color',[0 0 0]); + set(gca,'color',[0 0 0]); + ciapkg.view.changeFont('none','fontColor','w'); + drawnow; if applyPreviousTurboreg==0 if runAllFolders==1 @@ -2425,14 +2608,14 @@ function fftLowpassInputMovie() end end end -function h = subfxn_getImRect(titleStr) +function h = localfxn_getImRect(titleStr) h = imrect(gca); addNewPositionCallback(h,@(p) title([titleStr 10 mat2str(p,3)])); fcn = makeConstrainToRectFcn('imrect',get(gca,'XLim'),get(gca,'YLim')); setPositionConstraintFcn(h,fcn); end % function [ostruct options] = getPcaIcaParams(ostruct,options) -function [ostruct, options] = playOutputMovies(ostruct,options) +function [ostruct, options] = localfxn_playOutputMovies(ostruct,options) import ciapkg.api.* % import CIAtah functions in ciapkg package API. nFiles = length(ostruct.savedFilePaths); @@ -2440,58 +2623,67 @@ function fftLowpassInputMovie() movieFrameList = {}; numFramesPerPart = 50; numParts = 10; - disp('pre-allocating movies to display...') - thisMovieArray = {}; - for fileNum=1:nFiles - try - disp('+++++++') - if isempty(ostruct.savedFilePaths{fileNum}) - disp('no movie!') - % display([num2str(fileNum) '/' num2str(nFiles) ' skipping: ' ostruct.savedFilePaths{fileNum}]); - continue; - else - pathInfo = [num2str(fileNum) '/' num2str(nFiles) ': ' ostruct.savedFilePaths{fileNum}]; - % display(pathInfo); - fprintf('%d/%d:%s\n',fileNum,nFiles,ostruct.savedFilePaths{fileNum}) - end - movieList = {ostruct.savedFilePaths{fileNum}}; - % movieDims = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList,'getMovieDims',1); - movieDims = loadMovieList(movieList,'convertToDouble',0,'frameList',[],'getMovieDims',1,'inputDatasetName',options.outputDatasetName); + switch options.videoPlayer + case 'imagej' + disp('pre-allocating movies to display...') + thisMovieArray = {}; + for fileNum=1:nFiles + try + disp('+++++++') + if isempty(ostruct.savedFilePaths{fileNum}) + disp('no movie!') + % display([num2str(fileNum) '/' num2str(nFiles) ' skipping: ' ostruct.savedFilePaths{fileNum}]); + continue; + else + pathInfo = [num2str(fileNum) '/' num2str(nFiles) ': ' ostruct.savedFilePaths{fileNum}]; + % display(pathInfo); + fprintf('%d/%d:%s\n',fileNum,nFiles,ostruct.savedFilePaths{fileNum}) + end + movieList = {ostruct.savedFilePaths{fileNum}}; - movieFrames = movieDims.three; - if movieFrames>500 - movieFrames = 500; - else - % ostruct.movieFrames{fileNum} = movieFrames; - end - movieFrameList{fileNum} = movieFrames; + % movieDims = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList,'getMovieDims',1); + movieDims = loadMovieList(movieList,'convertToDouble',0,'frameList',[],'getMovieDims',1,'inputDatasetName',options.outputDatasetName); - % options.frameList = [1:ostruct.movieFrames{fileNum}]; - options.frameList = [1:movieFrames]; + movieFrames = movieDims.three; + if movieFrames>500 + movieFrames = 500; + else + % ostruct.movieFrames{fileNum} = movieFrames; + end + movieFrameList{fileNum} = movieFrames; - % get the movie - % thisMovieArray{fileNum} = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList); - thisMovieArray{fileNum} = loadMovieList(movieList,'convertToDouble',0,'frameList',[],'loadMovieInEqualParts',[numParts numFramesPerPart],'inputDatasetName',options.outputDatasetName); - % thisMovieArray{fileNum} = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList,'loadMovieInEqualParts',[numParts numFramesPerPart]); + % options.frameList = [1:ostruct.movieFrames{fileNum}]; + options.frameList = [1:movieFrames]; - catch err - thisMovieArray{fileNum} = []; - display(repmat('@',1,7)) - disp(getReport(err,'extended','hyperlinks','on')); - display(repmat('@',1,7)) - end - end - % inputdlg({'press OK to view a snippet of analyzed movies'},'...',1); - % Miji; - % MIJ.start - manageMiji('startStop','start'); + % get the movie + % thisMovieArray{fileNum} = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList); + thisMovieArray{fileNum} = loadMovieList(movieList,'convertToDouble',0,'frameList',[],'loadMovieInEqualParts',[numParts numFramesPerPart],'inputDatasetName',options.outputDatasetName,'largeMovieLoad',options.turboreg.largeMovieLoad); + % thisMovieArray{fileNum} = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList,'loadMovieInEqualParts',[numParts numFramesPerPart]); - if isempty(thisMovieArray)||all(cellfun(@isempty,thisMovieArray)) - uiwait(ciapkg.overloaded.msgbox('No movies, check that processing ran successfully.','Success','modal')); - else - uiwait(ciapkg.overloaded.msgbox('Press OK to view a snippet of analyzed movies','Success','modal')); + catch err + thisMovieArray{fileNum} = []; + display(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + display(repmat('@',1,7)) + end + end + % inputdlg({'press OK to view a snippet of analyzed movies'},'...',1); + % Miji; + % MIJ.start + manageMiji('startStop','start'); + + if isempty(thisMovieArray)||all(cellfun(@isempty,thisMovieArray)) + uiwait(ciapkg.overloaded.msgbox('No movies, check that processing ran successfully.','Success','modal')); + else + uiwait(ciapkg.overloaded.msgbox('Press OK to view a snippet of analyzed movies','Success','modal')); + end + case 'matlab' + % Do nothing + otherwise + % Do nothing end + % ask user for estimate of nPCs and nICs for fileNum = 1:nFiles try @@ -2505,48 +2697,68 @@ function fftLowpassInputMovie() display(pathInfo); end - % get the list of movies - movieList = {ostruct.savedFilePaths{fileNum}}; + trueFileNum = ostruct.fileNumList{fileNum}; - % options.frameList = [1:ostruct.movieFrames{fileNum}]; - options.frameList = [1:movieFrameList{fileNum}]; + switch options.videoPlayer + case 'matlab' + thisFilePath = ostruct.savedFilePaths{fileNum}; + [~,thisFileTitle,thisFileTitleExt] = fileparts(thisFilePath); + thisFileTitle = [thisFileTitle thisFileTitleExt]; + titleStr = sprintf('%d/%d (%d/%d): \n %s \n %s',trueFileNum,length(ostruct.folderList),fileNum,nFiles,ostruct.folderList{trueFileNum},thisFileTitle); - % get the movie - % thisMovie = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList); - thisMovie = thisMovieArray{fileNum}; + if fileNum==1 + ciapkg.overloaded.msgbox('Press E in movie GUI to move onto next movie, close this box to continue','Success','modal') + end + % playMovie(thisMovie); - if isempty(thisMovie) - continue; - end + % Play the movie from disk, better preview. + playMovie(thisFilePath,'extraTitleText',titleStr,'colormapColor','gray'); + case 'imagej' + % Try with Miji, else use built-in player + try + titleStr = sprintf('%d/%d (%d/%d): %s',trueFileNum,length(ostruct.folderList),fileNum,nFiles,ostruct.folderList{trueFileNum}); - trueFileNum = ostruct.fileNumList{fileNum}; + % get the list of movies + movieList = {ostruct.savedFilePaths{fileNum}}; - % Try with Miji, else use built-in player - try - % playMovie(thisMovie,'fps',120,'extraTitleText',[10 pathInfo]); - % MIJ.createImage([num2str(fileNum) '/' num2str(length(ostruct.folderList)) ': ' ostruct.folderList{fileNum}],thisMovie, true); - % [num2str(trueFileNum) '/' num2str(length(ostruct.folderList)) ': ' ostruct.folderList{trueFileNum}] - titleStr = sprintf('%d/%d (%d/%d): %s',trueFileNum,length(ostruct.folderList),fileNum,nFiles,ostruct.folderList{trueFileNum}); - ciapkg.overloaded.msgbox('Click movie to open next dialog box.','Success','normal') - MIJ.createImage(titleStr,thisMovie, true); - if size(thisMovie,1)<300 - % for foobar=1:2; MIJ.run('In [+]'); end - for foobar=1:1; MIJ.run('In [+]'); end - end - for foobar=1:2; MIJ.run('Enhance Contrast','saturated=0.35'); end - MIJ.run('Start Animation [\]'); - uiwait(ciapkg.overloaded.msgbox('press OK to move onto next movie','Success','modal')); - % MIJ.run('Close All Without Saving'); - manageMiji('startStop','closeAllWindows'); - catch err - disp(repmat('@',1,7)) - disp(getReport(err,'extended','hyperlinks','on')); - disp(repmat('@',1,7)) - ciapkg.overloaded.msgbox('Press E in movie GUI to move onto next movie, close this box to continue','Success','modal') - playMovie(thisMovie); + % options.frameList = [1:ostruct.movieFrames{fileNum}]; + options.frameList = [1:movieFrameList{fileNum}]; - % Play the movie from disk, better preview. - playMovie(ostruct.savedFilePaths{fileNum}); + % get the movie + % thisMovie = loadMovieList(movieList,'convertToDouble',0,'frameList',options.frameList); + thisMovie = thisMovieArray{fileNum}; + + if isempty(thisMovie) + continue; + end + + % playMovie(thisMovie,'fps',120,'extraTitleText',[10 pathInfo]); + % MIJ.createImage([num2str(fileNum) '/' num2str(length(ostruct.folderList)) ': ' ostruct.folderList{fileNum}],thisMovie, true); + % [num2str(trueFileNum) '/' num2str(length(ostruct.folderList)) ': ' ostruct.folderList{trueFileNum}] + + ciapkg.overloaded.msgbox('Click movie to open next dialog box.','Success','normal') + MIJ.createImage(titleStr,thisMovie, true); + if size(thisMovie,1)<300 + % for foobar=1:2; MIJ.run('In [+]'); end + for foobar=1:1; MIJ.run('In [+]'); end + end + for foobar=1:2; MIJ.run('Enhance Contrast','saturated=0.35'); end + MIJ.run('Start Animation [\]'); + uiwait(ciapkg.overloaded.msgbox('press OK to move onto next movie','Success','modal')); + % MIJ.run('Close All Without Saving'); + manageMiji('startStop','closeAllWindows'); + catch err + disp(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + disp(repmat('@',1,7)) + ciapkg.overloaded.msgbox('Press E in movie GUI to move onto next movie, close this box to continue','Success','modal') + playMovie(thisMovie); + + % Play the movie from disk, better preview. + playMovie(ostruct.savedFilePaths{fileNum}); + end + otherwise + disp('Wrong video player option, skipping...') end if options.askForPCICs==1 @@ -2569,10 +2781,18 @@ function fftLowpassInputMovie() display(repmat('@',1,7)) end end - % MIJ.exit; - manageMiji('startStop','exit'); + + switch options.videoPlayer + case 'matlab' + % Do nothing + case 'imagej' + % MIJ.exit; + manageMiji('startStop','exit'); + otherwise + % Do nothing + end end -function inputMovie = cropInputMovieSlice(inputMovie,options,ResultsOutOriginal) +function inputMovie = localfxn_cropInputMovieSlice(inputMovie,options,ResultsOutOriginal) import ciapkg.api.* % import CIAtah functions in ciapkg package API. % turboreg outputs 0s where movement goes off the screen @@ -2637,7 +2857,7 @@ function fftLowpassInputMovie() % set bottom rows to NaN inputMovie(bottomRowCrop:end,1:end,:) = NaN; end -function [movieList] = removeUnsupportedFiles(movieList,options) +function [movieList] = localfxn_removeUnsupportedFiles(movieList,options) import ciapkg.api.* % import CIAtah functions in ciapkg package API. % Reject anything not HDF5, TIF, AVI, or ISXD @@ -2657,32 +2877,36 @@ function fftLowpassInputMovie() end movieList = tmpMovieList; end -function [movieType, supported] = getMovieFileType(thisMoviePath) - % determine how to load movie, don't assume every movie in list is of the same type - supported = 1; - try - [~,~,ext] = fileparts(thisMoviePath); - catch - movieType = ''; - supported = 0; - return; - end - % files are assumed to be named correctly (lying does no one any good) - if strcmp(ext,'.h5')||strcmp(ext,'.hdf5') - movieType = 'hdf5'; - elseif strcmp(ext,'.tif')||strcmp(ext,'.tiff') - movieType = 'tiff'; - elseif strcmp(ext,'.avi') - movieType = 'avi'; - elseif strcmp(ext,'.isxd') - movieType = 'isxd'; - else - movieType = ''; - supported = 0; - end -end -function subfxn_dispMovieSize(thisMovie) +% function [movieType, supported] = getMovieFileType(thisMoviePath) +% % determine how to load movie, don't assume every movie in list is of the same type +% supported = 1; +% try +% [~,~,ext] = fileparts(thisMoviePath); +% catch +% movieType = ''; +% supported = 0; +% return; +% end +% % files are assumed to be named correctly (lying does no one any good) +% if strcmp(ext,'.h5')||strcmp(ext,'.hdf5') +% movieType = 'hdf5'; +% elseif strcmp(ext,'.tif')||strcmp(ext,'.tiff') +% movieType = 'tiff'; +% elseif strcmp(ext,'.avi') +% movieType = 'avi'; +% elseif strcmp(ext,'.isxd') +% movieType = 'isxd'; +% else +% movieType = ''; +% supported = 0; +% end +% end +function localfxn_dispMovieSize(thisMovie) j = whos('thisMovie'); j.bytes=j.bytes*9.53674e-7; display(['Movie dims: ' num2str(size(thisMovie)) ' | Movie size: ' num2str(j.bytes) 'Mb | ' num2str(j.size) ' | ' j.class]); +end +function localfxn_changeFigName(hFig,titleStr) + + set(hFig,'Name',[ciapkg.pkgName ': start-up GUI'],'NumberTitle','off') end \ No newline at end of file diff --git a/@ciatah/modelSaveImgToFile.m b/@ciatah/modelSaveImgToFile.m index 70a642c..ccb1f75 100644 --- a/@ciatah/modelSaveImgToFile.m +++ b/@ciatah/modelSaveImgToFile.m @@ -9,6 +9,8 @@ % changelog % 2021.08.10 [09:57:36] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.07.28 [17:32:57] - Update PaperPosition to auto to avoid error "Positioning Figure for ResizeFcn. Set PaperPositionMode to 'auto' (match figure screen size) to avoid resizing and this warning." + % 2022.09.14 [11:13:12] - Further change to PaperPositionMode to avoid errors. % TODO % @@ -60,10 +62,19 @@ if strcmp(class(thisFigNo),'char')&strcmp(thisFigNo,'current') else - set(thisFigNo,'PaperUnits',options.PaperUnits,'PaperPosition',options.PaperPosition) - % set(thisFigNo,'PaperUnits','inches','PaperPosition',[0 0 20 20]) - set(0,'CurrentFigure',thisFigNo) - % figure(thisFigNo) + try + % set(thisFigNo,'PaperUnits',options.PaperUnits,'PaperPosition',options.PaperPosition) + % set(thisFigNo,'PaperUnits',options.PaperUnits,'PaperPosition',options.PaperPosition) + % set(thisFigNo,'PaperUnits',options.PaperUnits,'PaperPosition','auto'); + set(thisFigNo,'PaperUnits',options.PaperUnits,'PaperPositionMode','auto'); + % set(thisFigNo,'PaperUnits','inches','PaperPosition',[0 0 20 20]) + set(0,'CurrentFigure',thisFigNo) + % figure(thisFigNo) + catch err + display(repmat('@',1,7)) + disp(getReport(err,'extended','hyperlinks','on')); + display(repmat('@',1,7)) + end end end diff --git a/@ciatah/modelVarsFromFiles.m b/@ciatah/modelVarsFromFiles.m index 67ef0b3..398c3f3 100644 --- a/@ciatah/modelVarsFromFiles.m +++ b/@ciatah/modelVarsFromFiles.m @@ -10,8 +10,10 @@ % changelog % 2017.01.14 [20:06:04] - support switched from [nSignals x y] to [x y nSignals] % 2021.08.10 [09:57:36] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.04.08 [16:41:27] - Enforce that NWB loading only looks for .nwb files and ensure that manual or automatic classifications are loaded when loading NWB files. + % 2022.04.09 [20:30:52] - Ensure when loading cell extraction CIAtah-style that only MAT files are loaded. % TODO - % ADD SUPPORT FOR EM ANALYSIS + % ADD SUPPORT FOR EM ANALYSIS - Done. import ciapkg.api.* % import CIAtah functions in ciapkg package API. @@ -20,7 +22,7 @@ % get options % options = getOptions(options,varargin); - % display(options) + % disp(options) % unpack options into current workspace % fn=fieldnames(options); % for i=1:length(fn) @@ -28,8 +30,8 @@ % end %======================== - display(repmat('#',1,21)) - display('loading files...') + disp(repmat('#',1,21)) + disp('loading files...') for figNo = [98 1996 1997 95 99] openFigure(figNo, ''); @@ -37,10 +39,6 @@ drawnow; signalExtractionMethod = obj.signalExtractionMethod; - % usrIdxChoiceStr = {'PCAICA','EM'}; - % [sel, ok] = listdlg('ListString',usrIdxChoiceStr); - % usrIdxChoiceList = {2,1}; - % signalExtractionMethod = usrIdxChoiceStr{sel}; optFieldnames = fieldnames(obj.filterImageOptions); if obj.guiEnabled==1 @@ -67,16 +65,16 @@ reportMidpoint = 0; end - [fileIdxArray idNumIdxArray nFilesToAnalyze nFiles] = obj.getAnalysisSubsetsToAnalyze(); + [fileIdxArray, idNumIdxArray, nFilesToAnalyze, nFiles] = obj.getAnalysisSubsetsToAnalyze(); for thisFileNumIdx = 1:nFilesToAnalyze try fileNum = fileIdxArray(thisFileNumIdx); obj.fileNum = fileNum; - display(repmat('=',1,21)) - % display([num2str(thisFileNumIdx) '/' num2str(nFilesToAnalyze) ' (' num2str(fileNum) '/' num2str(nFiles) '): ' obj.fileIDNameArray{obj.fileNum}]); + disp(repmat('=',1,21)) + % disp([num2str(thisFileNumIdx) '/' num2str(nFilesToAnalyze) ' (' num2str(fileNum) '/' num2str(nFiles) '): ' obj.fileIDNameArray{obj.fileNum}]); % nFolders = length(obj.dataPath); % for fileNum = 1:nFolders - % display(repmat('-',1,7)) + % disp(repmat('-',1,7)) % try obj.rawSignals{fileNum} = []; obj.rawImages{fileNum} = []; @@ -88,20 +86,21 @@ obj.validManual{fileNum} = []; obj.validAuto{fileNum} = []; if strmatch('#',obj.dataPath{fileNum}) - % display([num2str(fileNum) '/' num2str(nFolders) ' | skipping: ' obj.dataPath{fileNum}]); - % display([num2str(thisFileNumIdx) '/' num2str(nFilesToAnalyze) ' (' num2str(fileNum) '/' num2str(nFiles) ') | skipping: ' obj.fileIDNameArray{obj.fileNum}]); + % disp([num2str(fileNum) '/' num2str(nFolders) ' | skipping: ' obj.dataPath{fileNum}]); + % disp([num2str(thisFileNumIdx) '/' num2str(nFilesToAnalyze) ' (' num2str(fileNum) '/' num2str(nFiles) ') | skipping: ' obj.fileIDNameArray{obj.fileNum}]); fprintf('%d/%d (%d/%d) | skipping: %s\n',thisFileNumIdx,nFilesToAnalyze,fileNum,nFiles,obj.fileIDNameArray{obj.fileNum}); obj.rawSignals{fileNum} = []; obj.rawImages{fileNum} = []; continue; else - % display([num2str(fileNum) '/' num2str(nFolders) ': ' obj.dataPath{fileNum}]); + % disp([num2str(fileNum) '/' num2str(nFolders) ': ' obj.dataPath{fileNum}]); % obj.fileIDNameArray{obj.fileNum} - % display([num2str(thisFileNumIdx) '/' num2str(nFilesToAnalyze) ' (' num2str(fileNum) '/' num2str(nFiles) '): ' obj.fileIDNameArray{obj.fileNum}]); + % disp([num2str(thisFileNumIdx) '/' num2str(nFilesToAnalyze) ' (' num2str(fileNum) '/' num2str(nFiles) '): ' obj.fileIDNameArray{obj.fileNum}]); fprintf('%d/%d (%d/%d): %s\n',thisFileNumIdx,nFilesToAnalyze,fileNum,nFiles,obj.fileIDNameArray{obj.fileNum}); end signalExtractionMethodOriginal = signalExtractionMethod; + nwbLoadLock = 0; if obj.nwbLoadFiles==1 signalExtractionMethod = 'NWB'; end @@ -109,7 +108,7 @@ case 'NWB' % Check whether to use override NWB regular expression, else use calciumImagingAnalysis defaults. if isempty(obj.nwbFileRegexp) - filesToLoad = getFileList([obj.dataPath{fileNum} filesep obj.nwbFileFolder],obj.extractionMethodSaveStr.(obj.signalExtractionMethod)); + filesToLoad = getFileList([obj.dataPath{fileNum} filesep obj.nwbFileFolder],[obj.extractionMethodSaveStr.(obj.signalExtractionMethod) '.nwb$']); else filesToLoad = getFileList([obj.dataPath{fileNum} filesep obj.nwbFileFolder],obj.nwbFileRegexp); end @@ -125,6 +124,25 @@ signalExtractionMethod = signalExtractionMethodOriginal; end rawFiles = 1; + + % Load manual and classifier information if available. + regexPairs = {... + obj.extractionMethodSortedSaveStr.(obj.signalExtractionMethod); + obj.extractionMethodClassifierSaveStr.(obj.signalExtractionMethod); + }; + + % get list of files to load + filesToLoad = getFileList(obj.dataPath{fileNum},strrep(regexPairs{1},'.mat','')); + if isempty(filesToLoad) + disp('no files!'); + continue + end + % load files in order + for i=1:length(filesToLoad) + disp(['loading: ' filesToLoad{i}]); + load(filesToLoad{i}); + end + otherwise end @@ -142,13 +160,13 @@ % {obj.rawEMStructSaveStr},... }; % get list of files to load - filesToLoad = getFileList(obj.dataPath{fileNum},strrep(regexPairs{1},'.mat','')); + filesToLoad = getFileList(obj.dataPath{fileNum},strrep(regexPairs{1},'.mat','.*.mat$')); rawFiles = 0; filesToLoad = []; fileToLoadNo = 1; nRegExps = length(regexPairs); while isempty(filesToLoad) - filesToLoad = getFileList(obj.dataPath{obj.fileNum},strrep(regexPairs{fileToLoadNo},'.mat','')); + filesToLoad = getFileList(obj.dataPath{obj.fileNum},strrep(regexPairs{fileToLoadNo},'.mat','.*.mat$')); fileToLoadNo = fileToLoadNo+1; if fileToLoadNo>nRegExps break; @@ -160,7 +178,7 @@ cellfun(@(x) fprintf('Found: %s\n',x),filesToLoad) if isempty(filesToLoad) % if(~exist(filesToLoad{1}, 'file')) - display('no files!'); + disp('no files!'); continue end @@ -170,33 +188,35 @@ % rawFiles = 1; % if isempty(filesToLoad) % % if(~exist(filesToLoad{1}, 'file')) - % display('no files!'); + % disp('no files!'); % continue % end % end % load files in order for i=1:length(filesToLoad) - display(['loading: ' filesToLoad{i}]); + disp(['loading: ' filesToLoad{i}]); load(filesToLoad{i}); end - if exist('pcaicaAnalysisOutput','var') - signalTraces = double(pcaicaAnalysisOutput.IcaTraces); - % signalImages = permute(double(pcaicaAnalysisOutput.IcaFilters),[3 1 2]); - if strcmp(pcaicaAnalysisOutput.imageSaveDimOrder,'xyz') - signalImages = double(pcaicaAnalysisOutput.IcaFilters); - elseif strcmp(pcaicaAnalysisOutput.imageSaveDimOrder,'zxy') - signalImages = permute(double(pcaicaAnalysisOutput.IcaFilters),[2 3 1]); - % inputImages = pcaicaAnalysisOutput.IcaFilters; + if nwbLoadLock==0 + if exist('pcaicaAnalysisOutput','var') + signalTraces = double(pcaicaAnalysisOutput.IcaTraces); + % signalImages = permute(double(pcaicaAnalysisOutput.IcaFilters),[3 1 2]); + if strcmp(pcaicaAnalysisOutput.imageSaveDimOrder,'xyz') + signalImages = double(pcaicaAnalysisOutput.IcaFilters); + elseif strcmp(pcaicaAnalysisOutput.imageSaveDimOrder,'zxy') + signalImages = permute(double(pcaicaAnalysisOutput.IcaFilters),[2 3 1]); + % inputImages = pcaicaAnalysisOutput.IcaFilters; + else + % inputImages = permute(double(pcaicaAnalysisOutput.IcaFilters)); + signalImages = pcaicaAnalysisOutput.IcaFilters; + end + + clear pcaicaAnalysisOutput; else - % inputImages = permute(double(pcaicaAnalysisOutput.IcaFilters)); - signalImages = pcaicaAnalysisOutput.IcaFilters; + signalImages = permute(IcaFilters,[2 3 1]); + signalTraces = IcaTraces; + clear IcaFilters IcaTraces; end - - clear pcaicaAnalysisOutput; - else - signalImages = permute(IcaFilters,[2 3 1]); - signalTraces = IcaTraces; - clear IcaFilters IcaTraces; end rawFiles = 1; case {'EM','CELLMax'} @@ -210,12 +230,12 @@ % get list of files to load filesToLoad = getFileList(obj.dataPath{fileNum},strrep(regexPairs{1},'.mat','')); if isempty(filesToLoad) - display('no files!'); + disp('no files!'); continue end % load files in order for i=1:length(filesToLoad) - display(['loading: ' filesToLoad{i}]); + disp(['loading: ' filesToLoad{i}]); load(filesToLoad{i}); end if exist('cellmaxAnalysisOutput','var') @@ -244,12 +264,12 @@ % get list of files to load filesToLoad = getFileList(obj.dataPath{fileNum},strrep(regexPairs{1},'.mat','')); if isempty(filesToLoad) - display('no files!'); + disp('no files!'); continue end % load files in order for i=1:length(filesToLoad) - display(['loading: ' filesToLoad{i}]); + disp(['loading: ' filesToLoad{i}]); load(filesToLoad{i}); end signalImages = double(extractAnalysisOutput.filters); @@ -268,12 +288,12 @@ % get list of files to load filesToLoad = getFileList(obj.dataPath{fileNum},strrep(regexPairs{1},'.mat','')); if isempty(filesToLoad) - display('no files!'); + disp('no files!'); continue end % load files in order for i=1:length(filesToLoad) - display(['loading: ' filesToLoad{i}]); + disp(['loading: ' filesToLoad{i}]); load(filesToLoad{i}); end % signalImages = double(permute(cnmfAnalysisOutput.extractedImages,[3 1 2])); @@ -288,12 +308,12 @@ % get list of files to load filesToLoad = getFileList(obj.dataPath{fileNum},strrep(regexPairs{1},'.mat','')); if isempty(filesToLoad) - display('no files!'); + disp('no files!'); continue end % load files in order for i=1:length(filesToLoad) - display(['loading: ' filesToLoad{i}]); + disp(['loading: ' filesToLoad{i}]); load(filesToLoad{i}); end % signalImages = double(permute(cnmfAnalysisOutput.extractedImages,[3 1 2])); @@ -308,12 +328,12 @@ % get list of files to load filesToLoad = getFileList(obj.dataPath{fileNum},strrep(regexPairs{1},'.mat','')); if isempty(filesToLoad) - display('no files!'); + disp('no files!'); continue end % load files in order for i=1:length(filesToLoad) - display(['loading: ' filesToLoad{i}]); + disp(['loading: ' filesToLoad{i}]); load(filesToLoad{i}); end % signalImages = double(permute(cnmfAnalysisOutput.extractedImages,[3 1 2])); @@ -327,18 +347,18 @@ signalExtractionMethod = signalExtractionMethodOriginal; - display(['signalTraces: ' num2str(size(signalTraces))]) - display(['signalImages: ' num2str(size(signalImages))]) - % display(['signalPeaks: ' num2str(size(signalPeaks))]) - % display(['signalPeaksArray: ' num2str(size(signalPeaksArray))]) + disp(['signalTraces: ' num2str(size(signalTraces))]) + disp(['signalImages: ' num2str(size(signalImages))]) + % disp(['signalPeaks: ' num2str(size(signalPeaks))]) + % disp(['signalPeaksArray: ' num2str(size(signalPeaksArray))]) % if manually sorted signals, add % if exist('valid','var')|exist('validCellMax','var')|exist('validEXTRACT','var') varList = who; % if length(intersect({obj.validPCAICAStructVarname,obj.validEMStructVarname,obj.validPCAICAStructVarname,obj.validEXTRACTStructVarname,obj.validCNMFStructVarname},varList))>0 if length(intersect({obj.extractionMethodValidVarname.(obj.signalExtractionMethod)},varList))>0 - display('adding manually sorted values...') - display(['adding valid{' num2str(fileNum) '}.' obj.signalExtractionMethod '.manual identifications...']) + disp('adding manually sorted values...') + disp(['adding valid{' num2str(fileNum) '}.' obj.signalExtractionMethod '.manual identifications...']) if exist(obj.validPCAICAStructVarname,'var') obj.validManual{fileNum} = valid; obj.valid{fileNum}.(obj.signalExtractionMethod).manual = valid; @@ -369,12 +389,12 @@ obj.valid{fileNum}.(obj.signalExtractionMethod).manual = validROI; clear validROI; end - display('clearing manual variable...') + disp('clearing manual variable...') end if exist('validClassifier','var') - display('adding classifier annotation for signals...') + disp('adding classifier annotation for signals...') obj.valid{fileNum}.(obj.signalExtractionMethod).classifier = validClassifier; - display('clearing manual variable...') + disp('clearing manual variable...') clear validClassifier; end @@ -389,7 +409,7 @@ % get the x/y coordinates if isempty(signalImages);continue;end; - [xCoords yCoords] = findCentroid(signalImages,'thresholdValue',0.4,'imageThreshold',0.4); + [xCoords, yCoords] = findCentroid(signalImages,'thresholdValue',0.4,'imageThreshold',0.4); obj.objLocations{fileNum}.(obj.signalExtractionMethod) = [xCoords(:) yCoords(:)]; % rawFiles @@ -408,8 +428,8 @@ % [signalImagesTmp(imageNo,:,:)] = viewMontage(inputMovie,signalImages(imageNo,:,:),signalTraces(imageNo,:),obj.signalPeaksArray{fileNum}); % end % signalImages = signalImagesTmp; - display(['traces dims: ' num2str(size(signalTracesTmp))]) - display(['images dims: ' num2str(size(signalImages))]) + disp(['traces dims: ' num2str(size(signalTracesTmp))]) + disp(['images dims: ' num2str(size(signalImages))]) [~, ~, validAuto, imageSizes, imgFeatures] = filterImages(signalImages, signalTracesTmp,'featureList',obj.classifierImageFeaturesNames,'options',obj.filterImageOptions,'testpeaks',testpeaks,'testpeaksArray',obj.signalPeaksArray{fileNum},'xCoords',xCoords,'yCoords',yCoords); % obj.classifierFeatures{fileNum}.(obj.signalExtractionMethod).imageFeatures = imgFeatures; @@ -429,7 +449,7 @@ % obj.rawImagesFiltered{fileNum} = createObjMap(filterImageGroups); size(validAuto) % validAuto - display(['adding valid{' num2str(fileNum) '}.' obj.signalExtractionMethod '.auto identifications...']) + disp(['adding valid{' num2str(fileNum) '}.' obj.signalExtractionMethod '.auto identifications...']) obj.validAuto{fileNum} = validAuto; obj.valid{fileNum}.(obj.signalExtractionMethod).auto = validAuto; clear validAuto @@ -442,7 +462,7 @@ % add files if obj.loadVarsToRam == 1 - display('Loading variables into ram.') + disp('Loading variables into ram.') if exist('signalTraces','var') obj.rawSignals{fileNum} = signalTraces; end @@ -455,9 +475,9 @@ end clear signalTraces signalImages catch err - display(repmat('@',1,7)) + disp(repmat('@',1,7)) disp(getReport(err,'extended','hyperlinks','on')); - display(repmat('@',1,7)) + disp(repmat('@',1,7)) end end obj.guiEnabled = 0; diff --git a/@ciatah/viewMovie.m b/@ciatah/viewMovie.m index 9dc64f1..dc56474 100644 --- a/@ciatah/viewMovie.m +++ b/@ciatah/viewMovie.m @@ -25,7 +25,7 @@ % fileFilterRegexp = obj.fileFilterRegexp; FRAMES_PER_SECOND = obj.FRAMES_PER_SECOND; % DOWNSAMPLE_FACTOR = obj.DOWNSAMPLE_FACTOR; - options.videoPlayer = []; + options.videoPlayer = ''; options.settingsType = ''; % ===================== currentDateTimeStr = datestr(now,'yyyymmdd_HHMM','local'); diff --git a/@ciatah/viewMovieRegistrationTest.m b/@ciatah/viewMovieRegistrationTest.m index 69de4a3..6cd95aa 100644 --- a/@ciatah/viewMovieRegistrationTest.m +++ b/@ciatah/viewMovieRegistrationTest.m @@ -16,6 +16,7 @@ % 2021.06.18 [21:41:07] - Added modelVarsFromFilesCheck() to check and load signals if user hasn't already. % 2021.08.10 [09:57:36] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. % 2021.12.08 [22:11:00] - Additional updates to handle CIAtah v4.0 API switch. + % 2022.05.11 [11:48:58] - Updated so more settings are pulled from CIAtah object settings. % TODO % @@ -65,9 +66,9 @@ {... '1:500',... '3',... - 'concat',... + obj.fileFilterRegexpRaw,... obj.inputDatasetName,... - '/1',... + obj.outputDatasetName,... '1',... '1',... '1',... @@ -77,9 +78,9 @@ % And so the kings battle in the darkness frameList = str2num(movieSettings{1}); nTestToRun = str2num(movieSettings{2}); - fileFilterRegexp = movieSettings{3}; + fileFilterRegexp = movieSettings{3}; obj.fileFilterRegexpRaw = fileFilterRegexp; inputDatasetName = movieSettings{4}; obj.inputDatasetName = inputDatasetName; - outputDatasetName = movieSettings{5}; + outputDatasetName = movieSettings{5}; obj.outputDatasetName = outputDatasetName; treatMoviesAsContinuousSwitch = str2num(movieSettings{6}); runMotionCorrection = str2num(movieSettings{7}); montageDownsampleFactorSpace = str2num(movieSettings{8}); diff --git a/@ciatah/viewObjmaps.m b/@ciatah/viewObjmaps.m index 32262e5..d1c467b 100644 --- a/@ciatah/viewObjmaps.m +++ b/@ciatah/viewObjmaps.m @@ -290,7 +290,7 @@ set(gcf,'SizeChangedFcn',{@resizeui,axHandle}); linkaxes(linkAx); - set(gcf,'PaperUnits','inches','PaperPosition',[0 0 22 16]) + % set(gcf,'PaperUnits','inches','PaperPosition',[0 0 22 16]) obj.modelSaveImgToFile([],'objMapsGeneral_','current',[]); % ======= diff --git a/@ciatah/viewSubjectMovieFrames.m b/@ciatah/viewSubjectMovieFrames.m index 97e1cf1..2acf70a 100644 --- a/@ciatah/viewSubjectMovieFrames.m +++ b/@ciatah/viewSubjectMovieFrames.m @@ -11,6 +11,7 @@ % changelog % 2021.06.18 [21:41:07] - added modelVarsFromFilesCheck() to check and load signals if user hasn't already. % 2021.08.10 [09:57:36] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package. + % 2022.03.16 [08:45:40] - Update code standards. % TODO % @@ -164,37 +165,37 @@ % MIJ.run('Close All Without Saving'); manageMiji('startStop','closeAllWindows'); case 4 - [fileIdxArray idNumIdxArray nFilesToAnalyze nFiles] = obj.getAnalysisSubsetsToAnalyze(); + [fileIdxArray, idNumIdxArray, nFilesToAnalyze, nFiles] = obj.getAnalysisSubsetsToAnalyze(); for thisSubjectStr = subjectList try - display(repmat('=',1,7)) + disp(repmat('=',1,7)) fprintf('Subject %s', thisSubjectStr{1}); validFoldersIdx = find(strcmp(thisSubjectStr,obj.subjectStr)); validFoldersIdx = intersect(fileIdxArray,validFoldersIdx); if isempty(validFoldersIdx) - display('Skipping...') + disp('Skipping...') continue; end - subjectMovieFrames = []; + subjectMovieFrames = single([]); for folderNo = 1:length(validFoldersIdx) - display('===') + disp('===') thisFileNum = validFoldersIdx(folderNo); obj.fileNum = thisFileNum; % Check that signal extraction information is loaded. obj.modelVarsFromFilesCheck(thisFileNum); - [inputSignals inputImages signalPeaks signalPeakIdx valid] = modelGetSignalsImages(obj,'returnType','raw'); - if isempty(inputSignals);display('no input signals');continue;end + [inputSignals, inputImages, signalPeaks, signalPeakIdx, valid] = modelGetSignalsImages(obj,'returnType','raw'); + if isempty(inputSignals);disp('no input signals');continue;end inputImages = thresholdImages(inputImages,'binary',0,'getBoundaryIndex',0,'threshold',0.4,'imageFilter','none'); goodImages = inputImages(:,:,logical(valid)); - goodImages = nanmax(goodImages,[],3); + goodImages = max(goodImages,[],3,'omitnan'); badImages = inputImages(:,:,~logical(valid)); - badImages = nanmax(badImages,[],3); - [sum(valid) size(goodImages) NaN size(inputImages)] + badImages = max(badImages,[],3,'omitnan'); + disp(num2str([sum(valid) size(goodImages) NaN size(inputImages)])) goodImages = goodImages + 0.5*badImages; % [goodImages] = viewAddTextToMovie(goodImages,obj.assay{obj.fileNum},12); diff --git a/README.md b/README.md index 5fcdce6..410f861 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@
-`CIAtah` (pronounced cheetah; formerly calciumImagingAnalysis [ciapkg]) is a software package for analyzing one- and two-photon calcium imaging datasets. +`CIAtah` (pronounced cheetah; formerly calciumImagingAnalysis [ciapkg]) is a software package for analyzing one- and two-photon calcium imaging datasets. It can also be used to process other imaging datasets (e.g. from non-calcium indicators and dyes). ciatah_logo `CIAtah` currently requires `MATLAB` and runs on all major operating systems (Windows, Linux [e.g. Ubuntu], and macOS). -- Note: `CIAtah` version `v4` moves the remaining (i.e. all except external packages/software) CIAtah functions into the `ciapkg` package to improve namespace handling and requires MATLAB R2019b or above ([due to package import changes](https://www.mathworks.com/help/matlab/matlab_prog/upgrade-code-for-r2019b-changes-to-function-precedence-order.html#mw_2934c766-e115-4d22-9abf-eb46a1415f2c)). Users with earlier versions of MATLAB can download `CIAtah` version `v3` (see [Releases](https://github.com/bahanonu/ciatah/releases)) until pre-R2019b MATLAB support is fully integrated into v4. +- Note: `CIAtah` version `v4` moves the remaining (i.e. all except external packages and software) CIAtah functions into the `ciapkg` package to improve namespace handling and requires MATLAB R2019b or above ([due to package import changes](https://www.mathworks.com/help/matlab/matlab_prog/upgrade-code-for-r2019b-changes-to-function-precedence-order.html#mw_2934c766-e115-4d22-9abf-eb46a1415f2c)). Users with earlier versions of MATLAB can download `CIAtah` version `v3` (see [Releases](https://github.com/bahanonu/ciatah/releases)) until pre-R2019b MATLAB support is fully integrated into v4. ## Full documentation at https://bahanonu.github.io/ciatah/. @@ -68,14 +68,20 @@ Made in USA.
- A GUI, via `ciatah` class, with different modules for large-scale batch analysis. - Includes all major calcium imaging analysis steps: - Movie visualization (including reading from disk, for fast viewing of large movies); - - pre-processing (motion correction, spatiotemporal downsampling, spatial filtering, relative fluorescence calculation, etc.); + - pre-processing (motion correction [e.g. TurboReg, NoRMCorre] , spatiotemporal downsampling, spatial filtering, relative fluorescence calculation, etc.); - - support for multiple cell-extraction methods (CELLMax, PCA-ICA, CNMF, CNMF-E, EXTRACT, etc.); + - support for multiple cell-extraction methods: + - PCA-ICA + - CELLMax (additional) + - CNMF + - CNMF-E + - EXTRACT + - etc. - manual classification of cells via GUIs; - automated cell classification (i.e. CLEAN algorithm, coming soon!); - cross-session cell alignment, and more. - Includes example one- and two-photon calcium imaging datasets for testing `CIAtah`. -- Supports a plethora of major imaging movie file formats: HDF5, NWB, AVI, ISXD [Inscopix], TIFF, and [Bio-Formats](https://www.openmicroscopy.org/bio-formats/) compatible formats (Olympus [OIR] and Zeiss [CZI and LSM] currently, additional support to be added or upon request). +- Supports a plethora of major imaging movie file formats: HDF5, NWB, AVI, ISXD [Inscopix], TIFF, SLD [SlideBook], and [Bio-Formats](https://www.openmicroscopy.org/bio-formats/) compatible formats (Olympus [OIR] and Zeiss [CZI and LSM] currently, additional support to be added or upon request). - Supports [Neurodata Without Borders](https://www.nwb.org/) data standard (see [calcium imaging tutorial](https://neurodatawithoutborders.github.io/matnwb/tutorials/html/ophys.html)) for reading/writing cell-extraction and imaging movie files. - Animal position tracking (e.g. in open-field assay) via ImageJ plugin. - Requires `MATLAB` and runs on all major operating systems (Windows, Linux [e.g. Ubuntu], and macOS). diff --git a/docs/docs/help_cross_session_alignment.md b/docs/docs/help_cross_session_alignment.md index 951a064..98d21d2 100644 --- a/docs/docs/help_cross_session_alignment.md +++ b/docs/docs/help_cross_session_alignment.md @@ -1,7 +1,26 @@ # Cross-day or -session cell alignment alignment -The main function used to run cross-session analysis that allows users to align cells across sessions/days can be found at: -- https://github.com/bahanonu/ciatah/blob/master/classification/matchObjBtwnTrials.m +The purpose of cross-session alignment is to allow users to align cells across imaging sessions (e.g. those taken on different days) and thus compare coding of cells, changing in biological variables, and other parameters across conditions and time in their data. + +This page details additional notes on one of the algorithms used in CIAtah, usage outside the CIAtah GUI, and other tips. The main function used to run cross-session analysis that allows users to align cells across sessions/days can be found at: + +- https://github.com/bahanonu/ciatah/blob/master/+ciapkg/+classification/matchObjBtwnTrials.m + +

+ Stable cell alignment across imaging sessions. +

+

+ + m121_matchedCells + +

+ + + +Below is an example of what the main output from cross-session alignment, a `globalIDs` matrix containing indices matches cells across days, looks like when visualized. Each column is an imaging session and each row is an individual global cell with the color indicating that global cell's within-session number. Any black cells indicate where no match was found for that global cell in that imaging day. + +Global cell output + ## Algorithm overview @@ -9,6 +28,8 @@ For details, see __Cross-day analysis of BLA neuronal activity__ methods section - http://science.sciencemag.org/content/sci/suppl/2019/01/16/363.6424.276.DC1/aap8586_Corder_SM.pdf#page=10. +We will also have details in an forthcoming imaging experiments and analysis book chapter. + ![image](https://user-images.githubusercontent.com/5241605/126744851-cd6e64ab-9b83-40bf-aa38-2301276f0ccf.png) diff --git a/docs/docs/help_issues.md b/docs/docs/help_issues.md index 5dbff89..37fdb89 100644 --- a/docs/docs/help_issues.md +++ b/docs/docs/help_issues.md @@ -21,6 +21,23 @@ Page outlines some common issues and fixes to them that may be encountered while * [File or folder dialog box with no instructions](#file-or-folder-dialog-box-with-no-instructions) *** +## Error opening AVI or other files due to codec issues + +If run into issues opening certain AVI files (e.g. due to codec issues) with CIAtah/MATLAB: + +```Matlab +Error using VideoReader/initReader (line 734) +Unable to determine the required codec. + +Error in audiovideo.internal.IVideoReader (line 136) + initReader(obj, fileName, currentTime); + +Error in VideoReader (line 104) + obj@audiovideo.internal.IVideoReader(varargin{:}); +``` + +, install `K-Lite Codec Pack` (https://codecguide.com/download_kl.htm) or similar for added support. + ## PCA-ICA, CNMF-E, or other cell extraction algorithm's don't produce sensible output. - When running `modelPreprocessMovie` a dialog box appears showing the available analysis steps. Certain combinations of these steps make sense while others should be avoided. diff --git a/docs/docs/help_temporal_downsampling.md b/docs/docs/help_temporal_downsampling.md index ad5c295..6aaaebe 100644 --- a/docs/docs/help_temporal_downsampling.md +++ b/docs/docs/help_temporal_downsampling.md @@ -1,5 +1,7 @@ # Preprocessing: Temporal downsampling +Next, temporally smooth each movie by downsampling from the original 20 or 30 Hz to 5 Hz + Example code to run the downsample test function with the following commands: ```Matlab loadRepoFunctions; @@ -13,3 +15,6 @@ Below is an example pixel from a cell in a BLA animal. Note: - `imresize` using bilinear and bicubic produce similar results with bicubic having slower runtimes (e.g. on my machine 3.46 vs. 4.31 sec if set `cropSize` to 100). - The number next to each name is the vector's variance. ![image](https://cloud.githubusercontent.com/assets/5241605/13099409/b85b119c-d4e6-11e5-91d4-f6f7c74fed18.png) + +![image](https://cloud.githubusercontent.com/assets/5241605/13099409/b85b119c-d4e6-11e5-91d4-f6f7c74fed18.png) +img \ No newline at end of file diff --git a/docs/docs/install.md b/docs/docs/install.md index adfee5d..1a56dc9 100644 --- a/docs/docs/install.md +++ b/docs/docs/install.md @@ -19,6 +19,13 @@ Below are steps needed to quickly get started using the `{{ site.name }}` softwa cd('{{ code.mainclass }}-master') ``` +## Check required toolboxes installed + +`{{ site.name }}` depends on several MATLAB toolboxes to run properly. Run the below command to have `{{ site.name }}` check whether dependencies have been installed properly. If not use the `Add-Ons` (https://www.mathworks.com/help/matlab/matlab_env/get-add-ons.html) explorer to install each toolbox. + +```Matlab +ciapkg.io.matlabToolboxCheck;` +``` ## Setup `{{ site.name }}` diff --git a/docs/docs/pipeline_detailed_signal_extraction.md b/docs/docs/pipeline_detailed_signal_extraction.md index c7e4fef..664e5bb 100644 --- a/docs/docs/pipeline_detailed_signal_extraction.md +++ b/docs/docs/pipeline_detailed_signal_extraction.md @@ -4,7 +4,14 @@ title: Automated cell extraction. # Extracting cells with `modelExtractSignalsFromMovie` -Users can run PCA-ICA, EXTRACT, CNMF, CNMF-E, and ROI cell extraction by following the below set of option screens. Details on running the new Schnitzer lab cell-extraction methods (e.g. CELLMax) will be added here after they are released. +Users can run the following cell-extraction algorithms: + - CELLMax + - PCA-ICA + - CNMF + - CNMF-E + - EXTRACT + - etc. +by following the below set of option screens. Details on running the new Schnitzer lab cell-extraction methods (e.g. CELLMax) will be added here after they are released. We normally estimate the number of PCs and ICs on the high end, manually sort to get an estimate of the number of cells, then run PCA-ICA again with IC 1.5-3x the number of cells and PCs 1-1.5x number of ICs. @@ -19,3 +26,51 @@ The resulting output (on _Figure 45+_) at the end should look something like: + +## PCA-ICA (Mukamel, 2009) + +There are several parameters for PCA-ICA that users can input, these are `µ`, `term_tol`, `max_iter`, and the number of PCs and ICs to request. + +### Mukamel, 2009 (`µ`) +The original Mukamel, 2009 (https://doi.org/10.1016/j.neuron.2009.08.009) paper describing PCA-ICA gives an explanation of `µ`: + +![image](https://user-images.githubusercontent.com/5241605/180803955-55367e92-d1f6-494c-a78d-a1165da1b70a.png) + +`Fig. S3` also provides some information on the effects that varying `µ` from 0 to 1 have on cell extraction quality (we have often found lower values, e.g. 0.1, to work well in most cases): + +![image](https://user-images.githubusercontent.com/5241605/180803154-be738669-b90c-4cf3-850b-71441359bb25.png) + +### Ahanonu, 2022 (`µ` and # of PCs/ICs) + +We also describe `µ` in our recent calcium imaging experiments and analysis book chapter, see section `3.15 Extraction of Neuron Shapes, Locations, and Activity Traces`: https://link.springer.com/protocol/10.1007/978-1-0716-2039-7_13#Sec19. + +Further, we make a note about choosing the number + +![image](https://user-images.githubusercontent.com/5241605/180802392-b134fed1-c8ab-45b5-9ee6-814100e410ed.png) + +### term_tol + +The `term_tol` parameter is the ICA termination tolerance, e.g. when min difference between ICA iterations is below this value, the algorithm will exit (if it has not already reached `max_iter`). + +### max_iter + +The `max_iter` parameter determines how many iterations ICA will run before terminating. + +## CNMF (Pnevmatikakis et al. 2016) + +CNMF (Constrained Nonnegative Matrix Factorization) uses a modified version of NMF to reduce crosstalk between signals and also outputs model-based traces with reduced noise. It is recommended that users compare both the model-based, smoothed traces and the more noisy dF/F traces extracted from the movie as each can be useful for different types of analyses. + +A description of many of the parameters can be found at https://caiman.readthedocs.io/en/master/Getting_Started.html#parameters. + +## CNMF-e (Zhou et al. 2018) + +Use CNMF-e primarily on one-photon datasets or those with large background fluctuations, it will generally perform better than CNMF in those situations. + +- An overview of the CNMF-e model can be found at https://github.com/zhoupc/CNMF_E/wiki/Model-overview. +- Inscopix provides a good description of parameters at: https://github.com/inscopix/inscopix-cnmfe/blob/main/docs/parameter_tuning.md. + +## EXTRACT (Inan et al. 2021) + +EXTRACT improves signal estimation via robust estimation to reduce contamination from surrounding noise sources (be they nearby cells or background activity). + +A description of EXTRACT parameters can be found at https://github.com/schnitzer-lab/EXTRACT-public#advanced-aspects. \ No newline at end of file