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.
	%
	% 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 (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.
		% 2014.03.27 - several updates to speed up function, fixed several assumption issues (all movies same file type, etc.) and brought name scheme in line with other fxns
		% 2015.02.25 [15:32:10] fixed bug pertaining to treatMoviesAsContinuous not working properly if frameList was blank and only a single movie was input.
		% 2015.05.28 [02:39:51] bug fix with treatMoviesAsContinuous, the global frames weren't quite correct
		% 2016.02.23 [19:40:31] added in proper-ish ability to read in RGB grayscale AVI files
		% 2017.07.06 [20:13:53] improved getHdf5Info() subfxn to handle Jessica's HDF5 format in a more automatic fashion, e.g. if there is behavior or other group level data in the HDF5 file
		% 2018.02.16 [14:48:24] Fixed frame-by-frame HDF5 load being inefficient by using tmpMovie instead of loading directly into said movie
		% 2018.09.28 [16:49:29] HDF5 non-contiguous frame reading is faster, no looping since select multiple hyperslabs at once, see readHDF5Subset for more
		% 2019.01.16 [07:51:01] Added ISXD support (Inscopix format for v3+).
		% 2019.03.10 [19:28:47] Improve support for loading 1000s of TIF files by adding some performance improvements, ASSUMES that all tifs being loaded are from the same movie and have similar properties and dimensions. See options.onlyCheckFirstFileInfo.
		% 2019.06.06 [19:39:17] - Misc code fixes
		% 2019.10.07 [10:21:09] - Added h5info support and support for 16-bit float types.
		% 2019.10.09 [17:14:24] - Change dataset reading to handle HDF5 where main dataset is not a top-level directory OR it is a sub-directory that is not the only dataset in the HDF5.
		% 2020.04.05 [16:27:11] - Added check to support reading NWB as HDF5 file.
		% 2020.08.30 [10:16:08] - Change warning message output for HDF5 file of certain type.
		% 2020.08.31 [15:47:49] - Add option to suppress warnings.
		% 2020.10.19 [12:11:14] - Improved comments and options descriptions.
		% 2021.02.15 [11:55:36] - Fixed loading HDF5 datasetname that has only a single frame, loadMovieList would ask for 3rd dimension information that did not exist.
		% 2021.06.21 [10:22:32] - Added support for Bio-Formats compatible files, specifically Olympus (OIR) and Zeiss (CZI, LSM).
		% 2021.06.28 [16:57:27] - Added check that deals with users requesting more frames than are in the movie in the case where "options.largeMovieLoad==1" and a matrix is pre-allocated.
		% 2021.06.30 [12:26:12] - Added additional checks for frameList to remove if negative or zero along with additional checks during movie loading to prevent loading frames outside movie extent.
		% 2021.07.03 [08:55:06] - dims.three fix for reading tifs, esp. ImageJ >4GB.
		% 2021.08.08 [19:30:20] - Updated to handle CIAtah v4.0 switch to all functions inside ciapkg package.
		% 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
				% Allow outputting as a [x y c t] matrix to allow support for multiple color channels without needing to re-read file multiple times.
			% Determine file type by properties of file instead of extension (don't trust input...)
			% Remove all use of tmpMovie....
			% Add ability to degrade gracefully with HDF5 dataset names, so try several backup datasetnames if one doesn't work.
			% Allow fallbacks for HDF5 dataset name, e.g. if can't find /1, look for /images. Might never add this as it opens up dangers of loading the incorrect dataset if a user makes a mistake, better to give out a warning.
		% DONE
			% allow user to input frames that are global across several files, e.g. [1:500 1:200 1:300] are the lengths of each movie, so if input [650:670] in frameList, should grab 150:170 from movie 2 - DONE, see treatMoviesAsContinuous option.
			% verify movies are of supported load types, remove from list if not and alert user, should be an option (e.g. return instead) - DONE
			% MAKE tiff loading recognize frameList input. - DONE
			% Add preallocation by pre-reading each movie's dimensions - DONE

	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
		'.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;
	% 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
	% fn=fieldnames(options);
	% for i=1:length(fn)
	%     eval([fn{i} '=options.' fn{i} ';']);
	% end

	startTime = tic;

    % Ensure all output arguments set
    outputMovie = NaN;
    movieDims = NaN;
    nPixels = NaN;
    nFrames = NaN;

	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
	if ischar(movieList)
		movieList = {movieList};
    end
    % Setup file info.
    if isempty(options.fileInfo)
        options.fileInfo = cell([1 length(movieList)]);
    end

	% ========================
	% modify frameList if loading equal parts
	if ~isempty(options.loadMovieInEqualParts)
		fprintf('Loading %d equal parts of %d frames each\n',options.loadMovieInEqualParts(1),options.loadMovieInEqualParts(2))
		options.frameList = subfxnLoadEqualParts(movieList,options);
	end

	% Remove any frames that are zero or negative, not valid.
	if ~isempty(options.frameList)
        if any(options.frameList<1)
            subfxnDisplay(['Removing invalid frames from user input frame list (negative or zero).'],options);
            options.frameList(options.frameList<1) = [];
        end
	end

	% ========================
	% remove unsupported files
	movieNo = 1;
	movieTypeList = cell([1 length(movieList)]);
	for iMovie = 1:length(movieList)
		thisMoviePath = movieList{iMovie};
		[options.movieType, supported] = getMovieFileType(thisMoviePath);
		movieTypeList{iMovie} = options.movieType;
		if supported==0
			subfxnDisplay(['removing unsupported file from list: ' thisMoviePath],options);
		else
			tmpMovieList{movieNo} = movieList{iMovie};
			movieNo = movieNo + 1;
		end
	end
	% if tmp doesn't exist, means no input files are valid, return
	if exist('tmpMovieList','var')
		movieList = tmpMovieList;
	else
		if options.displayInfo==1
			toc(startTime);
			display(repmat('#',1,3))
		end
		return;
	end
	numMovies = length(movieList);

	% Test that Inscopix MATLAB API ISX package installed
	if any(strcmp(movieTypeList,'isxd'))==1
		inputFilePathCheck = movieList{find(strcmp(movieTypeList,'isxd'),1,'first')};
		try
			inputMovieIsx = isx.Movie.read(inputFilePathCheck);
		catch
			ciapkg.inscopix.loadIDPS();
		end
	end

	% ========================
	% pre-read each file to allow pre-allocation of output file
	reverseStr = '';
	for iMovie=1:numMovies
		thisMoviePath = movieList{iMovie};
		if options.onlyCheckFirstFileInfo==1&&iMovie>1
			fprintf('Adding movie #1 info to movie info for %d\\%d: %s\n',iMovie,numMovies,thisMoviePath);
			dims.x(iMovie) = dims.x(1);
			dims.y(iMovie) = dims.y(1);
			dims.z(iMovie) = dims.z(1);
			dims.one(iMovie) = dims.one(1);
			dims.two(iMovie) = dims.two(1);
			dims.three(iMovie) = dims.three(1);
			continue
		end
		if options.displayInfo==1
			fprintf('Getting movie info for %d\\%d: %s\n',iMovie,numMovies,thisMoviePath);
		end
		[options.movieType, supported] = getMovieFileType(thisMoviePath);
		if supported==0

		end
		switch options.movieType
			case 'tiff'
				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(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);
				dims.z(iMovie) = nTiles;
				dims.one(iMovie) = xyDims(1);
				dims.two(iMovie) = xyDims(2);
				% dims.three(iMovie) = size(imfinfo(thisMoviePath),1);
                dims.three(iMovie) = nTiles;

				if dims.z(iMovie)==1
					% fileInfo = imfinfo(thisMoviePath,'tif');
                    fileInfo = options.fileInfo{iMovie};
					try
						numFramesStr = regexp(fileInfo.ImageDescription, 'images=(\d*)', 'tokens');
						nFrames = str2double(numFramesStr{1}{1});
					catch
						% Try to grab using TIFF library
						try
							nFrames = tiffHandle.numberOfTiles;
						catch
							nFrames = 1;
						end
					end
					dims.z(iMovie) = nFrames;
					dims.three(iMovie) = nFrames;
				end
				tiffHandle.close(); clear tiffHandle
			case 'hdf5'

				% Check if NWB file and alter input dataset name to default NWB if inputDatasetName does not point to a valid NWB dataset.
				[~,~,extTmp] = fileparts(thisMoviePath);
				if strcmp(extTmp,'.nwb')
					try
						h5info(thisMoviePath,options.inputDatasetName);
					catch
						for zCheck = 1:length(options.defaultNwbDatasetName)
							try
								options.inputDatasetName = options.defaultNwbDatasetName{zCheck};
								h5info(thisMoviePath,options.inputDatasetName);
							catch
								fprintf('Incorrect NWB dataset name: %s\n',options.inputDatasetName)
							end
						end
					end
				end
				clear extTmp zCheck;

				if options.useH5info==1
					hinfo = h5info(thisMoviePath);
				else
					hinfo = hdf5info(thisMoviePath);
				end
				[hReadInfo, thisDatasetName, datasetDims] = getHdf5Info();
				if options.displayDiagnosticInfo==1
					display(hReadInfo)
					hReadInfo.Name
				end
				hReadInfo.Dims = datasetDims;
				dims.x(iMovie) = hReadInfo.Dims(1);
				dims.y(iMovie) = hReadInfo.Dims(2);
				dims.one(iMovie) = hReadInfo.Dims(1);
				dims.two(iMovie) = hReadInfo.Dims(2);
				% Check 3rd dimension exists
				if length(hReadInfo.Dims)>=3
					dims.z(iMovie) = hReadInfo.Dims(3);
					dims.three(iMovie) = hReadInfo.Dims(3);
				else
					dims.z(iMovie) = 0;
					dims.three(iMovie) = 0;
				end

				if dims.z(iMovie)
					offsetTmp = [0 0 1];
					blockTmp = [dims.x(iMovie) dims.y(iMovie) 1];
				else
					offsetTmp = [0 0];
					blockTmp = [dims.x(iMovie) dims.y(iMovie)];
				end
				if ischar(options.inputDatasetName)
					tmpDataset = options.inputDatasetName;
				else
					tmpDataset = thisDatasetName;
				end
				tmpFrame = readHDF5Subset(thisMoviePath,offsetTmp,blockTmp,'datasetName',tmpDataset,'displayInfo',options.displayInfo);
			case 'avi'
				xyloObj = VideoReader(thisMoviePath);
				dims.x(iMovie) = xyloObj.Height;
				dims.y(iMovie) = xyloObj.Width;
				dims.z(iMovie) = xyloObj.NumberOfFrames;
				dims.one(iMovie) = xyloObj.Height;
				dims.two(iMovie) = xyloObj.Width;
				dims.three(iMovie) = xyloObj.NumberOfFrames;
				tmpFrame = read(xyloObj, 1, 'native');
				tmpFrame = tmpFrame.cdata;
				% tmpFrame = readFrame(xyloObj);
			case 'isxd'
				inputMovieIsx = isx.Movie.read(thisMoviePath);
				nFrames = inputMovieIsx.timing.num_samples;
				xyDims = inputMovieIsx.spacing.num_pixels;
				dims.x(iMovie) = xyDims(1);
				dims.y(iMovie) = xyDims(2);
				dims.z(iMovie) = nFrames;
				dims.one(iMovie) = xyDims(1);
				dims.two(iMovie) = xyDims(2);
				dims.three(iMovie) = nFrames;
				tmpFrame = inputMovieIsx.get_frame_data(0);
			case 'bioformats'
				bfreaderTmp = bfGetReader(thisMoviePath);
				bfreaderTmp.setSeries(options.bfSeriesNo);

				omeMeta = bfreaderTmp.getMetadataStore();
				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);
				dims.z(iMovie) = nFrames;
				dims.one(iMovie) = xyDims(1);
				dims.two(iMovie) = xyDims(2);
				dims.three(iMovie) = nFrames;

				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);
		else
			imgClass = options.loadSpecificImgClass;
		end
		% change dims.z if user specifies a list of frames
		if (~isempty(options.frameList)|options.frameList>dims.z(iMovie))&options.treatMoviesAsContinuous==0
			% Disallow frames that are longer than the length of the movie
			frameListTmp = options.frameList;
			if any(frameListTmp>dims.three(iMovie))
				rmIDx = frameListTmp>dims.three(iMovie);
				if options.displayInfo==1
					fprintf('Removing %d frames outside movie length.\n',sum(rmIDx));
				end
				frameListTmp(rmIDx) = [];
			else
			end
			dims.z(iMovie) = length(frameListTmp);
		end
		if options.displayInfo==1
			reverseStr = cmdWaitbar(iMovie,numMovies,reverseStr,'inputStr','checking movies','waitbarOn',options.waitbarOn,'displayEvery',10);
		end
	end
	if options.getMovieDims==1
		outputMovie = dims;
		if options.displayInfo==1
			toc(startTime);
			display(repmat('#',1,3))
		end
		return;
	end
	% dims
	xDimMax = max(dims.x);
	yDimMax = max(dims.y);
	switch options.treatMoviesAsContinuous
		case 0
			zDimLength = sum(dims.z);
		case 1
			if isempty(options.frameList)
				zDimLength = sum(dims.z);
			else
				frameListTmp = options.frameList;
				zDimLength = length(frameListTmp);
				if any(zDimLength>sum(dims.three))
					rmIDx = frameListTmp>sum(dims.three);
					if options.displayInfo==1
						fprintf('Removing %d frames outside movie length.\n',sum(rmIDx));
					end
					frameListTmp(rmIDx) = [];
					zDimLength = length(frameListTmp);
				end
			end
		otherwise
			% body
	end
	% pre-allocated output structure, convert to input movie datatype
	% if strcmp(imgClass,'single')|strcmp(imgClass,'double')
	% 	if isempty(options.loadSpecificImgClass)
	% 		outputMovie = nan([xDimMax yDimMax zDimLength],imgClass);
	% 	else
	% 		display('pre-allocating single matrix...')
	% 		outputMovie = ones([xDimMax yDimMax zDimLength],imgClass);
	% 		% j = whos('outputMovie');j.bytes=j.bytes*9.53674e-7;display(['movie size: ' num2str(j.bytes) 'Mb | ' num2str(j.size) ' | ' j.class]);
	% 		% return;
	% 		outputMovie(:,:,:) = 0;
	% 	end
	% else
	% 	outputMovie = zeros([xDimMax yDimMax zDimLength],imgClass);
	% end

	% size(outputMovie)

	if options.treatMoviesAsContinuous==1&&~isempty(options.frameList)
		% totalZ = sum(dims.z);
		zdims = dims.z;
		frameList = options.frameList;
		zdimsCumsum = cumsum([0 zdims]);
		zdims = [1 zdims];
		for i=1:(length(zdims)-1)
			g{i} = frameList>zdimsCumsum(i)&frameList<=zdimsCumsum(i+1);
			globalFrame{i} = frameList(g{i}) - zdimsCumsum(i);
			dims.z(i) = length(globalFrame{i});
		end
		disp([cellfun(@max,globalFrame,'UniformOutput',false); cellfun(@min,globalFrame,'UniformOutput',false)])
		% pause
	else
		globalFrame = [];
	end

	% reshape movie if not including larger movies
	removeIdx = [];
	numList = 1:numMovies;
	for iMovie=numList
		if isempty(globalFrame)
			thisFrameList = options.frameList;
		else
			thisFrameList = globalFrame{iMovie};
			if isempty(thisFrameList)
				subfxnDisplay(['no global frames:' num2str(iMovie) '/' num2str(numMovies) ': ' thisMoviePath],options);
				removeIdx(end+1) = iMovie;
			end
		end
	end

	subfxnDisplay('-------',options);
	keepIdx = setdiff(numList,removeIdx);
	xDimMax = nanmax(dims.x(keepIdx));
	yDimMax = nanmax(dims.y(keepIdx));

	% pre-allocated output structure, convert to input movie datatype
	if options.largeMovieLoad==1
	else
		if strcmp(imgClass,'single')||strcmp(imgClass,'double')
			if isempty(options.loadSpecificImgClass)
				preallocSize = [xDimMax yDimMax zDimLength];
				subfxnDisplay(['pre-allocating ' imgClass ' NaN matrix: ' num2str(preallocSize)],options);
				outputMovie = NaN(preallocSize,imgClass);
			else
				preallocSize = [xDimMax yDimMax zDimLength];
				% subfxnDisplay('pre-allocating single ones matrix...',options);
				subfxnDisplay(['pre-allocating ' imgClass ' ones matrix: ' num2str(preallocSize)],options);
				outputMovie = ones(preallocSize,imgClass);
				% j = whos('outputMovie');j.bytes=j.bytes*9.53674e-7;display(['movie size: ' num2str(j.bytes) 'Mb | ' num2str(j.size) ' | ' j.class]);
				% return;
				outputMovie(:,:,:) = 0;
			end
		else
			preallocSize = [xDimMax yDimMax zDimLength];
			% subfxnDisplay('pre-allocating single zeros matrix...',options);
			subfxnDisplay(['pre-allocating ' imgClass ' zeros matrix: ' num2str(preallocSize)],options);
			outputMovie = zeros(preallocSize,imgClass);
		end
	end
	subfxnDisplay('-------',options);
	% ========================
	for iMovie=1:numMovies
		thisMoviePath = movieList{iMovie};

		[options.movieType] = getMovieFileType(thisMoviePath);

		if isempty(globalFrame)
			thisFrameList = options.frameList;
		else
			thisFrameList = globalFrame{iMovie};
			if isempty(thisFrameList)
				subfxnDisplay(['no global frames:' num2str(iMovie) '/' num2str(numMovies) ': ' thisMoviePath],options);
				continue
			end
		end

		% Remove any frames that are outside the movie length.
		if any(thisFrameList>dims.three(iMovie))
			rmIDx2 = thisFrameList>dims.three(iMovie);
			if options.displayInfo==1
				fprintf('Removing %d frames outside movie length.\n',sum(rmIDx2));
			end
			thisFrameList(rmIDx2) = [];
		end

		if options.displayInfo==1
			subfxnDisplay(['loading ' num2str(iMovie) '/' num2str(numMovies) ': ' thisMoviePath],options);
		end
		% depending on movie type, load differently
		switch options.movieType
			case 'tiff'
				% Pre-load the temporary image to reduce overhead and boost performance when loading many individual TIF images
				if iMovie==1&&options.onlyCheckFirstFileInfo==1
					thisMoviePath = movieList{iMovie};
					tiffHandle = Tiff(thisMoviePath, 'r');
					tmpFramePerma = tiffHandle.read();
					% 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 = options.fileInfo{iMovie};
				end

				if options.displayInfo==0
					displayInfoH = 0;
				end

				if numMovies==1
					if isempty(thisFrameList)
						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,'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;
					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
			% ========================
			case 'hdf5'
				if isempty(thisFrameList)&&options.forcePerFrameRead==0
					if options.useH5info==1
						hinfo = h5info(thisMoviePath);
					else
						hinfo = hdf5info(thisMoviePath);
					end
					% hReadInfo = hinfo.GroupHierarchy.Datasets(1);
					% datasetNames = {hinfo.GroupHierarchy.Datasets.Name};
					% thisDatasetName = strmatch(inputDatasetName,datasetNames);
					% hReadInfo = hinfo.GroupHierarchy.Datasets(thisDatasetName);
					hReadInfo = getHdf5Info();
					% read in the file
					% hReadInfo.Attributes
					if options.largeMovieLoad==1&&numMovies==1
						if options.useH5info==1
							outputMovie = h5read(thisMoviePath,options.inputDatasetName);
						else
							outputMovie = hdf5read(hReadInfo);
						end
					else
						if options.useH5info==1
							tmpMovie = h5read(thisMoviePath,options.inputDatasetName);
						else
							tmpMovie = hdf5read(hReadInfo);
						end
						if isempty(options.loadSpecificImgClass)
						else
							tmpMovie = cast(tmpMovie,imgClass);
						end
					end
				else
					% xxxx = 1;
					if sum(nanmin(diff(thisFrameList)==1))==1&&options.forcePerFrameRead==0
						% if contiguous segment of HDF5 file, read that in as one block to save time if user specifies as such
						inputFilePath = thisMoviePath;
						framesToGrab = thisFrameList;
						if options.useH5info==1
							hinfo = h5info(inputFilePath);
						else
							hinfo = hdf5info(inputFilePath);
						end
						% hReadInfo = hinfo.GroupHierarchy.Datasets(1);
						[hReadInfo, thisDatasetName, datasetDims] = getHdf5Info();
						hReadInfo.Dims = datasetDims;
						xDim = hReadInfo.Dims(1);
						yDim = hReadInfo.Dims(2);

						subfxnDisplay(['loading movie as contiguous chunk: ' num2str([0 0 framesToGrab(1)-1]) ' | ' num2str([xDim yDim length(framesToGrab)])],options);
						% tmpMovie = readHDF5Subset(inputFilePath,[0 0 framesToGrab(1)-1],[xDim yDim length(framesToGrab)],'datasetName',options.inputDatasetName);
						% size(tmpMovie)
						% size(outputMovie)
						if ischar(options.inputDatasetName)
							tmpMovie = readHDF5Subset(inputFilePath,[0 0 framesToGrab(1)-1],[xDim yDim length(framesToGrab)],'datasetName',options.inputDatasetName,'displayInfo',options.displayInfo);
						else
							tmpMovie = readHDF5Subset(inputFilePath,[0 0 framesToGrab(1)-1],[xDim yDim length(framesToGrab)],'datasetName',thisDatasetName,'displayInfo',options.displayInfo);
						end
					else
						subfxnDisplay('Read frame-by-frame',options)
						% read frame-by-frame to save space
						inputFilePath = thisMoviePath;
						if options.useH5info==1
							hinfo = h5info(inputFilePath);
						else
							hinfo = hdf5info(inputFilePath);
						end
						% hReadInfo = hinfo.GroupHierarchy.Datasets(1);
						[hReadInfo, thisDatasetName, datasetDims] = getHdf5Info();
						hReadInfo.Dims = datasetDims;
						xDim = hReadInfo.Dims(1);
						yDim = hReadInfo.Dims(2);
						% tmpMovie = readHDF5Subset(inputFilePath,[0 0 thisFrameList(1)],[xDim yDim length(thisFrameList)],'datasetName',options.inputDatasetName);
						framesToGrab = thisFrameList;
						if isempty(thisFrameList)&&options.forcePerFrameRead==1
							framesToGrab = 1:dims.z(iMovie);
						end
						nFrames = length(framesToGrab);
						% reverseStr = '';

						for iframe = 1:nFrames
							readFrame = framesToGrab(iframe);
							offsetT{iframe} = [0 0 readFrame-1];
							blockT{iframe} = [xDim yDim 1];
						end
						if ischar(options.inputDatasetName)
							thisFrame = readHDF5Subset(inputFilePath,offsetT,blockT,'datasetName',options.inputDatasetName,'displayInfo',0);
						else
							thisFrame = readHDF5Subset(inputFilePath,offsetT,blockT,'datasetName',thisDatasetName,'displayInfo',0);
						end
						if isempty(options.loadSpecificImgClass)
							% tmpMovie(:,:,iframe) = thisFrame;
							outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),1:nFrames) = thisFrame;
						else
							% assume 3D movies with [x y frames] as dimensions
							if(iMovie==1)
								outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),1:nFrames) = cast(thisFrame,imgClass);
							else
								zOffset = sum(dims.z(1:iMovie-1));
								outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),(zOffset+1:nFrames)) = cast(thisFrame,imgClass);
							end
						end
						% if options.displayInfo==1
						% 	reverseStr = cmdWaitbar(iframe,nFrames,reverseStr,'inputStr','loading hdf5','waitbarOn',options.waitbarOn,'displayEvery',50);
						% end

						% for iframe = 1:nFrames
						% 	readFrame = framesToGrab(iframe);
						% 	if ischar(options.inputDatasetName)
						% 		thisFrame = readHDF5Subset(inputFilePath,[0 0 readFrame-1],[xDim yDim 1],'datasetName',options.inputDatasetName,'displayInfo',0);
						% 	else
						% 		thisFrame = readHDF5Subset(inputFilePath,[0 0 readFrame-1],[xDim yDim 1],'datasetName',thisDatasetName,'displayInfo',0);
						% 	end
						% 	if isempty(options.loadSpecificImgClass)
						% 		% tmpMovie(:,:,iframe) = thisFrame;
						% 		outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),iframe) = thisFrame;
						% 	else
						%     	% assume 3D movies with [x y frames] as dimensions
						% 	    if(iMovie==1)
						% 			outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),iframe) = cast(thisFrame,imgClass);
						% 	    else
						% 	    	zOffset = sum(dims.z(1:iMovie-1));
						% 	    	outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),(zOffset+iframe)) = cast(thisFrame,imgClass);
						% 	    end
						% 	end
						% 	if options.displayInfo==1
						% 		reverseStr = cmdWaitbar(iframe,nFrames,reverseStr,'inputStr','loading hdf5','waitbarOn',options.waitbarOn,'displayEvery',50);
						% 	end
						% end
					end
				end
			% ========================
			case 'avi'
				xyloObj = VideoReader(thisMoviePath);

				if isempty(thisFrameList)
					nFrames = xyloObj.NumberOfFrames;
					framesToGrab = 1:nFrames;
				else
					nFrames = length(thisFrameList);
					framesToGrab = thisFrameList;
				end
				vidHeight = xyloObj.Height;
				vidWidth = xyloObj.Width;

				% Preallocate movie structure.
				tmpMovie = zeros(vidHeight, vidWidth, nFrames, 'uint8');

				% Read one frame at a time.
				reverseStr = '';
				iframe = 1;
				nFrames = length(framesToGrab);
				for iframe = 1:nFrames
					readFrame = framesToGrab(iframe);
					tmpAviFrame = read(xyloObj, readFrame);
					% check if frame is RGB or grayscale, if RGB only take one channel (since they will be identical for RGB grayscale)
					if size(tmpAviFrame,3)==3&isempty(options.rgbChannel)
						tmpAviFrame = squeeze(tmpAviFrame(:,:,1));
					elseif ~isempty(options.rgbChannel)
						tmpAviFrame = squeeze(tmpAviFrame(:,:,options.rgbChannel));
					end
					tmpMovie(:,:,iframe) = tmpAviFrame;
					% reduce waitbar access
					if options.displayInfo==1
						reverseStr = cmdWaitbar(iframe,nFrames,reverseStr,'inputStr','loading avi','waitbarOn',options.waitbarOn,'displayEvery',50);
					end
					iframe = iframe + 1;
				end
			% ========================
			case 'isxd'
				% Setup movie class
				inputMovieIsx = isx.Movie.read(thisMoviePath);
				nFramesHere = inputMovieIsx.timing.num_samples;
				xyDims = inputMovieIsx.spacing.num_pixels;

				if isempty(thisFrameList)
					nFrames = nFramesHere;
					framesToGrab = 1:nFrames;
				else
					nFrames = length(thisFrameList);
					framesToGrab = thisFrameList;
				end
				vidHeight = xyDims(1);
				vidWidth = xyDims(2);

				% Preallocate movie structure.
				tmpMovie = zeros(vidHeight, vidWidth, nFrames, imgClass);

				% Read one frame at a time.
				reverseStr = '';
				iframe = 1;
				nFrames = length(framesToGrab);
				for iframe = 1:nFrames
					readFrame = framesToGrab(iframe);
					tmpAviFrame = inputMovieIsx.get_frame_data(readFrame-1);
					% tmpAviFrame = read(xyloObj, readFrame);
					% check if frame is RGB or grayscale, if RGB only take one channel (since they will be identical for RGB grayscale)
					% if size(tmpAviFrame,3)==3
					% 	tmpAviFrame = squeeze(tmpAviFrame(:,:,1));
					% end
					tmpMovie(:,:,iframe) = tmpAviFrame;
					% reduce waitbar access
					if options.displayInfo==1
						reverseStr = cmdWaitbar(iframe,nFrames,reverseStr,'inputStr','loading ISXD','waitbarOn',options.waitbarOn,'displayEvery',50);
					end
					iframe = iframe + 1;
				end
			% ========================
			case 'bioformats'
				% Setup movie class
				bfreaderTmp = bfGetReader(thisMoviePath);
				bfreaderTmp.setSeries(options.bfSeriesNo);

				omeMeta = bfreaderTmp.getMetadataStore();
				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;

				xyDims = [stackSizeX stackSizeY];

				if isempty(thisFrameList)
					nFrames = nFramesHere;
					framesToGrab = 1:nFrames;
				else
					nFrames = length(thisFrameList);
					framesToGrab = thisFrameList;
				end
				vidHeight = xyDims(2);
				vidWidth = xyDims(1);

				% 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 = 0;
				iZ = 1;
				dispEvery = round(nFrames/20);
				% Adjust for zero-based indexing
				framesToGrab = framesToGrab-1;

				% 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)

				% continue;
				extraBioformatsFlag = 0;
				if extraBioformatsFlag==1
					% 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.

					% 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
			% ========================
			otherwise
				% let's just not deal with this for now
				return;
		end
		if exist('tmpMovie','var')
			if iMovie==1
				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;
				end
				% outputMovie(:,:,:) = tmpMovie;
			else
				% assume 3D movies with [x y frames] as dimensions
				zOffset = sum(dims.z(1:iMovie-1));
				outputMovie(1:dims.x(iMovie),1:dims.y(iMovie),(zOffset+1):(zOffset+dims.z(iMovie))) = tmpMovie;
				% outputMovie(:,:,end+1:end+size(tmpMovie,3)) = tmpMovie;
			end
			clear tmpMovie;
		else

		end
	end

	% hinfo = hdf5info('A:\shared\concatenated_2013_07_05_p62_m728_MAG1.h5');
	% DFOF = hdf5read(hinfo.GroupHierarchy.Datasets(1));
	% get size of movie
	% DFOFsize = size(DFOF.Movie);
	movieDims = size(outputMovie);
	nPixels = movieDims(1)*movieDims(2);
	if length(movieDims)==2
		nFrames = 1;
	else
		nFrames = movieDims(3);
	end
	if options.waitbarOn==1
		subfxnDisplay(['movie class: ' class(outputMovie)],options);
		subfxnDisplay(['movie size: ' num2str(size(outputMovie))],options);
		subfxnDisplay(['x-dims: ' num2str(dims.x)],options);
		subfxnDisplay(['y-dims: ' num2str(dims.y)],options);
		subfxnDisplay(['z-dims: ' num2str(dims.z)],options);
	end
	j = whos('outputMovie');j.bytes=j.bytes*9.53674e-7;
	subfxnDisplay(['movie size: ' num2str(j.bytes) 'Mb | ' num2str(j.size) ' | ' j.class],options);
	% display(dims);
	% Convert the movie to single
	% DFOF=single(DFOF);
	if options.convertToDouble==1
		subfxnDisplay('converting to double...',options);
		outputMovie=double(outputMovie);
	end

	if options.displayInfo==1
		toc(startTime);
		display(repmat('#',1,3))
	end

	function [hReadInfo, thisDatasetName, datasetDims] = getHdf5Info()
		if ischar(options.inputDatasetName)
			try
				if options.useH5info==1
					datasetNames = {hinfo.Datasets.Name};
					% Strip leading forward slash for h5info compatability
					thisDatasetName = strcmp(options.inputDatasetName(2:end),datasetNames);
					hReadInfo = hinfo.Datasets(thisDatasetName);
					if isempty(hReadInfo)
						hReadInfo = h5info(thisMoviePath,options.inputDatasetName);
						thisDatasetName = options.inputDatasetName;
					end
					datasetDims = hReadInfo.Dataspace.Size;
				else
					datasetNames = {hinfo.GroupHierarchy.Datasets.Name};
					thisDatasetName = strcmp(options.inputDatasetName,datasetNames);
					hReadInfo = hinfo.GroupHierarchy.Datasets(thisDatasetName);
					if isempty(hReadInfo)
						hReadInfo = h5info(thisMoviePath,options.inputDatasetName);
						datasetDims = hReadInfo.Dataspace.Size;
						thisDatasetName = options.inputDatasetName;
					else
						datasetDims = hReadInfo.Dims;
					end
				end
			catch err
				try
					if options.displayDiagnosticInfo==1
						disp(repmat('@',1,7))
						disp(getReport(err,'extended','hyperlinks','on'));
						disp(repmat('@',1,7))
						h5disp(thisMoviePath)
					elseif options.displayWarnings==1
						warning('HDF5 dataset name not found in hinfo.Datasets.Name. Trying another method to load HDF5 dataset.')
					end
				catch
				end
				try
					hReadInfo = h5info(thisMoviePath,options.inputDatasetName);
					datasetDims = hReadInfo.Dataspace.Size;
					thisDatasetName = options.inputDatasetName;
				catch err
					try
						if options.displayDiagnosticInfo==1
							disp(repmat('@',1,7))
							disp(getReport(err,'extended','hyperlinks','on'));
							disp(repmat('@',1,7))
						elseif options.displayWarnings==1
							warning('Could not find %s in %s.',options.inputDatasetName,thisMoviePath)
						end
					catch
					end
					try
						datasetNames = {hinfo.GroupHierarchy.Groups.Datasets.Name};
						thisDatasetName = strmatch(options.inputDatasetName,datasetNames);
						hReadInfo = hinfo.GroupHierarchy.Groups.Datasets(thisDatasetName);
					catch err
						try
							if options.displayDiagnosticInfo==1
								disp(repmat('@',1,7))
								disp(getReport(err,'extended','hyperlinks','on'));
								disp(repmat('@',1,7))
							elseif options.displayWarnings==1
								warning('HDF5 hinfo.GroupHierarchy.Groups.Datasets contains no dataset %s.',thisDatasetName)
							end
						catch
						end
						nGroups = length(hinfo.GroupHierarchy.Groups);
						datasetNames = {};
						for groupNo = 1:nGroups
							datasetNames{groupNo} = hinfo.GroupHierarchy.Groups(groupNo).Datasets.Name;
						end
						thisDatasetName = strmatch(options.inputDatasetName,datasetNames);
						thisGroupNo = strmatch(options.inputDatasetName,datasetNames);
						% hReadInfo = hinfo.GroupHierarchy.Groups(thisDatasetName).Datasets;
						% hinfo.GroupHierarchy.Groups(thisGroupNo).Datasets.Name
						thisDatasetNo = strmatch(options.inputDatasetName,{hinfo.GroupHierarchy.Groups(thisGroupNo).Datasets.Name});
						hReadInfo = hinfo.GroupHierarchy.Groups(thisGroupNo).Datasets(thisDatasetNo);
					end
				end
			end
		else
			hReadInfo = hinfo.GroupHierarchy.Datasets(options.inputDatasetName);
			thisDatasetName = hinfo.GroupHierarchy.Datasets(options.inputDatasetName).Name;
		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,'.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)
	end
