├── .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 | [](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);
--------------------------------------------------------------------------------