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