diff --git a/.gitignore b/.gitignore index 15cf9cb..7c7b3a6 100644 --- a/.gitignore +++ b/.gitignore @@ -93,3 +93,4 @@ ENV/ *.asv *.log +*.sublime-workspace diff --git a/src/LS_nnls.m b/src/LS_nnls.m index 4678333..0d50273 100644 --- a/src/LS_nnls.m +++ b/src/LS_nnls.m @@ -1,27 +1,27 @@ -function [X, G] = LS_nnls(A,Y,opts,G,x) -% LS_NNLS is a solver for large non-negative least squares problems. +function [X, G] = LS_nnls(A, Y, opts, G, x) +%% LS_NNLS Solver for large non-negative least-squares problems % -% argmin_{x>=0}||y-A(x)||_2^2 +% Solves argmin_{x>=0}||y-A(x)||_2^2 % % Input: -% A... Matrix corresponding to A in the formula above. -% x... Matrix of solution vectros of the above problem. LS_nnls +% A Matrix corresponding to A in the formula above. +% x Matrix of solution vectros of the above problem. LS_nnls % solves multiple nnls problems in parallel. -% Y... Matrix of inhomogenities, each row represents one nnls +% Y Matrix of inhomogenities, each row represents one nnls % problm. % struct opts -% opts.display... boolean, if true messages will be printed in console. -% opts.lambda... lagrangian multiplier for L1 regularization -% opts.gpu_id... ID of GPU to be used if GPU support is available. -% opts.use_std... calculate least standard deviation instead of L2-norm -% opts.sample... Read about convergence check below! -% opts.tol... - -% opts.tol_... - +% opts.display boolean, if true messages will be printed in console. +% opts.lambda lagrangian multiplier for L1 regularization +% opts.gpu_id ID of GPU to be used if GPU support is available. +% opts.use_std calculate least standard deviation instead of L2-norm +% opts.sample Read about convergence check below! +% opts.tol - +% opts.tol_ - % % Output -% X... Matrix of approximations of the solutions to the +% X Matrix of approximations of the solutions to the % nnls problems. Each row is one solution. -% G... Gram-matrix. +% G Gramian matrix. % % Convergence check: % LS_nnls checks for each of the nnls subproblem if a certain accuracy is @@ -34,7 +34,8 @@ % this information to accurately estimate the true error. If the true error % is below opts.tol the algorithm stops for the nnls-sub-problem in % question and puts the current solution into the output array. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Set default values for parameters not set by user if nargin<3 opts =struct; end @@ -71,8 +72,10 @@ opts.max_iter = 2000; end +%% Compute the first term of the gradient that won’t change throughout the function h = A'*Y - opts.lambda; +%% Assert that the dimensions of the input corresponds to an NNLS problem if nargin<5 x = zeros(size(A,2),size(Y,2)); if nargin<4 @@ -94,6 +97,7 @@ end rds=1:size(Y,2); +%% Compute the second term (the Gramian matrix) that does not change throughout the function if opts.use_std G = @(x) G*x - (G*sum(x,2))/size(Y,2); h = h - A'*sum(Y,2)/size(Y,2); @@ -101,6 +105,7 @@ G = @(x) G*x; end +%% Loop until all sub-problems have been solved to within desired accuracy iter = 0; test=[]; dn=[[1:2:2*opts.sample]', ones(opts.sample,1)]; @@ -108,6 +113,8 @@ tic while ~isempty(x) iter = iter + 1; + + %% Projected gradient descent with exact line search update x_ = gather(x); df = -h + G(x); passive=max(x>0,df<0); @@ -117,10 +124,12 @@ x = x - df_.*alpha; x(x<0) = 0; + %% If max. iterations has not been reached, log consecutive error history (see function help) + % wait for two times opts.sample and then use the error estimator if iter>opts.max_iter ids=true(1,size(x,2)); if opts.display - disp('max number of iterations is reached'); + disp('Max. number of iterations has been reached.'); end elseif ~opts.use_std if mod(iter,2)==1 @@ -144,6 +153,8 @@ ids = []; end + %% If one of the nnls-sub-problems k is finished (id(k)==true), transfer k from the GPU to the output array + % and delete it from the current job description if max(ids(:)) if ~isempty(opts.gpu_id) X(:,rds(ids)) = gather(x(:,ids)); @@ -159,8 +170,9 @@ disp(iter); end end + if opts.display toc end -end \ No newline at end of file +end diff --git a/src/S_update.m b/src/S_update.m index 516327a..3adeff0 100644 --- a/src/S_update.m +++ b/src/S_update.m @@ -1,20 +1,26 @@ -function [S,T]=S_update(Y,S,T,opts) +function [S, T] = S_update(Y, S, T, opts) +%% S_update Perform a gradient descent with exact line search update for the variables in S + T(isnan(T))=0; S(isnan(S))=0; +%% Compute components of the gradient of the 2-norm of Y-S*T Q_T = T*T'; q_S = Y*T'; +%% if opts.use_std opts.T = sum(T,2); - Q_T = Q_T - opts.T*opts.T'/size(T,2); + Q_T = Q_T - opts.T * opts.T'/size(T,2); q_S = q_S - opts.Y.*opts.T'/size(T,2); end +%% Modify matrix Q_T to include the contribution of the 1-norm orthogonality regularizer if opts.lamb_orth_L1 Q_T = Q_T + opts.lamb_orth_L1*(opts.hilf); end +%% Generate a function handle that computes the contribution of the spatial Total Variation regularizer if opts.lamb_spat_TV lb_spat_TV =@(X) opts.lamb_spat_TV*reshape(convn(reshape(X',opts.rank,opts.size(1),... opts.size(2)),laplace,'same'),opts.rank,[])'; @@ -22,10 +28,17 @@ lb_spat_TV =@(X) 0; end +%% Assemble gradient from its components df_S = -q_S + S*Q_T + lb_spat_TV(S) + opts.lamb_spat; +%% Generate the direction v in which the update shall be performed +% v is generated from the gradient by projecting along the normalization constraint +% that is imposed when useing an orthogonality regularizer, +% and by projecting onto the surface of the non-negativity constraint if (opts.lamb_orth_L1 + opts.lamb_orth_L2) if opts.lamb_orth_L2 + % Final assembly of the gradient for the 2-norm orthogonality regularizer + % (indirectly, via modification of the direction v) v = df_S + opts.lamb_orth_L2*(S*(opts.hilf*(S'*S))); else v = df_S; @@ -38,7 +51,11 @@ passive_S = max(S>0,df_S<0); v = passive_S.*df_S; end - + +%% Exact line search for the direction v +% In the case of the 2-norm orthogonality regularizer, the exact line search is approximated by exact line search for the residual gradient only. +% If this results in a value that leads into the opposing direction of the negative gradient, the learning rate is set to a fixed value 1e-6. +% This is done since the corresponding regularizer leads to a non-quadratic problem. if opts.pointwise alpha_S = sum(v.*df_S,2)./sum(v.*(v*Q_T + lb_spat_TV(v)),2); else @@ -51,8 +68,10 @@ alpha_S(isnan(alpha_S))=0; alpha_S(isinf(alpha_S))=0; +%% Update S S = S - alpha_S.*v; +%% Project onto constraints S(S<0)=0; if opts.lamb_orth_L1 + opts.lamb_orth_L2 @@ -61,6 +80,7 @@ S = S./platz; end +%% Output diagnostic info if opts.diagnostic figure(1); clf('reset') @@ -74,5 +94,4 @@ drawnow expose end - -end \ No newline at end of file +end diff --git a/src/T_update.m b/src/T_update.m index fa219c6..4601ea0 100644 --- a/src/T_update.m +++ b/src/T_update.m @@ -1,4 +1,7 @@ -function [S,T]=T_update(Y,T,S,opts) +function [S, T] = T_update(Y, T, S, opts) +%% T_update Gradient descent with exact line search update for the variables in T + +%% Replace nans with zeros S(isnan(S))=0; T(isnan(T))=0; % line = ~logical(sum(S,1)); @@ -7,6 +10,7 @@ % disp('zero line detected'); % end +%% Normalize T column-wise, and re-scale rows of S with inverse of normalization, for consistency if opts.lamb_corr>0 for u=1:size(T,1) platz = norm(T(u,:)); @@ -15,9 +19,12 @@ end end +%% Compute two essential components of the gradient with regards to T +% namely those who summed up are the gradient of the error in the 2-norm squared between the movie Y and S*T Q_S = S(opts.active,:)'*S(opts.active,:); q_T = S(opts.active,:)'*Y(opts.active,:); +%% if opts.use_std Q = @(x) Q_S*x - (Q_S*sum(x,2))/size(T,2); q_T = q_T - S(opts.active,:)'*opts.Y(opts.active,1)/size(T,2); @@ -25,6 +32,7 @@ Q = @(x) Q_S*x; end +%% Compute necessary terms for the gradient of the correlation regularizer and of the Total Variation regularizer and combine components of the gradient if opts.lamb_corr zsc = zscore(T); N =size(T,2); @@ -36,6 +44,7 @@ df_T = -q_T + Q(T) + opts.lamb_temp; end +%% if opts.lamb_temp_TV hilf=zeros(size(T)+[0,2]); hilf(:,2:end-1)=T; @@ -46,79 +55,82 @@ df_T = df_T + opts.lamb_temp_TV*hilf; end +%% Surface projection on the non-negativity constraint of the gradient passive_T = max(T>0,df_T<0); - df_T_ = passive_T.*df_T; +%% Compute optimal learning rate (exact line search) +% This is not true in the case of the correlation regularizer! if opts.pointwise alpha_T = sum(df_T_.^2,1)./sum(df_T_.*(Q(df_T_)),1); else alpha_T = sum(df_T_(:).^2)/sum(sum(df_T_.*(Q(df_T_)),1),2); end - alpha_T(isnan(alpha_T))=0; alpha_T(isinf(alpha_T))=0; +%% Perform gradient descent step if ~max(isnan(alpha_T(:))) T = T - df_T_.*alpha_T; end -T(T<0)=0; +%% Project back onto the surface of the non-negativity constraint +T(T<0)=0; +%% Diagnostic output if opts.diagnostic -ts = zscore(T(1:10,:), 0, 2); -y_shift = 4; -clip = true; - - sel = 1:size(ts,1); - -nixs = 1:size(ts,1); -sel_nixs = nixs(sel); - -figure(2); -subplot(121); -hold off -for n_ix = 1:floor(numel(sel_nixs)/2) - ax = gca(); - ax.ColorOrderIndex = 1; - loop_ts = ts(sel_nixs(n_ix),:); - if clip - loop_ts(loop_ts > 3*y_shift) = y_shift; - loop_ts(loop_ts < -3*y_shift) = -y_shift; + ts = zscore(T(1:10,:), 0, 2); + y_shift = 4; + clip = true; + + sel = 1:size(ts,1); + + nixs = 1:size(ts,1); + sel_nixs = nixs(sel); + + figure(2); + subplot(121); + hold off + for n_ix = 1:floor(numel(sel_nixs)/2) + ax = gca(); + ax.ColorOrderIndex = 1; + loop_ts = ts(sel_nixs(n_ix),:); + if clip + loop_ts(loop_ts > 3*y_shift) = y_shift; + loop_ts(loop_ts < -3*y_shift) = -y_shift; + end + t = (0:size(ts,2)-1); + plot(t, squeeze(loop_ts) + y_shift*(n_ix-1)); + hold on end - t = (0:size(ts,2)-1); - plot(t, squeeze(loop_ts) + y_shift*(n_ix-1)); - hold on -end -xlabel('Frame'); -xlim([min(t) max(t)]); -hold off; -axis tight; -set(gca,'LooseInset',get(gca,'TightInset')) -legend('boxoff'); - -subplot(122); -hold off -for n_ix = ceil(numel(sel_nixs)/2):numel(sel_nixs) - ax = gca(); - ax.ColorOrderIndex = 1; - loop_ts = ts(sel_nixs(n_ix),:); - if clip - loop_ts(loop_ts > y_shift) = y_shift; - loop_ts(loop_ts < -y_shift) = -y_shift; + xlabel('Frame'); + xlim([min(t) max(t)]); + hold off; + axis tight; + set(gca,'LooseInset',get(gca,'TightInset')) + legend('boxoff'); + + subplot(122); + hold off + for n_ix = ceil(numel(sel_nixs)/2):numel(sel_nixs) + ax = gca(); + ax.ColorOrderIndex = 1; + loop_ts = ts(sel_nixs(n_ix),:); + if clip + loop_ts(loop_ts > y_shift) = y_shift; + loop_ts(loop_ts < -y_shift) = -y_shift; + end + t = (0:size(ts,2)-1); + plot(t, squeeze(loop_ts) + y_shift*(n_ix-1)); + hold on; end - t = (0:size(ts,2)-1); - plot(t, squeeze(loop_ts) + y_shift*(n_ix-1)); - hold on; -end -xlabel('Frame'); -xlim([min(t) max(t)]); -hold off; -axis tight; -set(gca,'LooseInset',get(gca,'TightInset')) -legend('boxoff'); -drawnow expose + xlabel('Frame'); + xlim([min(t) max(t)]); + hold off; + axis tight; + set(gca,'LooseInset',get(gca,'TightInset')) + legend('boxoff'); + drawnow expose end - -end \ No newline at end of file +end diff --git a/src/compute_std_image.m b/src/compute_std_image.m index f26c7a7..814269a 100644 --- a/src/compute_std_image.m +++ b/src/compute_std_image.m @@ -8,6 +8,11 @@ % % Output: % std_image Standard deviation image +% +% To compute the standard deviation image of the difference of a movie and a tensor product +% without computing the difference, and thereby saving memory, the expression +% for the computation of the variance is expanded and reformulated for each summand in terms of +% movie and factors of the tensor product. if nargin<2 y_1=zeros(size(Y,1),1); @@ -20,4 +25,3 @@ std_image=sqrt((A - y_1.^2)/length(y_2) - B.^2 + 2*B.*y_1*C - y_1.^2*C^2); end - diff --git a/src/fast_NMF.m b/src/fast_NMF.m index f2acdda..112f92b 100644 --- a/src/fast_NMF.m +++ b/src/fast_NMF.m @@ -49,7 +49,14 @@ % Ouput: % S... Spatial components of the nnmf % T... Temporal components of the nnmf +% +% This algorithm performs updates on the variable S and T, overall resulting in an implementation of block-wise coordinate descent with exact line search and projected gradient descent. +% Between the lines 131 and 140, +% Between the lines 142 and 152, cross validation is performed, if required. +% The rest of the code consists of the repeated updates performed by S_update and T_update, and in case you activate diagnostic, it contains the computation and plotting of the curve of the objective function and the gram matrix of S. + +%% Set the default values, and in case initialization is required set initial values according to the parameters. if nargin<2 opts=struct; end @@ -137,6 +144,8 @@ S_0=LS_nnls(T_0',Y',option)'; end +%% Modify orthogonality regularizers +% This includes normalization of S and generation of the variable opts.hilf. This variable will be needed when computing the gradients of either of the orthogonality regularizers. if opts.lamb_orth_L1 + opts.lamb_orth_L2 for u=1:size(T_0,1) platz = norm(S_0(:,u)); @@ -148,6 +157,7 @@ opts.hilf(1,1:end) = 0; end +%% Perform cross-validation if isfield(opts,'xval') if opts.display disp('opts before cross validation'); @@ -162,6 +172,9 @@ T = T_0; S =S_0; +%% Iteratively update estimates of S and T +% Actual updates are performed by *S\_update* and *T\_update* +% In case opts.diagnostic is true, objective function and Gramian matrix of S are evaluated and plotted P=[]; E=[]; for iter=1:opts.max_iter @@ -204,4 +217,4 @@ S(:,u) = S(:,u)*platz; end -end \ No newline at end of file +end diff --git a/src/filter_recon.m b/src/filter_recon.m index ffbe593..d98ff13 100644 --- a/src/filter_recon.m +++ b/src/filter_recon.m @@ -1,19 +1,18 @@ -function segmm=filter_recon(recon, opts) -% FILTER_RECON performs a form a band-pass-filtering on the elements of a -% cell array 'recon'. +function segmm = filter_recon(recon, opts) +%% FILTER_RECON Perform a form of band-pass filtering on the elements of cell array 'recon' % % Input: -% recon... cell array of Volume-data +% recon. cell array of Volume-data % struct opts: -% opts.NumWorkers... Number of Workers to be used in the procedure. -% opts.gpu_ids... ID's of GPU's available to the workers. -% opts.border... 1x3 vector; size for boundary padding, for the -% algorithm to generate smooth boundaries, -% to avoid artefacts. +% opts.NumWorkers Number of Workers to be used in the procedure. +% opts.gpu_ids ID's of GPU's available to the workers. +% opts.border 1x3 vector; size for boundary padding, for the +% algorithm to generate smooth boundaries, to avoid artefacts. % % Output: -% segmm... cell array of band-pass-filtered Volumes -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% segmm cell array of band-pass-filtered Volumes + +%% Set default values for parameters not set by user [~,u] = max([size(recon,1),size(recon,2)]); poolobj = gcp('nocreate'); @@ -22,6 +21,7 @@ opts.NumWorkers=length(opts.gpu_ids); end +%% Check if poolobj has the right number of workers, if not delete it and start a parallel pool with the right number of workers. if ~isfield(opts,'NumWorkers') if isfield(opts,'gpu_ids')&&(~isempty(opts.gpu_ids)) opts.NumWorkers=length(opts.gpu_ids); @@ -46,6 +46,8 @@ n=1; gimp=-1; end + +%% Loop over problem subsets of size NumWorker segmm=recon; decay = (1/100)^(1/min(opts.border(3),2*opts.neur_rad/opts.axial)); @@ -54,8 +56,12 @@ si_V=cell(opts.NumWorkers,1); siz_I=cell(opts.NumWorkers,1); segm_=cell(opts.NumWorkers,1); + + %% Prepare all jobs for the current problem subset for worker=1:min(opts.NumWorkers,size(recon,u)-(kk-1)) - k=kk+worker-1; + k=kk+worker-1; + + %% If the filter size is smaller than 5, up-sample the volumes by linear interpolation if opts.neur_rad<5 [X,Y,Z]=meshgrid(1:2:2*size(recon{k},2)-1,1:2:2*size(recon{k},1)-1,... [1:opts.native_focal_plane-1 opts.native_focal_plane+1:size(recon{k},3)]); @@ -70,6 +76,9 @@ else V=recon{k}; end + + %% Set smooth boundaries + % This is necessary, since a sharp fall to zero, as it would occur with zero padding, would lead to artefacts si_V{worker}=size(V,3); I=zeros(size(V)+[0 0 2*opts.border(3)],'single'); I(:,:,1+opts.border(3):si_V{worker}+opts.border(3))=single(V); @@ -80,12 +89,18 @@ img{worker}=full(I/max(I(:))); siz_I{worker}=size(I); end + + %% If GPU is not available, perform serial band pass filtering if min(gimp)==-1 for worker=1:min(opts.NumWorkers,size(recon,u)-(kk-1)) filtered_Image_=band_pass_filter(img{worker}, cellSize, 3, gimp(mod(worker-1,n)+1),1.2); segm_{worker}=filtered_Image_(opts.border(1):size(filtered_Image_,1)-opts.border(1)... ,opts.border(2):size(filtered_Image_,2)-opts.border(2),opts.border(3)+1:opts.border(3)+si_V{worker}); end + + %% Else if GPU is available, each worker controls exactly one GPU and send its job to that GPU. + % The number of workers can exceed the number of GPUs. + % This is only recommended if the multiple jobs on the same GPU do not decrease computation time too much else parfor worker=1:min(opts.NumWorkers,size(recon,u)-(kk-1)) filtered_Image_=band_pass_filter(img{worker}, cellSize, 3, gimp(mod(worker-1,n)+1),1.2); @@ -96,6 +111,9 @@ end end end + + %% Collect current jobs in the right cell of the segmm cell array + % Also, remove the lateral smooth boundary sheets and if necessary down-sample to counter the previous up-sampling for kp=1:min(opts.NumWorkers,size(recon,u)-(kk-1)) filtered_Image=zeros(siz_I{kp}-[0 0 2*opts.border(3)]); filtered_Image(opts.border(1):siz_I{kp}(1)-opts.border(1),opts.border(2):siz_I{kp}(2)... @@ -110,6 +128,7 @@ num2str(min(kk+poolobj.NumWorkers-1,size(recon,u))) ' completed']); end +%% Cut away the smooth boundaries along the z-direction for ix=1:size(recon,u) Vol = recon{ix}*0; Vol(opts.border(3):end-opts.border(3),opts.border(3):end-opts.border(3),:) =... @@ -117,4 +136,4 @@ segmm{ix} = Vol/max(Vol(:)); end -end \ No newline at end of file +end diff --git a/src/rank_1_factorization.m b/src/rank_1_factorization.m index 086b7c8..2fb222a 100644 --- a/src/rank_1_factorization.m +++ b/src/rank_1_factorization.m @@ -1,15 +1,24 @@ function [bg_spatial,bg_temporal]=rank_1_factorization(Y,maxIter) -% RANK_1_FACTORIZATION Rank-1-matrix-factorization of the movie Y +%% RANK_1_FACTORIZATION Rank-1-matrix-factorization of the movie Y % % Y~bg_spatial*bg_temporal % % Input: -% Y... movie -% max_iter... maximum Number of Iterations +% Y movie +% max_iter maximum Number of Iterations % % Output: -% bg_temporal... temporal component of the rank-1-factorization -% bg_spatial... spatial component of the rank-1-factorization +% bg_temporal temporal component of the rank-1-factorization +% bg_spatial spatial component of the rank-1-factorization +% +% This algorithm performs a form a block-wise gradient descent on the objective function +% $$L\\left( s,t \\right) = \\sum\_{\\text{ij}}^{}\\left( Y\_{\\text{ij}} - s\_{i}t\_{j} \\right)^{2}$$ +% Here, s corresponds to *bg\_spatial*, and t corresponds to *bg\_temporal*. +% This can be seen when we calculate the gradient along s and t: +% *D**s**L* = 2(||*s*||22*s* − *s* \* *Y*) +% *D**t**L* = 2(||*t*||22*t* − *Y* \* *t*) +% and set them to zero. Between update we normalize the previously updated component. +% This simplifies the code and leads to better performance in the general case of NNMF. if nargin<2 maxIter=1; @@ -24,6 +33,4 @@ bg_spatial = bg_spatial/norm(bg_spatial(:)); end end - - -end \ No newline at end of file +end diff --git a/src/update_spatial_component.m b/src/update_spatial_component.m index d9295ff..1d1ef30 100644 --- a/src/update_spatial_component.m +++ b/src/update_spatial_component.m @@ -1,24 +1,25 @@ -function [forward_model]=update_spatial_component(timeseries, sensor_movie, template, opts) -% UPDATE_SPATIAL_COMPONENT performs an update of the spatial components, by -% splitting the problem in sub-problems defined by 'template' and solving +function [forward_model] = update_spatial_component(timeseries, sensor_movie, template, opts) +%% UPDATE_SPATIAL_COMPONENT Perform an update of the spatial components +% by splitting the problem in sub-problems defined by 'template' and solving % for each of those sub-problems a non-negative least squares problem. % % Input: -% timeseries... Array of timeseries -% sensor_movie... LFM-movie -% template... binary array assigning each neuron its spatial +% timeseries Array of timeseries +% sensor_movie LFM-movie +% template binary array assigning each neuron its spatial % extent. % struct opts: -% opts.bg_sub... perform background subtraction, treat the problem +% opts.bg_sub perform background subtraction, treat the problem % so that the last component of timeseries is treated % as the background. -% opts.display... boolean, if true print status information into the +% opts.display boolean, if true print status information into the % console. -% opts.lambda... lagrange multiplier for L1-regularizer. +% opts.lambda lagrange multiplier for L1-regularizer. % % Output: -% forward_model... updated forward_model -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% forward_model updated forward_model + +%% Set default values for parameters not set by user if ~isfield(opts,'bg_sub') opts.bg_sub=1; end @@ -27,19 +28,35 @@ opts.display=0; end +%% Order neurons according to the size of their template +% this is done so that sub-problems tend to be smaller. [~, order]=sort(sum(template,2)); +%% Initialize components for the sparse matrix generation I=[]; J=[]; S=[]; +%% Determine which part of the image is not covered by the template +% (and therefore needs to be updated separately if opts.bg_sub is true) outside = ~max(template,[],1); +%% Loop over neurons for neur=1:size(template,1) neuron=order(neur); + + %% Get all indices of pixels where the spatial component can be non-zero according to ‘template’ space=find(template(neuron,:)); + + %% If any such pixels remain proceed to the next line if ~isempty(space) + %% Find all neurons that are non-zero involved_neurons=find(max(template(:,space),[],2)); + + %% Check if the dimensions of timeseries and template have the right number of components + % (according to whether opts.bg_sub is true, or not). + % Generate the sub array of template corresponding to the indices found above. + % If opts.bg_sub is true add a ones() line at the end of the array, to incorporate the background if opts.bg_sub==0 temp=template(involved_neurons,space); if size(template,1)~=size(timeseries,1) @@ -56,11 +73,16 @@ return end end + + %% Get the sub-movie corresponding to the indices found above Y=sensor_movie(space,:); - - opts.Accy=0; + + %% Generate the matrix A for the objective function of the sub-problem and F to store the solution of the sub-problem A=timeseries(involved_neurons,:)'; F=zeros(length(involved_neurons),size(space,2)); + + %% Loop over each pixel corresponding to indices found above. Solve for each pixel separately + opts.Accy=0; for k_=1:length(space) idx=find(squeeze(temp(:,k_))); y=squeeze(Y(k_,:))'; @@ -72,7 +94,9 @@ else F(idx,k_) = 0; end - end + end + + %% Add the current results to the components I, J and S for the sparse matrix generation if size(involved_neurons,2)>=1 template(:,space)=0; [iI, iJ, iS]=find(F); @@ -89,17 +113,21 @@ end end +%% Release GPUs if isfield(opts,'gpu') if opts.gpu gpuDevice([]); end end + +%% Generate sparse matrix S=double(S); forward_model=sparse(I,J,S,size(timeseries,1),size(sensor_movie,1)); +%% Compute the values for the spatial background outside of the area covered by template if opts.bg_sub&&~isempty(outside) Y=sensor_movie(logical(outside),:); forward_model(end,logical(outside))=Y*timeseries(end,:)'; end -end \ No newline at end of file +end