end

function frameListTmp = subfxnLoadEqualParts(movieList,options)
    import ciapkg.api.* % import CIAtah functions in ciapkg package API.

	movieDims = loadMovieList(movieList,'convertToDouble',options.convertToDouble,'frameList',[],'inputDatasetName',options.inputDatasetName,'treatMoviesAsContinuous',options.treatMoviesAsContinuous,'loadSpecificImgClass',options.loadSpecificImgClass,'getMovieDims',1);

	loadMovieInEqualParts = options.loadMovieInEqualParts(1);
	defaultNumFrames = options.loadMovieInEqualParts(2);
	tmpList = round(linspace(1,sum(movieDims.z)-defaultNumFrames,loadMovieInEqualParts));
	display(['tmpList' num2str(tmpList)])
	tmpList = bsxfun(@plus,tmpList,[1:defaultNumFrames]');

	frameListTmp = tmpList(:);
	frameListTmp(frameListTmp<1) = [];
	frameListTmp(frameListTmp>sum(movieDims.z)) = [];
	frameListTmp = unique(frameListTmp);

	% [primaryMovie] = loadMovieList(movieListTmp2,'convertToDouble',0,'frameList',frameListTmp(:),'treatMoviesAsContinuous',treatMoviesAsContinuous,'inputDatasetName',obj.inputDatasetName);
end


classdef TimeUnitEnum < matlab.mixin.SetGet
    %TimeUnitEnum Summary of this class goes here
    %   Detailed explanation goes here

    enumeration
        Seconds('Seconds')
        Minutes('Minutes')
        Hours('Hours')
        Days('Days')
        Years('Years')
    end

    properties
        name(1,:) char
    end

    methods
        function obj = TimeUnitEnum(name)
            obj.name = name;
        end
    end

    methods(Static)
        function [listBoxStr, m] = getListboxStr()
            m = enumeration('TimeUnitEnum');
            listBoxStr = {m.name};
        end
    end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% REAL-TIME BCILAB/SIFT INTERFACE DEMO SCRIPT
%
% This script demonstrates a beta interface between BCILAB
% (Christian Kothe: www.sccn.ucsd.edu/wiki/BCILAB) and SIFT (Tim Mullen:
% www.sccn.ucsd.edu/wiki/SIFT) to perform real-time data extraction,
% cleaning, connectivity modeling and visualization.
%
% This script was prepared for the following whitepaper/demonstration:
% [1] Tim Mullen, Christian Kothe, Yu Chi, Tzyy-Ping Jung,
%     and Scott Makeig. "Real-Time Estimation and 3D Visualization of Sparse
%     Multivariate Effective Connectivity Using Wearable EEG." July 15th,
%     IEEE EMB/CAS/SMC Workshop on Brain-Machine-Body Interfaces. 34th
%     Annual International IEEE EMBS Conference of the IEEE Engineering in
%     Medicine and Biology Society (IEEE EMBS).
%
% Please refer to [1] when using any functionality from this demo.
%
% Copyright Tim Mullen, Christian Kothe, July 2012, SCCN/INC/UCSD
%
% Author: Tim Mullen,       July 2012, SCCN/INC/UCSD
%         Christian Kothe,  July 2012, SCCN/INC/UCSD
%
% This function is part of the Source Information Flow Toolbox (SIFT)
%
% This program is free software; you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation; either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% SET UP CONFIGURATION OPTIONS
RUN_LSL          = false;           % If RUN_LSL = true, then stream 'online' from device; If RUN_LSL=false then playback TestingDataFile (below)

% Source reconstruction options (leave disabled)
COLOR_SOURCE_ROI = true;          % this will use special meshes for coloring ROIs
HEAD_MODEL_NAME  = 'resources:/headmodels/Cognionics_64_HeadModelObj_3751.mat';             % path to head model object for source reconstruction. Leave empty if you aren't doing source reconstruction

% Establish file paths
% NOTE: all paths are relative to 'datapath' which is a
% platform-independent path which itself can be relative to bcilab root
% folder (i.e. data:/ is the userdata folder in the bcilab root dir)
datapath         = 'data:/';       % this is relative to the BCILAB root dir
TrainingDataFile = 'Cognionics_64_training.set'; %'calibration_mindo.xdf'; % %'calibration.xdf'; %'Cognionics_Pyramind_demo.set'; %'clean_reversed.xdf'; %'noisy.xdf'; %'Cognionics_Pyramind_demo.set';             % this is the relative path to the calibration dataset
TestingDataFile  = 'Cognionics_64_testing.set'; %'testing.xdf'; %'Cognionics_Pyramind_demo.set'; %'clean_reversed.xdf'; %'noisy.xdf'; %'Cognionics_Pyramind_demo.set';             % this is an optional path to a dataset to playback (if RUN_LSL = false)
GUI_CONFIG_NAME  = 'DEMO_SOURCELOC_METACP_CFG.mat'; %'DARPA_DEMO_METACP_CFG_FEWCHANS.mat';             % relative path to a default pipeline configuration
GUI_BRAINMOVIE_CONFIG_NAME = 'DEMO_SOURCELOC_BM_CFG.mat'; %'DARPA_DEMO_BM_CFG.mat';   % relative path to BrainMovie configuration

% Set up the name of the stream we will write to in the workspace
streamname              = 'EEGDATA';
LSL_SelectionProperty   = 'type';  % can also be 'name'
LSL_SelectionValue      = 'EEG';

%% load head model object
if ~isempty(HEAD_MODEL_NAME)
    hmObj = headModel.loadFromFile(env_translatepath(HEAD_MODEL_NAME));
    % load meshes for visualization
    surfData    = load(hmObj.surfaces);
    surfData    = surfData.surfData;
    scalpMesh   = surfData(1);
    csfMesh     = surfData(2);
    cortexMesh  = surfData(3);
end

%% load calibration recording
calibData = exp_eval(io_loadset([datapath TrainingDataFile],'markerchannel',{'remove_eventchns',false}));
flt_pipeline('update');

%% start LSL streaming
if RUN_LSL == true
    run_readlsl('MatlabStream',streamname,'SelectionProperty',LSL_SelectionProperty,'SelectionValue',LSL_SelectionValue);
end
%% ... OR read from datafile
if RUN_LSL == false
    run_readdataset('MatlabStream',streamname,'Dataset',io_loadset([datapath TestingDataFile],'markerchannel',{'remove_eventchns',false}));
end

%% initialize some variables
[benchmarking.preproc     ...
 benchmarking.modeling    ...
 benchmarking.brainmovie] = deal(NaN);

newPipeline     = true;
newBrainmovie   = true;
specOpts        = [];

% create a minimal moving average pipeline...
% noflats = exp_eval_optimized(flt_movavg(flt_selchans(calibData,{'FC3', 'FC4', 'FC2', 'FC1', 'C3', 'C4', 'C2', 'C1', 'CP3', 'CP4', 'CP2', 'CP1', 'P3', 'P4', 'P2', 'P1'})));
noflats = flt_seltypes(calibData,'EEG');
%noflats = exp_eval_optimized(flt_movavg(flt_seltypes(calibData,'EEG')));
highpassed   = exp_eval_optimized(flt_fir(noflats,[0.5 1],'highpass'));
goodchannels = exp_eval_optimized(flt_clean_channels('signal',highpassed,'min_corr',0.35,'ignored_quantile',0.3));

%% initialize options
if ~isempty(GUI_CONFIG_NAME) && exist(env_translatepath([datapath GUI_CONFIG_NAME]),'file')
    % try to load a configuration file
    io_load([datapath GUI_CONFIG_NAME]);
else
    % manually set some defaults
    opts.miscOptCfg.dispBenchmark  = true;
    opts.miscOptCfg.doSIFT         = [];
    opts.holdPipeline              = false;
    opts.exitPipeline              = false;

    opts.siftPipCfg.preproc.verb = 0;

    % set some defaults
    opts.fltPipCfg = ...
        struct('DataCleaning', ...
                struct('RetainPhases',true, ...
                       'HaveBursts', true, ...
                       'HaveChannelDropouts', false, ...
                       'DataSetting', 'drycap'), ...
                'Rereferencing', {{}});

    opts.siftPipCfg = ...
        struct('preproc', ...
                    struct('normalize', ...
                        struct('verb', 0, ...
                               'method', {{'time'}})));

end

% additional options regarding brainmovie node/edge adaptation
opts.adaptation.adaptationHL    = 10;  % HL of exp. win. MA in frames
opts.adaptation.updateInterval  = 1;
opts.adaptation.bufferTime      = 1;

% set the arg_direct flag to true
opts = arg_setdirect(opts,true);

%% initialize the Meta Control Panel
if ~isempty(HEAD_MODEL_NAME)
    calibData.srcpot = 1; % allow sources
end
figHandles.MetaControlPanel = gui_metaControlPanel(calibData,opts);
waitfor(figHandles.MetaControlPanel,'UserData','initialized');

%% Initialize figures
figHandles.specDisplay      = [];
figHandles.BMDisplay        = [];
figHandles.BMControlPanel   = [];

%% try to load a BrainMovie configuration
try
    io_load([datapath GUI_BRAINMOVIE_CONFIG_NAME]);
    BMCFG.BMopts.bmopts_suppl = {'title',{'Multivariate ','Granger Causality  '}};
    BMCFG.BMopts.caption = true;
catch
    BMCFG = struct([]);
end

BMRenderMode = 'init_and_render';

%% Main loop
% -------------------------------------------------------------------------
while ~opts.exitPipeline

    try

        if newPipeline
            % create a new pipeline on training data
            fprintf('Pipeline changed\n');
            cleaned_data = exp_eval(flt_pipeline('signal',calibData,opts.fltPipCfg));
            pipeline     = onl_newpipeline(cleaned_data,streamname);
            newPipeline  = false;

%             disp('profile on');
%             profile -memory on
        end

        if opts.holdPipeline
            % pause the pipeline
            pause(1);
            continue;
        end

        % grab a chunk of data from the stream and preprocess it
        % ---------------------------------------------------------------------
        prepbench = tic;
        [eeg_chunk,pipeline] = onl_filtered(pipeline, ...
                                            round(opts.miscOptCfg.winLenSec*cleaned_data.srate), ...
                                            opts.miscOptCfg.suppress_console_output, ...
                                            fastif(isfield(opts.fltPipCfg,'psourceLocalize') && opts.fltPipCfg.psourceLocalize.arg_selection,{'srcpot'},{}));
        benchmarking.preproc = toc(prepbench);

        % model the chunk via sift
        % ---------------------------------------------------------------------
        if opts.miscOptCfg.doSIFT.arg_selection
            try
                % force the modeling window length to agree with chunk length
                opts.siftPipCfg.modeling.winlen = opts.miscOptCfg.winLenSec;

                % import ROI Names
                if strcmpi(opts.siftPipCfg.preproc.sigtype.arg_selection,'sources')
                    opts.siftPipCfg.preproc.varnames = eeg_chunk.roiLabels;
                end

                % select a subset of channels
                if ~isempty(opts.miscOptCfg.doSIFT.channelSubset)
                    [dummy eeg_chunk] = evalc('hlp_scope({''disable_expressions'',1},@flt_selchans,''signal'',eeg_chunk,''channels'',opts.miscOptCfg.doSIFT.channelSubset,''arg_direct'',true)');
                end

                % run the SIFT modeling pipeline
                siftbench = tic;
                [eeg_chunk cfg] = hlp_scope({'is_online',1},@onl_siftpipeline,'EEG',eeg_chunk,opts.siftPipCfg);
                benchmarking.modeling = toc(siftbench);

                if eeg_chunk.pnts == 0
                    fprintf('No data!\n');
                    pause(0.01);
                    continue;
                end

            catch err
                hlp_handleerror(err);
                pause(0.1);
                continue;
            end

            % visualize the brainmovie
            % -------------------------------------------------------------
            if opts.miscOptCfg.doSIFT.dispBrainMovie.arg_selection

                if newBrainmovie ...
                    | isempty(ishandle(figHandles.BMDisplay)) ...
                    | ~ishandle(figHandles.BMDisplay)

                    % initialize brainmovie
                    if ~isempty(BMCFG)
                        BMCFG.showNodeLabels.nodelabels = eeg_chunk.CAT.curComponentNames;
                        figHandles.BMControlPanel = gui_causalBrainMovie3D_online(eeg_chunk,eeg_chunk.CAT.Conn,struct('arg_direct',0),BMCFG,'timeRange',[]);
                    else
                        figHandles.BMControlPanel = gui_causalBrainMovie3D_online(eeg_chunk,eeg_chunk.CAT.Conn,struct('arg_direct',0),'showNodeLabels',{'nodelabels',eeg_chunk.CAT.curComponentNames});
                    end
                    waitfor(figHandles.BMControlPanel,'UserData','newBrainmovie');

                    % set the closing behavior
                    set(figHandles.BMControlPanel,'CloseRequestFcn','evalin(''base'',''figHandles.BMControlPanel = [];''); delete(gcbf);');

                    BM_CFG_CHANGED       = true;
                    newBrainmovie        = false;
                    figHandles.BMDisplay = [];
                    BMStateVars          = [];
                end

                if BM_CFG_CHANGED
                    BMRenderMode    = 'init_and_render';
                    BMStateVars     = [];

                    % determine whether to adapt limits
                    adapt_nodeSizeDataRange  = isempty(BMCFG.BMopts.graphColorAndScaling.nodeSizeDataRange)  && opts.miscOptCfg.doSIFT.dispBrainMovie.adaptBrainMovieLimits;
                    adapt_edgeSizeDataRange  = isempty(BMCFG.BMopts.graphColorAndScaling.edgeSizeDataRange)  && opts.miscOptCfg.doSIFT.dispBrainMovie.adaptBrainMovieLimits;
                    adapt_nodeColorDataRange = isempty(BMCFG.BMopts.graphColorAndScaling.nodeColorDataRange) && opts.miscOptCfg.doSIFT.dispBrainMovie.adaptBrainMovieLimits;
                    adapt_edgeColorDataRange = isempty(BMCFG.BMopts.graphColorAndScaling.edgeColorDataRange) && opts.miscOptCfg.doSIFT.dispBrainMovie.adaptBrainMovieLimits;

                    % clear state
                    nodeSizeState   = [];
                    nodeColorState  = [];
                    edgeSizeState   = [];
                    edgeColorState  = [];

                    % reset flag
                    BM_CFG_CHANGED = false;
                end

                % Handle special rendering of source meshes and colors
                % ---------------------------------------------------------
                if COLOR_SOURCE_ROI
                    BG_COLOR = [0.1 0.1 0.1];

                    if BMCFG.BMopts.Layers.custom.arg_selection
                        BMCFG.BMopts.Layers.custom.volumefile = eeg_chunk.dipfit.surfmesh;
                        BMCFG.BMopts.Layers.custom.meshcolor  = hlp_getROIVertexColorTable( ...
                                size(eeg_chunk.dipfit.surfmesh.vertices,1), ...
                                eeg_chunk.roiVertices,BG_COLOR,@(x)distinguishable_colors(x,BG_COLOR));
                    end

                    % if the cortex mesh is a custom mesh with 'constant'
                    % coloring...
                    if strcmpi(BMCFG.BMopts.Layers.cortex.cortexres,'custommesh') ...
                       && strcmp(BMCFG.BMopts.Layers.cortex.cortexcolor.arg_selection,'Constant')
                            % ... replace the colors with ROI coloring
                            BMCFG.BMopts.Layers.cortex.cortexcolor.colormapping = ...
                                hlp_getROIVertexColorTable( ...
                                    size(eeg_chunk.dipfit.surfmesh.vertices,1), ...
                                    eeg_chunk.roiVertices,BG_COLOR,@(x)distinguishable_colors(x,BG_COLOR));
%                         {hmObj.atlas.label, hlp_getROIColorTable(hmObj.atlas.label,eeg_chunk.roiLabels,[0.5 0.5 0.5],[1 0 0])};
                    end
                end

                % Render the Brain Movie
                % ---------------------------------------------------------
                bmbench = tic;
                [BMcfg_tmp BMhandles BMout] = ...
                    vis_causalBrainMovie3D(eeg_chunk,eeg_chunk.CAT.Conn,BMCFG, ...
                        'timeRange',[], ...
                        'BMopts',hlp_mergeVarargin(BMCFG.BMopts, ...
                                    'figurehandle',figHandles.BMDisplay, ...
                                    'RenderMode',BMRenderMode, ...
                                    'speedy',true, ...
                                    'InternalStateVariables',BMStateVars));
                benchmarking.brainmovie = toc(bmbench);

                if isempty(BMStateVars)
                    set(figHandles.BMDisplay, 'MenuBar','figure', ...
                                'CloseRequestFcn','evalin(''base'',''figHandles.BMDisplay = [];''); delete(gcbf);', ...
                                'ToolBar','none', 'Name','BrainMovie3D');
                end

                % save figure handle and state
                figHandles.BMDisplay = BMhandles.figurehandle;
                BMStateVars          = BMcfg_tmp.BMopts.vars;
                BMRenderMode         = 'render';

                % adapt the node and edge color/size limits
                % ---------------------------------------------------------
                if opts.miscOptCfg.doSIFT.dispBrainMovie.arg_selection ...
                   && opts.miscOptCfg.doSIFT.dispBrainMovie.adaptBrainMovieLimits

                    % adapt BrainMovie node/edge limits
                    if adapt_nodeSizeDataRange
                        [BMCFG.BMopts.graphColorAndScaling.nodeSizeDataRange, nodeSizeState] ...
                            = hlp_adaptMinMaxLimits('values',   BMout.NodeSize,     ...
                                                    'state',    nodeSizeState,      ...
                                                    'adaptOpts',opts.adaptation);
                    end
                    if adapt_nodeColorDataRange
                        [BMCFG.BMopts.graphColorAndScaling.nodeColorDataRange, nodeColorState] ...
                            = hlp_adaptMinMaxLimits('values',   BMout.NodeColor,     ...
                                                    'state',    nodeColorState,      ...
                                                    'adaptOpts',opts.adaptation);
                    end
                    if adapt_edgeSizeDataRange
                        [BMCFG.BMopts.graphColorAndScaling.edgeSizeDataRange, edgeSizeState] ...
                            = hlp_adaptMinMaxLimits('values',   BMout.EdgeSize,     ...
                                                    'state',    edgeSizeState,      ...
                                                    'adaptOpts',opts.adaptation);
                    end
                    if adapt_edgeColorDataRange
                        [BMCFG.BMopts.graphColorAndScaling.edgeColorDataRange, edgeColorState] ...
                            = hlp_adaptMinMaxLimits('values',   BMout.EdgeColor,     ...
                                                    'state',    edgeColorState,      ...
                                                    'adaptOpts',opts.adaptation);
                    end

                end % adaptBrainMovieLimits

            else % dispBrainMovie == false
                % close the brainmovie figure if control panel is closed
                if isempty(figHandles.BMControlPanel) ...
                        || ~ishandle(figHandles.BMControlPanel) ...
                        && ishandle(figHandles.BMDisplay)
                    close(figHandles.BMDisplay);
                end
                benchmarking.brainmovie = NaN;
            end

            % display the VAR spectrum
            % -------------------------------------------------------------
            if opts.miscOptCfg.doSIFT.dispSpectrum
                try
                    if ishandle(figHandles.specDisplay)
                        % update the SpecViewer
                        vis_autospectrum(specOpts,'FigureHandle',figHandles.specDisplay,'stream',eeg_chunk);
                    else
                        % generate a GUI and initialize SpecViewer
                        [figHandles.specDisplay specOpts] = arg_guidialog(@vis_autospectrum,'Parameters',{'stream',eeg_chunk});

                        set(figHandles.specDisplay,'CloseRequestFcn','evalin(''base'',''opts.miscOptCfg.dispSpectrum=false; figHandles.specDisplay = [];''); delete(gcbf);');
                        specOpts.arg_direct = 0;
                    end
                catch e
                    disp(e.message);
                    opts.miscOptCfg.doSIFT.dispSpectrum = false;
                end
            else
                if ishandle(figHandles.specDisplay)
                    close(figHandles.specDisplay);
                end
            end

        else
            benchmarking.modeling = NaN;
        end

        if opts.miscOptCfg.dispBenchmark
            % TODO: implement this as a figure
            vis_benchmark(benchmarking);
        end

    catch err
         hlp_handleerror(err);
    end

    pause(0.005);

end

%% all done, close all open figures
fnames = fieldnames(figHandles);
for f=1:length(fnames)
    if ishandle(figHandles.(fnames{f}))
        close(figHandles.(fnames{f}));
    end
end


function Fog_simulation_Cityscapes(task_id, dataset_split, refinement_level,...
    variant, beta, output_root_directory, images_per_task)
%FOG_SIMULATION_CITYSCAPES  Simulate fog for a batch of images from Cityscapes
%and write the results. Structured for execution on a cluster.
%
%   INPUTS:
%
%   -|task_id|: ID of the task. Used in conjuction with |images_per_task| to
%    determine which images out of the entire dataset will form the batch that
%    will be processed by this task.
%
%   -|dataset_split|: string that indicates which subset of Cityscapes is used
%    for running the simulation, e.g. 'trainval', 'train_extra'.
%
%   -|refinement_level|: string that indicates the refinement that is applied to
%    the used subset, e.g. 'full', 'refined'.
%
%   -|variant|: string that indicates the type of fog simulation that is being
%    run, e.g. 'stereoscopic_inpainting_with_guided_filtering',
%    'nearest_neighbor'.
%
%   -|beta|: value of attenuation coefficient that determines fog density.
%
%   -|cityscapes_output_root_directory|: full path to directory under which the
%    results of the simulation are written, e.g.
%    '/scratch_net/nowin/csakarid/Toyota-foggy/data/Cityscapes'.
%
%   -|images_per_task|: maximum number of images for which fog simulation is run
%    in each task.
%    19997 train_extra Cityscapes images -> 200 images per task * 100 tasks.
%    5000 trainvaltest Cityscapes images -> 100 images per task * 50 tasks.
%    550 trainval refined Cityscapes images -> 22 images per task * 25 tasks.

if ischar(task_id)
    task_id = str2double(task_id);
end

% ------------------------------------------------------------------------------

% Check which variant of the simulation is being run and distinguish between
% different cases.

% Get abbreviations to be included in output names.
abbreviations_of_variants =...
    containers.Map({'stereoscopic_inpainting_with_guided_filtering',...
    'nearest_neighbor'}, {'stereogf', 'nearest'});
variant_abbreviation = abbreviations_of_variants(variant);

% ------------------------------------------------------------------------------

% Add paths to functions that are called for fog simulation.

current_script_full_name = mfilename('fullpath');
current_script_directory = fileparts(current_script_full_name);
addpath(fullfile(current_script_directory, '..', 'utilities'));
addpath_relative_to_caller(current_script_full_name,...
    fullfile('..', 'Fog_simulation'));
addpath_relative_to_caller(current_script_full_name,...
    fullfile('..', 'Depth_processing'));
addpath_relative_to_caller(current_script_full_name,...
    fullfile('..', 'Dehazing', 'Dark_channel_prior'));
addpath_relative_to_caller(current_script_full_name,...
    fullfile('..', 'external', 'SLIC_mex'));

% ------------------------------------------------------------------------------

% Create lists of input files for various modalities.

cityscapes_file_lists_directory = fullfile(current_script_directory, '..',...
    '..', 'data', 'lists_file_names');

% Left input images.
list_of_left_images_file = strcat('leftImg8bit_', dataset_split, '_',...
    refinement_level, '_clean_filenames.txt');
fid = fopen(fullfile(cityscapes_file_lists_directory,...
    list_of_left_images_file));
left_image_file_names = textscan(fid, '%s');
fclose(fid);
left_image_file_names = left_image_file_names{1};

number_of_images = length(left_image_file_names);

% Right input images.
list_of_right_images_file = strcat('rightImg8bit_', dataset_split, '_',...
    refinement_level, '_filenames.txt');
fid = fopen(fullfile(cityscapes_file_lists_directory,...
    list_of_right_images_file));
right_image_file_names = textscan(fid, '%s');
fclose(fid);
right_image_file_names = right_image_file_names{1};

% Disparity map files.
list_of_disparity_maps_file = strcat('disparity_', dataset_split, '_',...
    refinement_level, '_filenames.txt');
fid = fopen(fullfile(cityscapes_file_lists_directory,...
    list_of_disparity_maps_file));
disparity_file_names = textscan(fid, '%s');
fclose(fid);
disparity_file_names = disparity_file_names{1};

% Camera parameters files.
list_of_camera_parameters_file = strcat('camera_', dataset_split, '_',...
    refinement_level, '_filenames.txt');
fid = fopen(fullfile(cityscapes_file_lists_directory,...
    list_of_camera_parameters_file));
camera_parameters_file_names = textscan(fid, '%s');
fclose(fid);
camera_parameters_file_names = camera_parameters_file_names{1};

% Based on the selected simulation variant, form the name of the corresponding
% text file with the file names of the respective depth maps.
switch variant_abbreviation
    case 'stereogf'
        list_of_depth_maps_file = strcat('depth_stereoscopic_',...
            dataset_split, '_', refinement_level, '_filenames.txt');
    case 'nearest'
        list_of_depth_maps_file = strcat('depth_nearest_', dataset_split,...
            '_', refinement_level, '_filenames.txt');
end

% Get all names of depth map files, whether they already exist or not.
fid = fopen(fullfile(cityscapes_file_lists_directory,...
    list_of_depth_maps_file));
depth_file_names = textscan(fid, '%s');
fclose(fid);
depth_file_names = depth_file_names{1};

% Sanity checks. Total number of files should be identical for all modalities.
assert(number_of_images == length(right_image_file_names));
assert(number_of_images == length(disparity_file_names));
assert(number_of_images == length(camera_parameters_file_names));
assert(number_of_images == length(depth_file_names));

% ------------------------------------------------------------------------------

% Determine current batch.

% Determine the set of images that are to be processed in the current task.
batch_ind = (task_id - 1) * images_per_task + 1:task_id * images_per_task;
if batch_ind(1) > number_of_images
    return;
end
if batch_ind(end) > number_of_images
    % Truncate for last task.
    batch_ind = batch_ind(1:number_of_images - batch_ind(1) + 1);
end

% ------------------------------------------------------------------------------

% Instantiate individual components of the fog simulation pipeline that are
% required for the selected variant and set their parameters.

% Attenuation coefficient. Set it to meaningful values.
attenuation_coefficient_method = @scattering_coefficient_fixed;
beta_parameters.beta = beta;

% Atmospheric light.
atmospheric_light_estimation = @estimate_atmospheric_light_rf;

% Select the optical model which is used for the synthesis of foggy images from
% the clean input images.
fog_optical_model = @haze_linear;

% Transmittance model of the clear scene radiance.
transmittance_model = @transmission_homogeneous_medium;

% Specify whether depth maps will be loaded from existing .mat files if possible
% or computed on the fly.
load_depth = true;

% Specify whether the depth maps used for fog simulation will be saved to .mat
% files.
save_depth = true;

% Select the method for depth map completion and denoising, and the method for
% subsequent postprocessing of the initial transmittance map.
switch variant_abbreviation
    case 'stereogf'
        depth_completion_method =...
            @depth_in_meters_cityscapes_stereoscopic_inpainting;
        transmittance_postprocessing =...
            @transmission_postprocessing_guided_filter;
    case 'nearest'
        depth_completion_method = @depth_in_meters_cityscapes_nn_inpainting;
end

% ------------------------------------------------------------------------------

% Output specifications.

% Specify output format for synthetic foggy images and corresponding
% transmittance maps.
output_format = '.png';

% Determine initial part of the path of output files.
cityscapes_output_directories_basename = strcat('leftImg8bit_',...
    dataset_split, '_', refinement_level, '_', variant_abbreviation,...
    '_beta_', num2str(beta_parameters.beta));
cityscapes_output_foggy_directory = fullfile(output_root_directory,...
    strcat(cityscapes_output_directories_basename, '_foggy'));
cityscapes_output_transmittance_directory = fullfile(output_root_directory,...
    strcat(cityscapes_output_directories_basename, '_transmittance'));

% ------------------------------------------------------------------------------

% Run fog simulation on current batch using the specified settings.

switch variant_abbreviation

    % Stereoscopic inpainting followed by guided filtering.
    case 'stereogf'
        clean2foggy_Cityscapes(left_image_file_names(batch_ind),...
            right_image_file_names(batch_ind),...
            disparity_file_names(batch_ind),...
            camera_parameters_file_names(batch_ind),...
            depth_file_names(batch_ind), load_depth, save_depth,...
            depth_completion_method, attenuation_coefficient_method,...
            beta_parameters, atmospheric_light_estimation,...
            transmittance_model, transmittance_postprocessing,...
            fog_optical_model, output_root_directory,...
            cityscapes_output_foggy_directory,...
            cityscapes_output_transmittance_directory, output_format);

    % Nearest neighbor inpainting of depth values.
    case 'nearest'
        clean2foggy_Cityscapes_nn(left_image_file_names(batch_ind),...
            disparity_file_names(batch_ind),...
            camera_parameters_file_names(batch_ind),...
            attenuation_coefficient_method, beta_parameters,...
            atmospheric_light_estimation, transmittance_model,...
            fog_optical_model, cityscapes_output_foggy_directory,...
            cityscapes_output_transmittance_directory, output_format);
end

end


% hack to convert autoalign to manual align matrices
% do a regular save from mralign2 after this...
% AW 99
mf=MfA;
trans=transA';
rot=rotA;


function check = isExceptionStrip(grstruct, fieldname)

% initialize output
check = false;

% exception list {fieldname, val_types}
exceptions = {'color', @iscell, 'width', @(x)(length(x)>1), 'size', @(x)(length(x)>1)};

for e = 1:2:length(exceptions)

    %comparison function
    compfun = exceptions{e+1};

    % look for fieldnames of type exceptions{e} and compare the underyling data using exceptions{e+1}
    if strcmp(fieldname, exceptions{e}) && compfun(grstruct.(fieldname))
        check = true;
    end

end
end


function files_out = bids_spreadsheet2participants(varargin)
    %
    % routine to takes an spreadsheet file (for example: excel) as input and export as participants.tsv
    % and participants.json
    %
    % FORMAT files_out = bids_spreadsheet2participants(file_in,'ignore','field1','field2',...)
    %
    % INPUTS if empty user is prompted:
    %
    %   - 'file_in' is the excel file, with at least the 3 BIDS mandatory fields in the 1st sheet:
    %       - participant_id,
    %       - age,
    %       - gender
    %
    %     A second sheet can be used to specify variables for the json file column format:
    %       - variable name (same as sheet 1),
    %       - description,
    %       - values (optional)
    %
    %   - 'ignore' is the key to ignore specific fields (columns) in the excel file
    %     for example: 'ignore', 'variableX', 'variableY'
    %
    %   - 'export_dir' is the directory to save output files
    %
    % OUTPUT:
    %
    %   - 'files_out' is a cell array with:
    %       - files_out{1} the full name of the particpiants.tsv
    %       - files_out{2} the full name of the particpiants.json
    %
    % EXAMPLE:
    %
    %   files_out = bids_spreadsheet2participants('D:\icpsr_subset.xlsx','ignore','EEGtesttime')
    %
    % the spreadsheet must have:
    %   worksheet 1 with data, each column has a variable name and values
    %       -- note missing values MUST be n/a or nill
    %   worksheet 2 is optional with columns variables, Description, Levels, Units to describe worksheet 1
    %   variables similar to
    % https://bids-specification.readthedocs.io/en/stable/03-modality-agnostic-files.html#participants-file
    %       -- note the order of 'Levels' names should match the order of apparition in the data
    %          to have an exact match
    %
    % Requires matlab 2018a or above
    %
    % Cyril Pernet - March 2021
    % ---------------------------------

    if verLessThan('matlab', '9.4')
        error('This function requires matlab 2018a or above.');
    end

    files_out = cell(2, 1);
    % check library
    if ~exist('jsonwrite.m', 'file')
        if exist(fullfile(fileparts(which('bids_spreadsheet2participants.m')), 'JSONio'), 'dir')
            addpath(fullfile(fileparts(which('bids_spreadsheet2participants.m')), 'JSONio'));
        else
            error(['JSONio library needed, available with EEGLAB bids-matlab-tools or', ...
                   'at https://github.com/gllmflndn/JSONio']);
        end
    end

    %% deal with input file
    if nargin == 0
        [filename, pathname] = uigetfile({'*.xlsx;*.ods;*.xls'}, 'Pick an spreadsheet file');
        if isequal(filename, 0) || isequal(pathname, 0)
            disp('Selection cancelled');
            return
        else
            filein = fullfile(pathname, filename);
            disp(['file selected: ', filein]);
        end
    else
        filein = varargin{1};
        if ~exist(filein, 'file')
            error('%s not found', filein);
        end
    end

    % detect what's inside the selected file
    sheet1_opts = detectImportOptions(filein, 'Sheet', 1);
    try
        sheet2_opts = detectImportOptions(filein, 'PreserveVariableNames', true, 'Sheet', 2);
    catch one_sheet
        fprintf('import only variables: %s\n', one_sheet.message);
        sheet2_opts = [];
    end

    %% quickly check other arguments are valid
    if nargin > 1
        if ~any(contains(varargin, {'ignore', 'export'}, 'IgnoreCase', true))
            error('key input arguments in are missing ''ignore'' and/or ''export_dir''');
        else
            if any(contains(varargin, 'export', 'IgnoreCase', true))
                export_dir = varargin{find(contains(varargin, 'export', 'IgnoreCase', true)) + 1};
            end

            if any(contains(varargin, 'ignore', 'IgnoreCase', true))
                if any(contains(varargin, 'export', 'IgnoreCase', true))
                    if find(contains(varargin, 'ignore', 'IgnoreCase', true)) < ...
                      find(contains(varargin, 'export', 'IgnoreCase', true))
                        ignore_var = find(contains(varargin, 'ignore', 'IgnoreCase', true)) + ...
                          1:find(contains(varargin, 'export', 'IgnoreCase', true)) - 1;
                    else
                        ignore_var = find(contains(varargin, 'ignore', 'IgnoreCase', true)) + 1:nargin;
                    end
                else
                    ignore_var = find(contains(varargin, 'ignore', 'IgnoreCase', true)) + 1:nargin;
                end
            end
        end
    end

    %% create particpiants.tsv
    Data = readtable(filein, sheet1_opts);

    % 1st check mandatory/recommended fields are presents
    participant_idvar = any(strcmpi(Data.Properties.VariableNames, 'participant_id'));
    idvar = any(strcmpi(Data.Properties.VariableNames, 'id'));
    if idvar == 0 && participant_idvar == 0
        error('no participant ID variable detected, invalid file');
    elseif idvar == 1 && participant_idvar == 0
        disp('ID variable detected, the BIDS value exported will be participant_id ');
        Data.Properties.VariableNames(strcmpi(Data.Properties.VariableNames, 'id')) = {'participant_id'};
    end

    if length(unique(Data.participant_id)) ~= length(Data.participant_id)
        [~, idx] = unique(Data.participant_id);
        non_unique = setdiff(1:length(Data.participant_id), idx);
        non_unique = cell2mat(Data.participant_id(non_unique));
        for id = 1:size(non_unique, 1)
            warning('some IDs are duplicate: %s', non_unique(id, :));
        end
        error('non unique IDS are not permitted');
    end

    if any(strcmp(Data.Properties.VariableNames, 'Sex'))
        Data.Properties.VariableNames(strcmpi(Data.Properties.VariableNames, 'Sex')) = {'sex'};
    end
    sexvar = any(strcmpi(Data.Properties.VariableNames, 'sex'));
    gendervar = any(strcmpi(Data.Properties.VariableNames, 'gender'));
    if gendervar == 1 && sexvar == 0
        disp('gender variable detected - the recommended optional field is ''sex'' but ok using gender');
    end

    if any(strcmp(Data.Properties.VariableNames, 'Age'))
        Data.Properties.VariableNames(strcmpi(Data.Properties.VariableNames, 'Age')) = {'age'};
    end
    if ~any(strcmpi(Data.Properties.VariableNames, 'age'))
        disp('age variable is missing - this is only an optional field still exporting');
    else
        if ~isnumeric(Data.age)
            for v = 1:length(Data.age)
                Data.age{v} = str2double(Data.age{v});
            end
            Data.age = cell2mat(Data.age); % ensure age treated as number
        end
    end

    % 2nd check if any of those variable should be removed - and feedback if
    % some input variable names were not found
    if nargin > 1
        if any(contains(varargin, 'ignore', 'IgnoreCase', true))
            for var = ignore_var
                if any(contains(Data.Properties.VariableNames, varargin{var}, 'IgnoreCase', true))
                    Data = removevars(Data, varargin{var});
                else
                    fprintf('''%s'' input variable to ignore not recognized - skipping it\n', varargin{var});
                end
            end
        else
            warning('ignore argument not recognized - skipping any options');
        end
    end

    % last check variable values (also useful for metadata)
    if any(~contains(Data.participant_id, 'sub-'))
        disp('participant_id should include ''sub-'', adding it to current IDs');
        for sub = 1:length(Data.participant_id)
            if ~contains(Data.participant_id{sub}, 'sub-')
                Data.participant_id{sub} = ['sub-' Data.participant_id{sub}];
            end
        end
    end

    value_types = varfun(@class, Data, 'OutputFormat', 'cell');
    for v = length(value_types):-1:1
        values{v} = unique(Data.(Data.Properties.VariableNames{v}));
    end

    export_data = convert_NAN_to_na(Data);

    if ~exist('export_dir', 'var')
        export_dir = uigetdir(fileparts(filein), 'Select directory to save exported files');
        if export_dir == 0
            export_dir = fileparts(filein);
        end
    end
    try
        writetable(export_data, [export_dir filesep 'participants.tsv'], 'FileType', 'text', 'Delimiter', '\t');
        files_out{1} = fullfile(export_dir, 'participants.tsv');
        fprintf('participants.tsv file saved in %s\n', export_dir);
        clear export_data;
    catch tsv_error
        files_out{2} = 'particpants.tsv file not created';
        error('participants.tsv not saved %s\n', tsv_error.mewsage);
    end

    %% create participants.json
    if ~isempty(sheet2_opts)
        GivenMetaData = readtable(filein, sheet2_opts);

        if any(contains(GivenMetaData.Properties.VariableNames, 'Variable'))
            GivenMetaData.Properties.VariableNames(strcmpi(GivenMetaData.Properties.VariableNames, 'Variable')) = ...
              {'variables'};
        elseif any(contains(GivenMetaData.Properties.VariableNames, 'variable'))
            GivenMetaData.Properties.VariableNames(strcmpi(GivenMetaData.Properties.VariableNames, 'variable')) = ...
              {'variables'};
        end

        if ~any(strcmpi('variables', GivenMetaData.Properties.VariableNames))
            warning('metadata sheet provided but ''variables name'' not found - using worksheet 1');
        else
            % given variables present check description
            if ~any(contains(GivenMetaData.Properties.VariableNames, 'description', 'IgnoreCase', true))
                warning('metadata sheet provided but ''description of variables'' not found - using worksheet 1');
            else
                matched_var = cellfun(@(x) find(strcmpi(x, Data.Properties.VariableNames)), GivenMetaData.variables, ...
                                      'UniformOutput', false);
                matched_var(cellfun(@(x) isempty(x), matched_var')) = {0}; % mark empty as 0
                try
                    matched_var = cell2mat(matched_var); % works if unique, ie one value per cell
                    if any(contains(GivenMetaData.Properties.VariableNames, 'description'))
                        GivenMetaData.Properties.VariableNames(strcmpi(GivenMetaData.Properties.VariableNames, ...
                                                                       'description')) = ...
                          {'Description'};
                    end
                catch matched_var_err
                    warning(['variable names between worksheets likely not unique,', ...
                             ' skipping provided metadata \n%s\n'], ...
                            matched_var_err.message); %#ok<MEXCEP>
                end
            end

            % given variables present check levels
            if ~any(contains(GivenMetaData.Properties.VariableNames, 'level', 'IgnoreCase', true))
                warning('metadata sheet provided but ''levels'' not found - using worksheet 1');
            else
                if any(contains(GivenMetaData.Properties.VariableNames, 'level'))
                    GivenMetaData.Properties.VariableNames(strcmpi(GivenMetaData.Properties.VariableNames, ...
                                                                   'level')) = ...
                      {'Levels'};
                elseif any(contains(GivenMetaData.Properties.VariableNames, 'levels'))
                    GivenMetaData.Properties.VariableNames(strcmpi(GivenMetaData.Properties.VariableNames, ...
                                                                   'levels')) = ...
                      {'Levels'};
                end
            end

            % given variables present check unit
            if ~any(contains(GivenMetaData.Properties.VariableNames, 'unit', 'IgnoreCase', true))
                warning('metadata sheet provided but ''Units'' of variables not found');
            else
                if any(contains(GivenMetaData.Properties.VariableNames, 'unit'))
                    GivenMetaData.Properties.VariableNames(strcmpi(GivenMetaData.Properties.VariableNames, ...
                                                                   'unit')) = ...
                      {'Units'};
                elseif any(contains(GivenMetaData.Properties.VariableNames, 'units'))
                    GivenMetaData.Properties.VariableNames(strcmpi(GivenMetaData.Properties.VariableNames, ...
                                                                   'units')) = ...
                      {'Units'};
                end
            end
        end
    else
        matched_var = zeros(length(Data.Properties.VariableNames), 1);
    end

    % prepare json variables from worksheet 1
    % 'values' are the unique values read on the data
    % 'value_types' gives an indication if we want to use Level or Unit
    % we can complement with GivenMetaData.Description, GivenMetaData.Levels and GivenMetaData.Units
    % using 'matched_var' for instance: variables names match between worksheet

    json = struct;
    for var = 1:length(Data.Properties.VariableNames)
        if ~strcmpi(Data.Properties.VariableNames{var}, 'participant_id')
            % add description
            if ismember(var, matched_var)
                if any(contains(GivenMetaData.Properties.VariableNames, 'Description'))
                    if ~isempty(GivenMetaData.Description{matched_var == var})
                        json.(Data.Properties.VariableNames{var}).Description = ...
                          GivenMetaData.Description{matched_var == var};
                    else
                        json.(Data.Properties.VariableNames{var}).Description = [];
                    end
                end
            else
                json.(Data.Properties.VariableNames{var}).Description = [];
            end

            % add Levels and Units
            if strcmpi(value_types{var}, 'cell') % likely Levels
                if ismember(var, matched_var) && any(contains(GivenMetaData.Properties.VariableNames, 'Levels'))
                    if ~isempty(GivenMetaData.Levels{matched_var == var})
                        tmp = lower(GivenMetaData.Levels{matched_var == var});
                        tmp = regexprep(tmp, '\t', ';'); % in case tab is used
                        tmp(isspace(tmp)) = []; % deblank as well
                        separators = sort([strfind(tmp, ';') strfind(tmp, ',') length(tmp)]);
                        index1 = 1;
                        index2 = separators(1) - 1;
                        for l = 1:length(separators)
                            Levels{l} = tmp(index1:index2);
                            if l < length(separators)
                                index1 = separators(l) + 1;
                                index2 = separators(l + 1);
                            end
                        end

                        tmp = values{var};
                        tmp(strcmpi(tmp, 'n/a')) = [];
                        tmp(strcmpi(tmp, 'nill')) = [];
                        if length(tmp) ~= length(Levels)
                            warning('Levels of ''%s'' between data and metadata don''t match', ...
                                    Data.Properties.VariableNames{var});
                            % only write fields
                            for v = 1:length(tmp)
                                json.(Data.Properties.VariableNames{var}).Levels.(tmp{v}) = [];
                            end
                        else
                            % store levels
                            for v = 1:length(tmp)
                                json.(Data.Properties.VariableNames{var}).Levels.(tmp{v}) = Levels{v};
                            end
                        end
                    else
                        json.(Data.Properties.VariableNames{var}).Levels = [];
                    end
                else % no metadata
                    json.(Data.Properties.VariableNames{var}).Levels = values{var};
                end
            else % likely units
                if ismember(var, matched_var) && any(contains(GivenMetaData.Properties.VariableNames, 'Units'))
                    if ~isempty(GivenMetaData.Levels{var})
                        json.(Data.Properties.VariableNames{var}).Units = ...
                          lower(GivenMetaData.Units{matched_var == var});
                    else
                        json.(Data.Properties.VariableNames{var}).Units = [];
                    end
                else % no metadata
                    json.(Data.Properties.VariableNames{var}).Units = [];
                end
            end
        end
    end

    try
        jsonwrite([export_dir filesep 'participants.json'], json, struct('indent', ' '));
        files_out{2} = fullfile(export_dir, 'participants.json');
        fprintf('participants.json file saved in %s\n', export_dir);
        warndlg(sprintf('json file created, do check it ! \n it is almost certain it needs editing'), ...
                'BIDS spec', 'modal');
    catch json_err
        files_out{2} = 'particpants.json file not created';
        error('participants.json not saved %s\n', json_err.mewsage);
    end

end

function export_data = convert_NAN_to_na(data)

    % make sure to export NaN as n/a
    export_data = data;

    for v = 1:length(export_data.Properties.VariableNames)

        if strcmp(class(export_data.(cell2mat(export_data.Properties.VariableNames(v)))), 'double')

            export_data.(cell2mat(export_data.Properties.VariableNames(v))) = ...
              num2cell(export_data.(cell2mat(export_data.Properties.VariableNames(v))));

            for n = 1:size(export_data.(cell2mat(export_data.Properties.VariableNames(v))), 1)
                if isnan(export_data.(cell2mat(export_data.Properties.VariableNames(v))){n})
                    export_data.(cell2mat(export_data.Properties.VariableNames(v))){n} = 'n/a';
                end
            end

        end

    end

end


function FreeDataCache(obj,ndataadd)

while true,

  if obj.ndatacached + ndataadd <= obj.maxdatacached || obj.ndatacached == 0,
    break;
  end
  if isempty(obj.perframehistory),
    break;
  end
  [~,j] = min(cell2mat(obj.perframehistory(:,2)));
  fn = obj.perframehistory{j,1};
  ClearDataCache(obj,fn);
%   for i = 1:obj.nexpdirs,
%     if isfield(obj.datacached{i},fn),
%       ndatacurr = 0;
%       for k = 1:numel(obj.datacached{i}),
%         ndatacurr = ndatacurr + numel(obj.datacached{i}(k).(fn));
%       end
%       obj.datacached{i} = rmfield(obj.datacached{i},fn);
%       obj.ndatacachedperexp(i) = obj.ndatacachedperexp(i) - ndatacurr;
%       obj.ndatacached = obj.ndatacached - ndatacurr;
%     end
%   end

  obj.perframehistory(j,:) = [];

end


classdef PrincipalDirectionComputerFactory < handle

    methods (Access = public, Static)

        function pdc = create(cParams)

            switch cParams.type
                case '2D'
                    pdc = PrincipalDirectionComputerIn2D(cParams);
                case '3D'
                    pdc = PrincipalDirectionComputerIn3D(cParams);
            end
        end

    end

end