├── .gitignore ├── README.md ├── cont_ca_sampler.m ├── license.txt ├── plot_continuous_samples.m ├── sampling_demo_ar2.m ├── traceData.mat ├── utilities ├── HMC_exact2.m ├── addSpike.m ├── get_initial_sample.m ├── get_next_spikes.m ├── lambda_rate.m ├── make_G_matrix.m ├── make_mean_sample.m ├── plot_marginals.m ├── removeSpike.m ├── replaceSpike.m ├── samples_cell2mat.m ├── tau_c2d.m └── tau_d2c.m └── wrapper.m /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.m~ 3 | 4 | *.m~ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Join the chat at https://gitter.im/epnev/ca_source_extraction](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/epnev/ca_source_extraction?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | 3 | MCMC spike inference in continuous time 4 | ========================== 5 | 6 | The code takes as an input a time series vector of calcium observations 7 | and produces samples from the posterior distribution of the underlying 8 | spike in continuous time. The code also samples the model parameters 9 | (baseline, spike amplitude, initial calcium concentration, firing rate, 10 | noise variance) and also iteratively re-estimates the discrete time 11 | constant of the model. More info can be found at 12 | 13 | Pnevmatikakis, E., Merel, J., Pakman, A. & Paninski, L. (2014). 14 | Bayesian spike inference from calcium imaging data. Asilomar Conf. on 15 | Signals, Systems, and Computers. http://arxiv.org/abs/1311.6864 16 | 17 | For initializing the MCMC sampler, the algorithm uses the constrained deconvolution method maintained separately in https://github.com/epnev/constrained-foopsi 18 | 19 | ### Contributors 20 | 21 | Eftychios A. Pnevmatikakis, Simons Foundation 22 | 23 | John Merel, Columbia University 24 | 25 | ### Contact 26 | 27 | For questions join the chat room (see the button above) or open an issue (for bugs etc). 28 | 29 | ### Acknowledgements 30 | 31 | Special thanks to Tim Machado for providing the demo dataset. 32 | 33 | License 34 | ======= 35 | 36 | This program is free software; you can redistribute it and/or 37 | modify it under the terms of the GNU General Public License 38 | as published by the Free Software Foundation; either version 2 39 | of the License, or (at your option) any later version. 40 | 41 | This program is distributed in the hope that it will be useful, 42 | but WITHOUT ANY WARRANTY; without even the implied warranty of 43 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 44 | GNU General Public License for more details. 45 | 46 | You should have received a copy of the GNU General Public License 47 | along with this program. If not, see . 48 | -------------------------------------------------------------------------------- /cont_ca_sampler.m: -------------------------------------------------------------------------------- 1 | function SAMPLES = cont_ca_sampler(Y,params) 2 | 3 | % MCMC continuous time sampler of spiketimes given a fluorescence trace Y 4 | 5 | % Inputs: 6 | % Y data 7 | % params parameters structure 8 | % params.g discrete time constant(s) (estimated if not provided) 9 | % params.sn initializer for noise (estimated if not provided) 10 | % params.b initializer for baseline (estimated if not provided) 11 | % params.c1 initializer for initial concentration (estimated if not provided) 12 | % params.Nsamples number of samples after burn in (default 400) 13 | % params.B number of burn in samples (default 200) 14 | % params.marg flag for marginalized sampler for baseline and initial concentration (default 0) 15 | % params.upd_gam flag for updating gamma (default 1) 16 | % params.gam_step number of samples after which gamma is updated (default 1) 17 | % params.std_move standard deviation of shifting kernel (default 3*Dt) 18 | % params.add_move number of add moves per iteration (default T/100) 19 | % params.init initial sample 20 | % params.f imaging rate (default 1) 21 | % params.p order of AR model (p == 1 or p == 2, default 1) 22 | % params.defg default discrete time constants in case constrained_foopsi cannot find stable estimates (default [0.6 0.95]) 23 | % params.TauStd standard deviation for time constants in continuous time (default [0.2,2]) 24 | % params.A_lb lower bound for spike amplitude (default 0.1*range(Y)) 25 | % params.b_lb lower bound for baseline (default 0.01 quantile of Y) 26 | % params.c1_lb lower bound for initial value (default 0) 27 | 28 | % output struct SAMPLES 29 | % ss Nsamples x 1 cells with spike times for each sample 30 | % ns Nsamples x 1 vector with number of spikes 31 | % Am Nsamples x 1 vector with samples for spike amplitude 32 | % ld Nsamples x 1 vector with samples for firing rate 33 | 34 | % If marginalized sampler is used (params.marg = 1) 35 | % Cb posterior mean and sd for baseline 36 | % Cin posterior mean and sd for initial condition 37 | % else 38 | % Cb Nsamples x 1 vector with samples for baseline 39 | % Cin Nsamples x 1 vector with samples for initial concentration 40 | % sn Nsamples x 1 vector with samples for noise variance 41 | 42 | % If gamma is updated (params.upd_gam = 1) 43 | % g Nsamples x p vector with the gamma updates 44 | 45 | % Author: Eftychios A. Pnevmatikakis and Josh Merel 46 | 47 | % Reference: Pnevmatikakis et al., Bayesian spike inference from calcium 48 | % imaging data, Asilomar 2013. 49 | 50 | Y = Y(:); 51 | T = length(Y); 52 | isanY = ~isnan(Y); % Deal with possible missing entries 53 | E = speye(T); 54 | E = E(isanY,:); 55 | 56 | % define default parameters 57 | defparams.g = []; % initializer for time constants 58 | defparams.sn = []; % initializer for noise std 59 | defparams.b = []; % initializer for baseline concentration 60 | defparams.c1 = []; % initializer for initial concentration 61 | defparams.c = []; % initializer for calcium concentration 62 | defparams.sp = []; % initializer for spiking signal 63 | defparams.bas_nonneg = 0; % allow negative baseline during initialization 64 | defparams.Nsamples = 400; % number of samples after burn in period 65 | defparams.B = 200; % length of burn in period 66 | defparams.marg = 0; % flag to marginalize out baseline and initial concentration 67 | defparams.upd_gam = 1; % flag for updating time constants 68 | defparams.gam_step = 1; % flag for how often to update time constants 69 | defparams.A_lb = 0.1*range(Y); % lower bound for spike amplitude 70 | defparams.b_lb = quantile(Y,0.01); % lower bound for baseline 71 | defparams.c1_lb = 0; % lower bound for initial concentration 72 | 73 | defparams.std_move = 3; % standard deviation of spike move kernel 74 | defparams.add_move = ceil(T/100); % number of add moves 75 | defparams.init = []; % sampler initializer 76 | defparams.f = 1; % imaging rate (irrelevant) 77 | defparams.p = 1; % order of AR process (use p = 1 or p = 2) 78 | defparams.defg = [0.6,0.95]; % default time constant roots 79 | defparams.TauStd = [.2,2]; % Standard deviation for time constant proposal 80 | defparams.prec = 1e-2; % Precision parameter when adding new spikes 81 | defparams.con_lam = true; % Flag for constant firing across time 82 | defparams.print_flag = 0; 83 | 84 | if nargin < 2 85 | params = defparams; 86 | else 87 | if ~isfield(params,'g'); params.g = defparams.g; end 88 | if ~isfield(params,'sn'); params.sn = defparams.sn; end 89 | if ~isfield(params,'b'); params.b = defparams.b; end 90 | if ~isfield(params,'c1'); params.c1 = defparams.c1; end 91 | if ~isfield(params,'c'); params.c = defparams.c; end 92 | if ~isfield(params,'sp'); params.sp = defparams.sp; end 93 | if ~isfield(params,'bas_nonneg'); params.bas_nonneg = defparams.bas_nonneg; end 94 | if ~isfield(params,'Nsamples'); params.Nsamples = defparams.Nsamples; end 95 | if ~isfield(params,'B'); params.B = defparams.B; end 96 | if ~isfield(params,'marg'); params.marg = defparams.marg; end 97 | if ~isfield(params,'upd_gam'); params.upd_gam = defparams.upd_gam; end 98 | if ~isfield(params,'gam_step'); params.gam_step = defparams.gam_step; end 99 | if ~isfield(params,'std_move'); params.std_move = defparams.std_move; end 100 | if ~isfield(params,'add_move'); params.add_move = defparams.add_move; end 101 | if ~isfield(params,'init'); params.init = defparams.init; end 102 | if ~isfield(params,'f'); params.f = defparams.f; end 103 | if ~isfield(params,'p'); params.p = defparams.p; end 104 | if ~isfield(params,'defg'); params.defg = defparams.defg; end 105 | if ~isfield(params,'TauStd'); params.TauStd = defparams.TauStd; end 106 | if ~isfield(params,'A_lb'); params.A_lb = defparams.A_lb; end 107 | if ~isfield(params,'b_lb'); params.b_lb = defparams.b_lb; end 108 | if ~isfield(params,'c1_lb'); params.c1_lb = defparams.c1_lb; end 109 | if ~isfield(params,'prec'); params.prec = defparams.prec; end 110 | if ~isfield(params,'con_lam'); params.con_lam = defparams.con_lam; end 111 | if ~isfield(params,'print_flag'); params.print_flag = defparams.print_flag; end 112 | end 113 | 114 | Dt = 1; % length of time bin 115 | 116 | marg_flag = params.marg; 117 | gam_flag = params.upd_gam; 118 | gam_step = params.gam_step; 119 | std_move = params.std_move; 120 | add_move = params.add_move; 121 | 122 | if isempty(params.init) 123 | params.init = get_initial_sample(Y,params); 124 | end 125 | SAM = params.init; 126 | 127 | if isempty(params.g) 128 | p = params.p; 129 | else 130 | p = length(params.g); % order of autoregressive process 131 | end 132 | 133 | g = SAM.g(:)'; % check initial time constants, if not reasonable set to default values 134 | 135 | if g == 0 136 | gr = params.defg; 137 | pl = poly(gr); 138 | g = -pl(2:end); 139 | p = 2; 140 | end 141 | gr = sort(roots([1,-g(:)'])); 142 | if p == 1; gr = [0,max(gr)]; end 143 | if any(gr<0) || any(~isreal(gr)) || length(gr)>2 || max(gr)>0.998 144 | gr = params.defg; 145 | end 146 | tau = -Dt./log(gr); 147 | tau1_std = max(tau(1)/100,params.TauStd(1)); 148 | tau2_std = min(tau(2)/5,params.TauStd(2)); 149 | ge = max(gr).^(0:T-1)'; 150 | if p == 1 151 | G1 = sparse(1:T,1:T,Inf*ones(T,1)); 152 | elseif p == 2 153 | G1 = spdiags(ones(T,1)*[-min(gr),1],[-1:0],T,T); 154 | else 155 | error('This order of the AR process is currently not supported'); 156 | end 157 | G2 = spdiags(ones(T,1)*[-max(gr),1],[-1:0],T,T); 158 | 159 | sg = SAM.sg; 160 | 161 | spiketimes_ = SAM.spiketimes_; 162 | lam_ = SAM.lam_; 163 | A_ = SAM.A_*diff(gr); 164 | b_ = max(SAM.b_,prctile(Y,8)); 165 | C_in = max(min(SAM.C_in,Y(1)-b_),0); 166 | 167 | s_1 = zeros(T,1); 168 | s_2 = zeros(T,1); 169 | s_1(ceil(spiketimes_/Dt)) = exp((spiketimes_ - Dt*ceil(spiketimes_/Dt))/tau(1)); 170 | s_2(ceil(spiketimes_/Dt)) = exp((spiketimes_ - Dt*ceil(spiketimes_/Dt))/tau(2)); 171 | 172 | prec = params.prec; 173 | 174 | ef_d = exp(-(0:T)/tau(2)); % construct transient exponentials 175 | if p == 1 176 | h_max = 1; % max value of transient 177 | ef_h = [0,0]; 178 | e_support = find(ef_dT*Dt) = 2*T*Dt - spiketimes(spiketimes>T*Dt); 246 | spiketimes_ = spiketimes; 247 | trunc_spikes = ceil(spiketimes/Dt); 248 | trunc_spikes(trunc_spikes == 0) = 1; 249 | s_1 = zeros(T,1); 250 | s_2 = zeros(T,1); 251 | s_1(trunc_spikes) = exp((spiketimes_ - Dt*trunc_spikes)/tau(1)); 252 | s_2(trunc_spikes) = exp((spiketimes_ - Dt*trunc_spikes)/tau(2)); 253 | if p == 1; G1sp = zeros(T,1); else G1sp = G1\s_1(:); end 254 | Gs = (-G1sp+G2\s_2(:))/diff(gr); 255 | 256 | %s_ = s_2 - s_1 - min(gr)*[0;s_2(1:end-1)] + max(gr)*[0;s_1(1:end-1)]; 257 | %Gs = filter(1,[1,-sum(gr),prod(gr)]',s_/diff(gr)); 258 | 259 | ss{i} = spiketimes; 260 | nsp = length(spiketimes); 261 | ns(i) = nsp; 262 | lam(i) = nsp/(T*Dt); 263 | lam_ = lam(i); 264 | 265 | AM = [Gs,ones(T,1),ge]; 266 | EAM = E*AM; 267 | L = inv(Ld + EAM'*EAM/sg^2); 268 | mu_post = (Ld + EAM'*EAM/sg^2)\(EAM'*Y(isanY)/sg^2 + Sp\mu); 269 | if ~marg_flag 270 | x_in = [A_;b_;C_in]; 271 | if any(x_in <= lb) 272 | x_in = max(x_in,(1+0.1*sign(lb)).*lb) + 1e-5; 273 | end 274 | if all(isnan(L(:))) % FN added to avoid error in R = chol(L) in HMC_exact2 due to L not being positive definite. It happens when isnan(det(Ld + AM'*AM/sg^2)), ie when Ld + AM'*AM/sg^2 is singular (not invertible). 275 | Am(i) = NaN; 276 | Cb(i) = NaN; 277 | Cin(i) = NaN'; 278 | else 279 | [temp,~] = HMC_exact2(eye(3), -lb, L, mu_post, 1, Ns, x_in); 280 | Am(i) = temp(1,Ns); 281 | Cb(i) = temp(2,Ns); 282 | Cin(i) = temp(3,Ns)'; 283 | end 284 | A_ = Am(i); 285 | b_ = Cb(i); 286 | C_in = Cin(i); 287 | 288 | Ym = Y - b_ - ge*C_in; 289 | res = Ym - A_*Gs; 290 | sg = 1./sqrt(gamrnd(1+length(isanY)/2,1/(0.1 + sum((res(isanY).^2)/2)))); 291 | SG(i) = sg; 292 | else 293 | repeat = 1; 294 | cnt = 0; 295 | while repeat 296 | A_ = mu_post(1) + sqrt(L(1,1))*randn; 297 | repeat = (A_<0); 298 | cnt = cnt + 1; 299 | if cnt > 1e3 300 | error('The marginalized sampler cannot find a valid amplitude value. Set params.marg = 0 and try again.') 301 | end 302 | end 303 | Am(i) = A_; 304 | if i > B 305 | mub = mub + mu_post(2:3); %mu_post(1+(1:p)); 306 | Sigb = Sigb + L(2:3,2:3); %L(1+(1:p),1+(1:p)); 307 | end 308 | end 309 | if gam_flag 310 | if mod(i-B,gam_step) == 0 % update time constants 311 | if p >= 2 % update rise time constant 312 | logC = -norm(Y(isanY) - EAM*[A_;b_;C_in])^2; 313 | tau_ = tau; 314 | tau_temp = tau_(1)+(tau1_std*randn); 315 | while tau_temp >tau(2) || tau_temptau_max || tau_temp 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /plot_continuous_samples.m: -------------------------------------------------------------------------------- 1 | function plot_continuous_samples(SAMPLES,Y) 2 | 3 | % plot results of MCMC sampler 4 | % The mean calcium sample, spike sampler raster plot and samples for 5 | % amplitude, number of spikes, discrete time constants, noise variance, 6 | % baseline and initial concentration are generated, together with their 7 | % autocorrelation functions. If the marginalized flag was used, then the 8 | % posterior pdfs of baseline and initial concentration are plotted. 9 | 10 | % Inputs: 11 | % SAMPLES: structure with SAMPLES obtained from cont_ca_sampler.m 12 | % Y: inpurt fluorescence trace 13 | 14 | % Author: Eftychios A. Pnevmatikakis, 2016, Simons Foundation 15 | 16 | T = length(Y); 17 | N = length(SAMPLES.ns); 18 | show_gamma = 1; 19 | P = SAMPLES.params; 20 | P.f = 1; 21 | g = P.g(:); 22 | p = min(length(g),2); 23 | Dt = 1/P.f; % length of time bin 24 | if ~isfield(SAMPLES,'g'); 25 | show_gamma = 0; 26 | SAMPLES.g = ones(N,1)*g'; 27 | end 28 | 29 | if p == 1 30 | tau_1 = 0; 31 | tau_2 = -Dt/log(g); % continuous time constant 32 | G1 = speye(T); 33 | G2 = spdiags(ones(T,1)*[-g,1],[-1:0],T,T); 34 | ge = P.g.^((0:T-1)'); 35 | elseif p == 2 36 | gr = roots([1,-g']); 37 | p1_continuous = log(min(gr))/Dt; 38 | p2_continuous = log(max(gr))/Dt; 39 | tau_1 = -1/p1_continuous; %tau h - smaller (tau_d * tau_r)/(tau_d + tau_r) 40 | tau_2 = -1/p2_continuous; %tau decay - larger 41 | G1 = spdiags(ones(T,1)*[-min(gr),1],[-1:0],T,T); 42 | G2 = spdiags(ones(T,1)*[-max(gr),1],[-1:0],T,T); 43 | ge = G2\[1;zeros(T-1,1)]; 44 | else 45 | error('This order of the AR process is currently not supported'); 46 | end 47 | 48 | 49 | if length(SAMPLES.Cb) == 2 50 | marg = 1; % marginalized sampler 51 | else 52 | marg = 0; % full sampler 53 | end 54 | 55 | C_rec = zeros(N,T); 56 | for rep = 1:N 57 | %trunc_spikes = ceil(SAMPLES.ss{rep}/Dt); 58 | tau = SAMPLES.g(rep,:); 59 | gr = exp(-1./tau); 60 | ge = max(gr).^(0:T-1)'; 61 | s_1 = sparse(ceil(SAMPLES.ss{rep}/Dt),1,exp((SAMPLES.ss{rep} - Dt*ceil(SAMPLES.ss{rep}/Dt))/tau(1)),T,1); 62 | s_2 = sparse(ceil(SAMPLES.ss{rep}/Dt),1,exp((SAMPLES.ss{rep} - Dt*ceil(SAMPLES.ss{rep}/Dt))/tau(2)),T,1); 63 | if gr(1) == 0 64 | G1 = sparse(1:T,1:T,Inf*ones(T,1)); G1sp = zeros(T,1); 65 | else 66 | G1 = spdiags(ones(T,1)*[-min(gr),1],[-1:0],T,T); G1sp = G1\s_1(:); 67 | end 68 | G2 = spdiags(ones(T,1)*[-max(gr),1],[-1:0],T,T); 69 | Gs = (-G1sp+ G2\s_2(:))/diff(gr); 70 | if marg 71 | %C_rec(rep,:) = SAMPLES.Cb(1) + SAMPLES.Am(rep)*filter(1,[1,-SAMPLES.g(rep,:)],full(s_)+[SAMPLES.Cin(:,1)',zeros(1,T-p)]); 72 | C_rec(rep,:) = SAMPLES.Cb(1) + SAMPLES.Am(rep)*Gs + (ge*SAMPLES.Cin(:,1)); 73 | else 74 | %C_rec(rep,:) = SAMPLES.Cb(rep) + SAMPLES.Am(rep)*filter(1,[1,-SAMPLES.g(rep,:)],full(s_)+[SAMPLES.Cin(rep,:),zeros(1,T-p)]); 75 | C_rec(rep,:) = SAMPLES.Cb(rep) + SAMPLES.Am(rep)*Gs + (ge*SAMPLES.Cin(rep,:)'); 76 | end 77 | end 78 | Nc = 60; 79 | 80 | if marg 81 | rows = 4; 82 | else 83 | rows = 5; 84 | end 85 | 86 | figure; 87 | set(gcf, 'PaperUnits', 'inches','Units', 'inches') 88 | set(gcf, 'PaperPositionMode', 'manual') 89 | set(gcf, 'PaperPosition',[0,0, 14, 15]) 90 | set(gcf, 'Position',[2,2, 14, 15]) 91 | ha(1) = subplot(rows,4,[1:4]);plot(Dt*(1:T),Y); hold all; plot(Dt*(1:T),mean(C_rec,1),'linewidth',2); 92 | title('Calcium traces','fontweight','bold','fontsize',14) 93 | legend('Raw data','Mean sample'); 94 | ha(2) = subplot(rows,4,[5:8]); imagesc((1:T)*Dt,1:N,samples_cell2mat(SAMPLES.ss,T)); 95 | title('Spike raster plot','fontweight','bold','fontsize',14) 96 | linkaxes(ha,'x'); 97 | subplot(rows,4,4+5); plot(1:N,SAMPLES.ns); title('# of spikes','fontweight','bold','fontsize',14) 98 | subplot(rows,4,4+6); plot(-Nc:Nc,xcov(SAMPLES.ns,Nc,'coeff')); set(gca,'XLim',[-Nc,Nc]); 99 | title('Autocorrelation','fontweight','bold','fontsize',14) 100 | 101 | if ~show_gamma 102 | subplot(rows,4,4+7); plot(1:N,SAMPLES.ld); title('Firing Rate','fontweight','bold','fontsize',14) 103 | subplot(rows,4,4+8); plot(-Nc:Nc,xcov(SAMPLES.ld,Nc,'coeff')); set(gca,'XLim',[-Nc,Nc]) 104 | title('Autocorrelation','fontweight','bold','fontsize',14) 105 | else 106 | if gr(1) == 0 107 | subplot(rows,4,4+7); plot(1:N,exp(-1./SAMPLES.g(:,2))); title('Decay Time Constant','fontweight','bold','fontsize',14); 108 | subplot(rows,4,4+8); plot(-Nc:Nc,xcov(exp(-1./SAMPLES.g(:,2)),Nc,'coeff')); title('Autocorrelation','fontweight','bold','fontsize',14); 109 | set(gca,'XLim',[-Nc,Nc]) 110 | else 111 | subplot(rows,4,4+7); plot(1:N,exp(-1./SAMPLES.g)); title('Decay Time Constants','fontweight','bold','fontsize',14); 112 | g_cov = xcov(exp(-1./SAMPLES.g),Nc,'coeff'); 113 | subplot(rows,4,4+8); plot(-Nc:Nc,g_cov(:,[1,4])); title('Autocorrelation','fontweight','bold','fontsize',14); 114 | set(gca,'XLim',[-Nc,Nc]) 115 | end 116 | end 117 | 118 | subplot(rows,4,4+9); plot(1:N,SAMPLES.Am); title('Spike Amplitude','fontweight','bold','fontsize',14) 119 | subplot(rows,4,4+10); plot(-Nc:Nc,xcov(SAMPLES.Am,Nc,'coeff')); set(gca,'XLim',[-Nc,Nc]) 120 | title('Autocorrelation','fontweight','bold','fontsize',14) 121 | if marg 122 | xx = SAMPLES.Cb(1) + linspace(-4*SAMPLES.Cb(2),4*SAMPLES.Cb(2)); 123 | subplot(4,4,15); plot(xx,normpdf(xx,SAMPLES.Cb(1),SAMPLES.Cb(2))); 124 | set(gca,'XLim',[xx(1),xx(end)]) 125 | title('Marg. post. of baseline','fontweight','bold','fontsize',14) 126 | 127 | xx = SAMPLES.Cin(1) + linspace(-4*SAMPLES.Cin(2),4*SAMPLES.Cin(2)); 128 | subplot(4,4,16); plot(xx,normpdf(xx,SAMPLES.Cin(1),SAMPLES.Cin(2))); 129 | set(gca,'XLim',[xx(1),xx(end)]) 130 | title('Marg. post. of initial con','fontweight','bold','fontsize',14) 131 | else 132 | subplot(5,4,4+11); plot(1:N,SAMPLES.Cb); title('Baseline','fontweight','bold','fontsize',14) 133 | subplot(5,4,4+12); plot(-Nc:Nc,xcov(SAMPLES.Cb,Nc,'coeff')); set(gca,'XLim',[-Nc,Nc]) 134 | title('Autocorrelation','fontweight','bold','fontsize',14) 135 | subplot(5,4,4+13); plot(1:N,SAMPLES.Cin); title('Initial Concentration','fontweight','bold','fontsize',14) 136 | xcov_Cin = xcov(SAMPLES.Cin,Nc,'coeff'); 137 | subplot(5,4,4+14); plot(-Nc:Nc,xcov_Cin); set(gca,'XLim',[-Nc,Nc]) 138 | title('Autocorrelation','fontweight','bold','fontsize',14) 139 | subplot(5,4,4+15); plot(1:N,SAMPLES.sn2); title('Noise variance','fontweight','bold','fontsize',14) 140 | subplot(5,4,4+16); plot(-Nc:Nc,xcov(SAMPLES.sn2,Nc,'coeff')); set(gca,'XLim',[-Nc,Nc]) 141 | title('Autocorrelation','fontweight','bold','fontsize',14) 142 | end 143 | drawnow; -------------------------------------------------------------------------------- /sampling_demo_ar2.m: -------------------------------------------------------------------------------- 1 | % creating a demo for the MCMC sampler 2 | 3 | clearvars; 4 | addpath utilities 5 | dt = 1e-1; % Data is generated with provided dt but sampled with Dt = 1 6 | T = 7000; 7 | ld = 0.05; % rate spikes per second 8 | 9 | s = rand(1,round(T/dt)) < ld*dt; 10 | tau_rise = 2; 11 | tau_decay = 10; 12 | hmax = tau_decay/(tau_decay+tau_rise)*(tau_rise/(tau_decay+tau_rise))^(tau_rise/tau_decay); 13 | [g,h1] = tau_c2d(tau_rise,tau_decay,dt); 14 | 15 | b = hmax/4; % baseline value 16 | cin = [.2*b,.15*b]; % initial concentration 17 | c = [cin,filter(h1,[1,-g],s(3:end),filtic(h1,[1,-g],cin))] + b; % true calcium at original resolution 18 | c_true = c(round(1/dt):round(1/dt):round(T/dt)); % true calcium at sampled resolution 19 | sg = hmax/4; % noise level 20 | y = c_true + sg*randn(1,length(c_true)); % noisy observations 21 | 22 | figure;subplot(2,1,1); plot(dt:dt:T,c); hold all; scatter(1:T,y,'r*'); 23 | legend('True Calcium','Observed Values'); 24 | 25 | %% Run constrained deconvolution approach (constrained foopsi) 26 | 27 | [g2,h2] = tau_c2d(tau_rise,tau_decay,1); % generate discrete time constants 28 | 29 | [ca_foopsi,cb,c1,~,~,spikes_foopsi] = constrained_foopsi(y,[],[],g2); 30 | 31 | % do some plotting 32 | spiketimes{1} = find(s)*dt; 33 | spikeRaster = samples_cell2mat(spiketimes,T,1); 34 | f = find(spikeRaster); 35 | spikes = zeros(sum(spikeRaster),2); 36 | count = 0; 37 | for cnt = 1:length(f) 38 | spikes(count+(1:spikeRaster(f(cnt))),1) = f(cnt); 39 | spikes(count+(1:spikeRaster(f(cnt))),2) = 0.95 + 0.025*max(spikes_foopsi)*(1:spikeRaster(f(cnt))); 40 | count = count + spikeRaster(f(cnt)); 41 | end 42 | 43 | subplot(2,1,2); 44 | stem(spikes_foopsi); hold all; 45 | scatter(spikes(:,1),spikes(:,2)-0.95+max(spikes_foopsi),15,'magenta','filled'); 46 | axis([1,T,0,max(spikes(:,2))-0.95+max(spikes_foopsi)]); 47 | title('Foopsi Spikes','FontWeight','bold','Fontsize',14); xlabel('Timestep','FontWeight','bold','Fontsize',16); 48 | legend('Foopsi Spikes','Ground Truth'); 49 | drawnow; 50 | 51 | %% run continuous time MCMC sampler 52 | 53 | params.p = 2; 54 | params.g = g2; 55 | params.sp = spikes_foopsi; % pass results of foopsi for initialization (if not, they are computed) 56 | params.c = ca_foopsi; 57 | params.b = cb; 58 | params.c1 = c1; 59 | params.sn = sg; 60 | params.marg = 0; 61 | 62 | SAMPLES = cont_ca_sampler(y,params); %% MCMC 63 | plot_continuous_samples(SAMPLES,y(:)); 64 | M = plot_marginals(SAMPLES.ss,T,spikeRaster); -------------------------------------------------------------------------------- /traceData.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epnev/continuous_time_ca_sampler/354500836a5199ad96c357637f41d46f9a6d4b9f/traceData.mat -------------------------------------------------------------------------------- /utilities/HMC_exact2.m: -------------------------------------------------------------------------------- 1 | function [Xs, bounce_count] = HMC_exact2(F, g, M, mu_r, cov, L, initial_X) 2 | 3 | % Author: Ari Pakman 4 | 5 | % returns samples from a d-dimensional Gaussian with m constraints given by F*X+g >0 6 | % If cov == true 7 | % then M is the covariance and the mean is mu = mu_r 8 | % if cov== false 9 | % then M is the precision matrix and the log-density is -1/2 X'*M*X + r'*X 10 | 11 | % Input 12 | % F: m x d matrix 13 | % g: m x 1 vector 14 | % M d x d matrix, must be symmmetric and definite positive 15 | % mu_r d x 1 vector. 16 | % cov: see explanation above 17 | % L: number of samples desired 18 | % initial_X d x 1 vector. Must satisfy the constraint. 19 | 20 | 21 | % Output 22 | % Xs: d x L matrix, each column is a sample 23 | % bounce_count: number of times the particle bounced 24 | 25 | %% go to a whitened frame 26 | 27 | m = size(g,1); 28 | if size(F,1) ~= m 29 | display('error'); 30 | return 31 | end 32 | 33 | 34 | if cov 35 | mu= mu_r; 36 | g = g + F*mu; 37 | %if min(eig(M))<0 38 | % M = M - 1.01*min(eig(M))*eye(size(M,1)); 39 | %end 40 | R = chol(M); 41 | F = F*R'; 42 | initial_X= initial_X -mu; 43 | initial_X = R'\initial_X; 44 | 45 | else 46 | r=mu_r; 47 | R=chol(M); % M = R'*R 48 | mu = R\(R'\r); 49 | g = g+F*mu; 50 | F = F/R; % this is the time-consuming step in this code section 51 | initial_X= initial_X -mu; 52 | initial_X = R*initial_X; 53 | end 54 | 55 | 56 | 57 | 58 | 59 | d = size(initial_X,1); 60 | 61 | 62 | Xs=NaN; 63 | 64 | bounce_count =0; 65 | 66 | nearzero= 10000*eps; 67 | 68 | 69 | 70 | 71 | % Verify that initial_X is feasible 72 | c= F*initial_X +g; 73 | if any(c<0) 74 | display('error: inconsistent initial condition'); 75 | return 76 | end 77 | 78 | 79 | 80 | % Unsparcify the linear constraints 81 | g=full(g); 82 | F2 = sum(F.*F,2); % squared norm of the rows of F, needed for reflecting the velocity 83 | F=full(F); % if we don't unsparcify qj= F(j,:)*V/F2(j) becomes very slow. 84 | Ft = F'; 85 | 86 | 87 | %% Sampling loop 88 | 89 | 90 | last_X= initial_X; 91 | Xs=zeros(d,L); 92 | Xs(:,1)=initial_X; 93 | 94 | i=2; 95 | while (i <= L) 96 | %i 97 | stop=0; 98 | j=0; 99 | V0= normrnd(0,1, d,1); % initial velocity 100 | X = last_X; 101 | 102 | T=pi/2; % total time the particle will move 103 | tt=0; % records how much time the particle already moved 104 | 105 | while (1) 106 | a = V0; 107 | a= real(a); 108 | b = X; 109 | 110 | fa = F*a; 111 | fb = F*b; 112 | 113 | U = sqrt(fa.^2 + fb.^2); 114 | phi = atan2(-fa,fb); % -pi < phi < +pi 115 | 116 | 117 | 118 | pn = abs(g./U)<=1; % these are the walls that may be hit 119 | 120 | 121 | % find the first time constraint becomes zero 122 | 123 | if any(pn) 124 | inds = find(pn); 125 | 126 | phn= phi(pn); 127 | 128 | t1=-phn + acos(-g(pn)./U(pn)); % time at which coordinates hit the walls 129 | % t1 in [-pi, 2*pi] 130 | t1(t1<0) = 2*pi + t1(t1<0); % t1 in [0, 2*pi] 131 | t2 = -t1 -2*phn; % second solution to hit the walls 132 | % t2 in [-4*pi, 2*pi] 133 | t2(t2<0) = 2*pi + t2(t2<0); % t2 in [-2*pi, 2*pi] 134 | t2(t2<0) = 2*pi + t2(t2<0); % t2 in [0, 2*pi] 135 | 136 | 137 | % if there was a previous reflection (j>0) 138 | % and there is a potential reflection at the sample plane 139 | % make sure that a new reflection at j is not found because of numerical error 140 | if j>0 141 | if pn(j)==1 142 | indj=sum(pn(1:j)); 143 | tt1 = t1(indj); 144 | if abs(tt1) < nearzero || abs(tt1-2*pi)< nearzero 145 | t1(indj)=Inf; 146 | else 147 | tt2 = t2(indj); 148 | if abs(tt2) < nearzero || abs(tt1-2*pi)< nearzero 149 | t2(indj) = Inf; 150 | end 151 | end 152 | end 153 | end 154 | 155 | 156 | [mt1 ind1] = min(t1); 157 | [mt2 ind2] = min(t2); 158 | 159 | [mt ind12] = min([mt1 mt2]); 160 | 161 | if ind12==1 162 | m_ind = ind1; 163 | else 164 | m_ind= ind2; 165 | end 166 | 167 | % find the reflection plane 168 | j = inds(m_ind); % j is an index in the full vector of dim-m, not in the restriced vector determined by pn. 169 | 170 | else %if pn(i) =0 for all i 171 | mt =T; 172 | end %if pn(i) 173 | 174 | tt=tt+mt; 175 | % fprintf(num2str(tt/T)); 176 | 177 | if tt>=T 178 | mt= mt-(tt-T); 179 | stop=1; 180 | 181 | end 182 | 183 | % move the particle a time mt 184 | 185 | X = a*sin(mt) + b*cos(mt); 186 | V = a*cos(mt) - b*sin(mt); 187 | 188 | if stop 189 | break; 190 | end 191 | 192 | % compute reflected velocity 193 | 194 | qj= F(j,:)*V/F2(j); 195 | V0 = V -2*qj*Ft(:,j); 196 | bounce_count = bounce_count+ 1; 197 | 198 | % dif =V0'*M*V0 - V'*M*V; 199 | 200 | end % while(1) 201 | 202 | % at this point we have a sampled value X, but due to possible 203 | % numerical instabilities we check that the candidate X satisfies the 204 | % constraints before accepting it. 205 | 206 | if all(F*X +g > 0) 207 | Xs(:,i)=X; 208 | last_X = X; 209 | i= i+1; 210 | 211 | else 212 | disp('hmc reject') 213 | 214 | end 215 | 216 | end %while (i <= L) 217 | 218 | % transform back to the unwhitened frame 219 | if cov 220 | Xs = R'*Xs + repmat(mu, 1,L); 221 | else 222 | Xs = R\Xs + repmat(mu, 1,L); 223 | end 224 | 225 | 226 | 227 | 228 | end 229 | 230 | -------------------------------------------------------------------------------- /utilities/addSpike.m: -------------------------------------------------------------------------------- 1 | function [newSpikeTrain, newCalcium, newLL] = addSpike(oldSpikeTrain,oldCalcium,oldLL,filters,tau,obsCalcium,timeToAdd,indx,Dt,A) 2 | 3 | % Add a given spike to the existing spike train. 4 | 5 | % Inputs: 6 | % oldSpikeTrain: current spike train 7 | % oldCalcium: current noiseless calcium trace 8 | % oldLL: current value of the log-likelihood function 9 | % filters: exponential rise and decay kernels for calcium transient 10 | % tau: continuous time rise and decay time constants 11 | % obsCalcium: observed fluorescence trace 12 | % timetoAdd: time of the spike to be added 13 | % indx: place where the new spike is added in the existing spike train vector 14 | % Dt: time-bin width 15 | % A: spike amplitude 16 | 17 | % Outputs: 18 | % newSpikeTrain: new vector of spike times 19 | % newCalcium: new noiseless calcium trace 20 | % newLL: new value of the log-likelihood function 21 | 22 | % Author: Eftychios A. Pnevmatikakis and Josh Merel 23 | 24 | tau_h = tau(1); 25 | tau_d = tau(2); 26 | 27 | ef_h = filters{1,1}; 28 | ef_d = filters{1,2}; 29 | ef_nh = filters{2,1}; 30 | ef_nd = filters{2,2}; 31 | 32 | if isempty(oldSpikeTrain); indx = 1; end 33 | newSpikeTrain = [oldSpikeTrain(1:indx-1) timeToAdd oldSpikeTrain(indx:end)]; %possibly inefficient, change if problematic (only likely to be a problem for large numbers of spikes) 34 | 35 | %use infinite precision to scale the precomputed FIR approximation to the calcium transient 36 | wk_h = A*exp((timeToAdd - Dt*ceil(timeToAdd/Dt))/tau_h); 37 | wk_d = A*exp((timeToAdd - Dt*ceil(timeToAdd/Dt))/tau_d); 38 | 39 | %%%%%%%%%%%%%%%%% 40 | %handle ef_h first 41 | newCalcium = oldCalcium; 42 | tmp = 1 + (floor(timeToAdd):min((length(ef_h)+floor(timeToAdd)-1),length(newCalcium)-1)); 43 | wef_h = wk_h*ef_h(1:length(tmp)); 44 | newCalcium(tmp) = newCalcium(tmp) + wef_h; 45 | 46 | %if you really want to, ef*ef' could be precomputed and passed in 47 | relevantResidual = obsCalcium(tmp)-oldCalcium(tmp); 48 | relevantResidual(isnan(relevantResidual)) = 0; 49 | %newLL = oldLL - ( wk_h^2*norm(ef_h(1:length(tmp)))^2 - 2*relevantResidual*(wk_h*ef_h(1:length(tmp))')); 50 | newLL = oldLL - ( wk_h^2*ef_nh(length(tmp)) - 2*relevantResidual*wef_h(:)); 51 | oldCalcium = newCalcium; 52 | oldLL = newLL; 53 | %%%%%%%%%%%%%%%%% 54 | 55 | %%%%%%%%%%%%%%%%% 56 | %handle ef_d next 57 | tmp = 1 + (floor(timeToAdd):min((length(ef_d)+floor(timeToAdd)-1),length(newCalcium)-1)); 58 | wef_d = wk_d*ef_d(1:length(tmp)); 59 | newCalcium(tmp) = newCalcium(tmp) + wef_d; 60 | 61 | relevantResidual = obsCalcium(tmp)-oldCalcium(tmp); 62 | relevantResidual(isnan(relevantResidual)) = 0; 63 | %newLL = oldLL - ( wk_d^2*norm(ef_d(1:length(tmp)))^2 - 2*relevantResidual*(wk_d*ef_d(1:length(tmp))')); 64 | newLL = oldLL - ( wk_d^2*ef_nd(length(tmp)) - 2*relevantResidual*wef_d(:)); 65 | %%%%%%%%%%%%%%%%% -------------------------------------------------------------------------------- /utilities/get_initial_sample.m: -------------------------------------------------------------------------------- 1 | function SAM = get_initial_sample(Y,params) 2 | 3 | % obtain initial sample by performing sparse noise-constrained deconvolution 4 | 5 | % Inputs: 6 | % Y: observed fluorescence trace 7 | % params: parameter struct (results of constrained foopsi can be passed here to avoid unnecessary computation) 8 | 9 | % Output: 10 | % SAM: sample structure with values for spikes in continuous time, 11 | % amplitude, baseline, noise, initial concentration and time constants 12 | 13 | % Author: Eftychios A. Pnevmatikakis, 2016, Simons Foundation 14 | 15 | 16 | if isfield(params,'p'); options.p = params.p; else options.p = 1; end 17 | if isempty(params.c) || isempty(params.b) || isempty(params.c1) || isempty(params.g) || isempty(params.sn) || isempty(params.sp) 18 | fprintf('Initializing using noise constrained FOOPSI... '); 19 | options.bas_nonneg = params.bas_nonneg; 20 | [c,b,c1,g,sn,sp] = constrained_foopsi(Y,params.b,params.c1,params.g,params.sn,options); 21 | fprintf('done. \n'); 22 | else 23 | c = params.c; 24 | b = params.b; 25 | c1 = params.c1; 26 | g = params.g; 27 | sn = params.sn; 28 | sp = params.sp; 29 | end 30 | 31 | Dt = 1; 32 | T = length(Y); 33 | if ~exist('sp','var') 34 | G = make_G_matrix(T,params.g); 35 | sp = G*c; 36 | end 37 | s_in = sp>0.15*max(sp); 38 | spiketimes_ = Dt*(find(s_in) + rand(size(find(s_in))) - 0.5); 39 | spiketimes_(spiketimes_ >= T*Dt) = 2*T*Dt - spiketimes_(spiketimes_ >= T*Dt); 40 | SAM.lam_ = length(spiketimes_)/(T*Dt); 41 | SAM.spiketimes_ = spiketimes_; 42 | 43 | SAM.A_ = max(median(sp(s_in)),max(sp(s_in))/4); % initial amplitude value 44 | if length(g) == 2 45 | SAM.A_ = SAM.A_/sqrt(g(1)^2+4*g(2)); 46 | end 47 | 48 | SAM.b_ = max(b,min(Y)+range(Y)/25); % initial baseline value 49 | SAM.C_in = max(c1,(Y(1)-b)/10); % initial value sample 50 | SAM.sg = sn; % initial noise value 51 | SAM.g = g; % initial time constant value -------------------------------------------------------------------------------- /utilities/get_next_spikes.m: -------------------------------------------------------------------------------- 1 | function [new_spikes, new_calcium, moves] = get_next_spikes(curr_spikes,curr_calcium,calciumSignal,filters,tau,calciumNoiseVar, lam, proposalSTD, add_move, Dt, A, con_lam) 2 | 3 | % Function for sampling the next of spike times given the current set of spike time and observed fluorescence trace 4 | 5 | % Inputs: 6 | % curr_spikes: current set of spike times in continuous time 7 | % curr_calcium: current estimate of noiseless calcium trace 8 | % calciumSingal: observed fluorescence trace 9 | % filters: exponential rise and decay kernels for calcium transient 10 | % tau: continuous time rise and decay time constants 11 | % calciumNoiseVar: observation noise variance 12 | % lam: function handle for computing firing rate 13 | % proposalSTD: standard deviation for perturbing a spike time 14 | % add_move: # of addition/removal proposals per sample 15 | % Dt: time-bin width 16 | % A: spike amplitude 17 | % con_lam: flag for constant firing rate (speeds up computation) 18 | 19 | % Outputs: 20 | % new_spikes: new set of spike times 21 | % new_calcium: new estimate of noiseless calcium trace 22 | % moves: acceptance probabilities for perturbing,adding, droping spikes respectively 23 | 24 | % Author: Eftychios A. Pnevmatikakis and Josh Merel 25 | 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | %% initialize some parameters 28 | T = length(calciumSignal); %for all of this, units are bins and spiketrains go from 0 to T where T is number of bins 29 | ff = ~isnan(calciumSignal); % entries with data 30 | 31 | %% start with initial spiketrain and initial predicted calcium 32 | si = curr_spikes; %initial set of spike times has no spikes - this will not be sorted but that shouldn't be a problem 33 | new_calcium = curr_calcium; %initial calcium is set to baseline 34 | 35 | N = length(si); %number of spikes in spiketrain 36 | 37 | %initial logC - compute likelihood initially completely - updates to likelihood will be local 38 | logC = -norm(new_calcium(ff)-calciumSignal(ff))^2; 39 | 40 | %flag for uniform vs likelihood proposal (if using likelihood proposal, then time shifts are pure Gibbs) 41 | %this really should be split into four cases (not implemented yet), 42 | % 1) RW for time shifts with uniform add/drop 43 | % 2) RW for time shifts with likelihood proposal add/drop 44 | % 3) Gibbs for time shifts with uniform add/drop 45 | % 4) Gibbs for time shifts with likelihood proposal add/drop 46 | 47 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 48 | addMoves = [0 0]; %first elem is number successful, second is number total 49 | dropMoves = [0 0]; 50 | timeMoves = [0 0]; 51 | 52 | %% loop over spikes, perform spike time move (could parallelize here for non-interacting spikes, i.e. spikes that are far enough away) 53 | 54 | for ni = 1:N %possibly go through in a random order (if you like) 55 | 56 | tmpi = si(ni); 57 | tmpi_ = si(ni)+(proposalSTD*randn); %with bouncing off edges 58 | if tmpi_<0 59 | tmpi_ = -(tmpi_); 60 | elseif tmpi_>T 61 | tmpi_ = T-(tmpi_-T); 62 | end 63 | 64 | %set si_ to set of spikes with the move and ci_ to adjusted calcium and update logC_ to adjusted 65 | % [si_, ci_, logC_] = removeSpike(si,ci,logC,ef,tau,calciumSignal,tmpi,ni,Dt,A); 66 | % [si_, ci_, logC_] = addSpike(si_,ci_,logC_,ef,tau,calciumSignal,tmpi_,ni,Dt,A); 67 | [si_, ci_, logC_] = replaceSpike(si,new_calcium,logC,filters,tau,calciumSignal,tmpi,ni,tmpi_,Dt,A); 68 | 69 | %accept or reject 70 | ratio = exp((logC_-logC)/(2*calciumNoiseVar)); 71 | if ~con_lam; ratio = ratio*lam(tmpi)/lam(tmpi_); end 72 | if ratio>1 %accept 73 | si = si_; 74 | new_calcium = ci_; 75 | logC = logC_; 76 | timeMoves = timeMoves + [1 1]; 77 | elseif rand1 %accept 110 | si = si_; 111 | new_calcium = ci_; 112 | logC = logC_; 113 | addMoves = addMoves + [1 1]; 114 | elseif rand0 127 | %propose a uniform removal 128 | tmpi = randi(N); 129 | [si_, ci_, logC_] = removeSpike(si,new_calcium,logC,filters,tau,calciumSignal,si(tmpi),tmpi,Dt,A); 130 | 131 | %reverse probability 132 | rprob = 1/(T*Dt); 133 | 134 | %compute forward prob 135 | fprob = 1/N; 136 | 137 | %accept or reject 138 | ratio = exp((logC_ - logC)/(2*calciumNoiseVar))*(rprob/fprob)*(1/lam(si(tmpi))); %posterior times reverse prob/forward prob 139 | 140 | if ratio>1 %accept 141 | si = si_; 142 | new_calcium = ci_; 143 | logC = logC_; 144 | dropMoves = dropMoves + [1 1]; 145 | elseif rand floor(timetoAdd) 52 | wef_h = wk_hr*[zeros(1,min(length(tmp),floor(timetoRemove)-floor(timetoAdd))),ef_h(1:length(tmp)-(floor(timetoRemove)-floor(timetoAdd)))] - wk_ha*ef_h(1:length(tmp)); 53 | else 54 | wef_h = wk_hr*ef_h(1:length(tmp)) - wk_ha*[zeros(1,min(length(tmp),floor(timetoAdd)-floor(timetoRemove))),ef_h(1:length(tmp)-(floor(timetoAdd)-floor(timetoRemove)))]; 55 | end 56 | newCalcium(tmp) = newCalcium(tmp) - wef_h; 57 | end 58 | 59 | %%%%%%%%%%%%%%%%% 60 | %handle ef_d next 61 | tmp = 1+ (min_t:min((length(ef_d)+min_t-1),length(newCalcium)-1)); 62 | if floor(timetoRemove) == floor(timetoAdd) 63 | wef_d = (wk_dr-wk_da)*ef_d(1:length(tmp)); 64 | elseif floor(timetoRemove) > floor(timetoAdd) 65 | wef_d = wk_dr*[zeros(1,min(length(tmp),floor(timetoRemove)-floor(timetoAdd))),ef_d(1:length(tmp)-(floor(timetoRemove)-floor(timetoAdd)))] - wk_da*ef_d(1:length(tmp)); 66 | else 67 | wef_d = wk_dr*ef_d(1:length(tmp)) - wk_da*[zeros(1,min(length(tmp),floor(timetoAdd)-floor(timetoRemove))),ef_d(1:length(tmp)-(floor(timetoAdd)-floor(timetoRemove)))]; 68 | end 69 | newCalcium(tmp) = newCalcium(tmp) - wef_d; 70 | 71 | obstemp = obsCalcium(tmp); 72 | newLL = oldLL - norm(newCalcium(tmp)-obstemp)^2 + norm(oldCalcium(tmp)-obstemp)^2; %+ 2*(newCalcium(tmp)-oldCalcium 73 | %%%%%%%%%%%%%%%% -------------------------------------------------------------------------------- /utilities/samples_cell2mat.m: -------------------------------------------------------------------------------- 1 | function spikeRaster = samples_cell2mat(sampleCell,T,Dt) 2 | 3 | % Constructs matrix of spike raster plot from cell array of spike times 4 | 5 | % Inputs: 6 | % sampleCell: Cell array with spike times in continuous time (SAMPLES.ss) 7 | % T: End time of trace/experiment 8 | % Dt: Time-bin resolution (default: 1) 9 | 10 | % Output: 11 | % spikeRaster: Spike raster plot matrix 12 | 13 | % Author: Eftychios A. Pnevmatikakis and Josh Merel 14 | 15 | if nargin == 2 16 | Dt = 1; 17 | end 18 | bins = 0:Dt:(T-Dt); 19 | nsamples = length(sampleCell); 20 | spikeRaster = zeros(nsamples,length(bins)); 21 | for i = 1:nsamples 22 | tmp = histc([sampleCell{i}(:); inf],[bins, (T+1)]); 23 | spikeRaster(i,:) = tmp(1:(end-1))'; 24 | end -------------------------------------------------------------------------------- /utilities/tau_c2d.m: -------------------------------------------------------------------------------- 1 | function [g,h1] = tau_c2d(tau_r,tau_d,dt) 2 | 3 | % convert continuous time constants to discrete with resolution dt 4 | % h(t) = (1-exp(-t/tau_r))*exp(-t/tau_d) 5 | % g: discrete time constants 6 | % h1: h(dt); 7 | % h*s can be written in discrete form as filter(h1,[1,-g],s) 8 | 9 | % Author: Eftychios A. Pnevmatikakis, 2016, Simons Foundation 10 | 11 | A = [-(2/tau_d+1/tau_r), - (tau_r+tau_d)/(tau_r*tau_d^2); 1 0]; 12 | lc = eig(A*dt); 13 | ld = exp(lc); 14 | g = [sum(ld),-prod(ld)]; 15 | h1 = (1-exp(-dt/tau_r))*exp(-dt/tau_d); -------------------------------------------------------------------------------- /utilities/tau_d2c.m: -------------------------------------------------------------------------------- 1 | function tau = tau_d2c(g,dt) 2 | 3 | % convert discrete time constantswith resolution dt to continuous 4 | % h(t) = (1-exp(-t/tau(1)))*exp(-t/tau(2)) 5 | 6 | % Author: Eftychios A. Pnevmatikakis, 2016, Simons Foundation 7 | 8 | gr = max(roots([1,-g(:)']),0); 9 | p1_continuous = log(min(gr))/dt; 10 | p2_continuous = log(max(gr))/dt; 11 | tau_1 = -1/p1_continuous; %tau h - smaller (tau_d * tau_r)/(tau_d + tau_r) 12 | tau_2 = -1/p2_continuous; %tau decay - larger 13 | 14 | tau_rise = 1/(1/tau_1 - 1/tau_2); 15 | tau = [tau_rise,tau_2]; %tau_h , tau_d -------------------------------------------------------------------------------- /wrapper.m: -------------------------------------------------------------------------------- 1 | clear; 2 | load traceData.mat; 3 | addpath utilities 4 | addpath(genpath('../constrained-foopsi')); 5 | %Y = squeeze(traceData.traces(129,7,:)); % pick a particular trace (low SNR) 6 | Y = mean(squeeze(traceData.traces(:,7,:))); % average over ROI (high SNR) 7 | 8 | %% run MCMC sampler and plot results 9 | params.p = 1; 10 | params.print_flag = 1; 11 | params.B = 300; 12 | SAMP = cont_ca_sampler(Y,params); 13 | plot_continuous_samples(SAMP,Y); --------------------------------------------------------------------------------