Contents

function [config_valid, config_out] = sid_config_manage(config_in)
%SID_CONFIG_MANAGER Summary of this function goes here
%   Detailed explanation goes here
if isunix()
    psf_cache_dir_default = '/dev/shm';
else
    psf_cache_dir_default = tempdir();
end

config_definition = {
    % first column is param name
    % second colum is validator function handle
    % third colum is default value; left empty to indicate required value

REQUIRED PARAMS

    % Input directory containing raw LFM frames in single-page tiff files
    {'indir',                       @isfolder                              'required'};

    % Directory to place output files in
    {'outdir',                      @is_in_existing_dir                    'required'};

    % LFM PSF file as generated using LFrecon package
    % (https://media.nature.com/original/nature-assets/nmeth/journal/v11/n7/extref/nmeth.2964-S2.zip)
    {'psffile',                     @is_existing_file                      'required'};

    % Horizontal position of center of central microlens in LFM raw frames
    % (determine using http://graphics.stanford.edu/software/LFDisplay/)
    {'x_offset',                    @is_double                             'required'};

    % Vertical position of center of central microlens in LFM raw frames
    % (determine using http://graphics.stanford.edu/software/LFDisplay/)
    {'y_offset',                    @is_double                             'required'};

    % Microlens pitch in LFM raw frames
    % (determine using http://graphics.stanford.edu/software/LFDisplay/)
    {'dx',                          @is_double                             'required'};

OPTIONAL PARAMS

Frames to include

    % start:step:end indices of frames to use for demixing
    {'frames.start',                @is_positive_integer_or_zero,          1};
    {'frames.step',                 @is_positive_integer_or_zero,          1};
    {'frames.end',                  @is_positive_integer_or_zero,          inf};

    % Alternative: give explicit list
    {'frames.list',                 @isvector,                             []};

    % Set to true to average over frames in between that ones specified by
    % start:step:end
    {'frames.mean',                 @islogical,                            true};

Mask for valid pixels

    % Image file, in which pixels that should be ignored  are set to 0,
    % others to 1. Use this to ignore irrelevant areas in the input frames.
    {'mask_file',                   @is_existing_file,                     ''};

Physical parameters, neuron size

    % Ratio between the physical length of a voxel in the axial direction vs.
    % the physical length in the lateral direction
    {'axial',                       @is_double,                            4};

    % Typical neuron radius in px. Typically 6 for fish using 20x/0.5NA
    % objective, 9-12 for mouse cortex and 16x/0.8NA
    {'neur_rad',                    @is_double,                            8};

    % Index of native focal plane (=resolution breakdown plane) in
    % reconstructed LFM stack
    {'native_focal_plane',          @isinteger,                            26};

LFM frame rectification

    % Whether or not to rectify the input frames. Set to false if input
    % frames are pre-rectified.
    {'rectify',                     @islogical,                            true};

Other global options

    % Output filename prefix for all generated files
    {'SID_output_name',             @isstring,                             ['sid_result_' datestr(now, 'YY-mm-ddTHHMM') '.mat']};

    % Directory with very high access speed for PSF file caching
    % (ideally, use a RAM-disk such as /dev/shm)
    {'psf_cache_dir',               @is_in_existing_dir,                   psf_cache_dir_default};

    % Directory for temporary files
    {'tmp_dir',                     @is_in_existing_dir,                   tempdir()};

    % List of GPU IDs to use throughout SID. default: [].
    % (Note that in Matlab, gpu_ids start with 1, not 0, as in the output of nvidia-smi)
    {'gpu_ids',                     @isvector,                             []};

    % Whether or not to use standard deviation instead of 2-norm of
    % residual in merit functions of all optimizers
    {'use_std',                     @islogical,                            false};

Background subtraction

    % Number of iterations for low-rank matrix factorization during
    % background subtraction
    {'bg_iter',                     @isinteger,                            2};

    % Enable background subtraction
    {'bg_sub',                      @islogical,                            true};

Detrending

    % Whether to perform detrending prior to NNMF
    {'detrend',                     @islogical,                            true};

    % Half width of sliding window (in units of frames) for low-pass filtering
    % the frame means prior to detrending. Set this to a value that is large
    % compared to the duration of a Ca transient (e.g. 10 times as large),
    % to avoid that the detrending smoothes out true Ca transients.
    {'delta',                       @isinteger,                            100};

Cropping

    % Number of microlenses to crop from input frames on each side
    % [left right top bottom], to avoid border artefacts
    % When giving a value of
    % floor([ix1_lo_border_width ix1_hi_border_width ix2_hi_border_width ix2_hi_border_width] / Nnum)
    % that means that
    % cropped_img = full_img(ix1_lo_border_width + 1 : end - ix1_hi_border_width, ix2_lo_border_width + 1 : end - ix2_hi_border_width)
    {'crop_border_microlenses',     @isvector,                             [0 0 0 0]};

    % Whether to crop frames based on threasholds on standard deviation and
    % background
    {'do_crop',                     @islogical,                            true};

    % Two-element vector, first element is threshold on standard deviation
    % used to crop frames. Second is cut-off threshold on background
    % between microlenses.
    % NOTE: If not given or empty, user is asked for interactive input
    {'crop_params',                 @isvector,                             []};

    % User-defined crop mask, given as an array of the same size as the
    % input frames
    {'crop_mask',                   @ismatrix,                             true};

Low-rank NMF

for more detailed docs on all options, see fast_NMF.m and https://github.com/vazirilab/sid/wiki/Description-and-usage#low-rank-nnmf

    % NMF rank
    {'nnmf_opts.rank',              @isinteger,                            30};

    % Max. number of iterations to perform when computing the NMF
    {'nnmf_opts.max_iter',          @isinteger,                            600};

    % Initialization for NMF. For options, see fast_NMF.m
    {'nnmf_opts.ini_method',        @isstring,                            'pca'};

    % Lagrange multiplier for spatial sparsity (L1 norm of spatial component matrix S)
    {'nnmf_opts.lamb_spat',         @isfloat,                              0};

    % Lagrange multiplier for temporal sparsity (L1 norm of temporal component matrix T)
    {'nnmf_opts.lamb_temp',         @isfloat,                              0};

    % Lagrange multiplier for temporal correlation (L2 norm of covariance matrix)
    {'nnmf_opts.lamb_corr',         @isfloat,                              0};

    % Lagrange multiplier for L1 norm of Gramian of S
    {'nnmf_opts.lamb_orth_L1',      @isfloat,                              5e-4};

    % Lagrange multiplier for L2 norm of Gramian of S
    {'nnmf_opts.lamb_orth_L2',      @isfloat,                              0};

    % Lagrange multiplier for L2 norm of Total Variation of S
    {'nnmf_opts.lamb_spat_TV',      @isfloat,                              0};

    % Lagrange multiplier for L2 norm of Total Variation of T
    {'nnmf_opts.lamb_temp_TV',      @isfloat,                              0};

    % Enable cross-validation
    {'nnmf_opts.xval_enable',       @islogical,                            false};

    % Number of partitions in which the data is decomposed for cross-validation
    {'nnmf_opts.xval_num_part',     @isinteger,                            5};

    % Paramter range of the multiplier that needs to be scanned by xval.
    % For default, see xval.m
    {'nnmf_opts.xval_param',        @islogical,                            []};

LFM reconstruction

for more detailed docs on all options, see reconstruct_S.m and https://github.com/vazirilab/sid/wiki/Description-and-usage#lfm-reconstruction

    % Number of iterations of Richardson-Lucy updates for LFM deconvolution
    {'recon_opts.maxIter',          @isinteger,                            8};

    % Lagrange multiplier for L1-norm of reconstructed LFM volume
    {'recon_opts.lamb_L1',          @isfloat,                              0.1};

    % Lagrange multiplier for L2-norm of reconstructed LFM volume
    {'recon_opts.lamb_L2',          @isfloat,                              0};

    % Lagrange multiplier for Total Variation of reconstructed LFM volume
    {'recon_opts.lamb_TV_L2',       @isfloat,                              0};

    % Neuron shape to allow during reconstruction
    {'recon_opts.ker_shape',        @isstring,                             'user'};

    % Enable automatic learning of neuron shape kernel my iteratively comparing
    % a convolution of current kernel shape with neuron centroids of reconstructed volume with
    % actual reconstructed volume
    {'optimize_kernel',             @islogical,                            true};

    % Band-pass filter the reconstructed NMF componet volumes. Warning: this can take tens of minutes per component and GPU
    {'filter',                      @islogical,                            false};

Segmentation

    % Threshold for accepting local maxima as neuron candidates
    % (increase to reduce over-segmentation)
    {'segmentation.threshold',      @isfloat,                              0.01};

    % Smallest z-plane index from which to accept segmentation centers as neuron condidates
    % (increase to exclude artefacts at top of volume)
    {'segmentation.top_cutoff',     @isinteger,                            1};

    % Largest z-plane index from which to accept segmentation centers as neuron condidates
    % (decrease to exclude artefacts at bottom of volume)
    % if left [], defaults to size(psf_ballistic.H,5)
    {'segmentation.bottom_cutoff',  @isinteger,                            []};

    % Number of iterations to perform when attempting to merge closely spaced
    % neuron candidates from different NMF components by spatial clustering.
    % Maximal size of clusters is determined by parameter neur_rad.
    {'cluster_iter',                @isinteger,                            40};

Neuron footprint dictionary generation

    % If GPU support is enabled and this flag is set to true, generate_LFM_library_CPU.m
    % is used for neuron footprint dictionary generation. Otherwise, generate_LFM_library_GPU.m
    % is used. If GPU support is disabled, generate_LFM_library_CPU.m is used regardless of this flag.
    % (see https://github.com/vazirilab/sid/wiki/Description-and-usage#lfm-footprint-dictionary-generation)
    {'use_std_GLL',                 @islogical,                            false};

Template generation

    % Threshold that determines size of template radius for each neuron candidate.
    % Increase to generate larger templates
    {'template_threshold',          @isfloat,                              0.01};

Bi-convex optimization (main SID demixing)

    % Number of bi-convex SID demixing iterations,
    % where one iteration consist of a spatial and a temporal update
    {'num_iter',                    @isinteger,                            4};

    % Lagrange multiplier for L1-norm (sparsity) regularizer of spatial components during SID demixing
    {'SID_optimization_args.spatial_lamb_L1', @isfloat,                    0};

    % Lagrange multiplier for L2-norm regularizer of spatial components during SID demixing
    {'SID_optimization_args.spatial_lamb_L2', @isfloat,                    0};

    % Lagrange multiplier for L1-norm of Gramian matrix of spatial components during SID demixing
    {'SID_optimization_args.spatial_lamb_orth_L1', @isfloat,               1e-4};

    % Lagrange multiplier for L1-norm (sparsity) regularizer of temporal components during SID demixing
    {'SID_optimization_args.temporal_lambda', @isfloat,                    1e-4};

    % Increase size of templates after each iteration (enable to merge components and thus avoid false positive neurons)
    {'update_template',                       @islogical,                  true};

Timeseries extraction

    % Number of frames to extract timeseries from in one chunk (see incremental_temporal_update_gpu.m)
    % Decrease to avoid out-of-memory errors
    {'ts_extract_chunk_size',       @isinteger,                            200};

Reconstruct final (SID-demixed) neuron spatial filters

    % Whether to reconstruct final (SID-demixed) neuron spatial filters
    % (entirely optional; not required for time series extraction)s
    {'recon_final_spatial_filters', @islogical,                            false};
};

Loop over fields in config_definition. Check if it exists in config_in. If yes, use that value. Else, use default

TODO: add support for varargin input instead of struct TODO: add support for key-value pairs of strings as input (for compiled command line use)

config_out = struct;
config_valid = true;
for i = 1:size(config_definition, 1)
    field_name = config_definition{i}{1};
    substruct_dot_index = strfind(field_name, '.');
    validator = config_definition{i}{2};
    default = config_definition{i}{3};

    if isempty(substruct_dot_index)
        if isfield(config_in, field_name) && validator(config_in.(field_name))
            config_out.(field_name) = config_in.(field_name);
        elseif ~strcmp(default, 'required')
            config_out.(field_name) = default;
        else
            config_valid = false;
            disp(['Required argument not given:' field_name]);
        end
    else
        substruct_name = field_name(1:substruct_dot_index-1);
        substruct_field_name = field_name(substruct_dot_index + 1 : end);
        if isfield(config_in, substruct_name) && isfield(config_in.(substruct_name), substruct_field_name) && validator(config_in.(substruct_name).(substruct_field_name))
            config_out.(substruct_name).(substruct_field_name) = config_in.(substruct_name).(substruct_field_name);
        else
            config_out.(substruct_name).(substruct_field_name) = default;
        end
        if strcmp(default, 'required')
            error('Required arguments cannot be sub-structs. Make it a top-level argument.');
        end
    end
end
end