def show_fit_results(self, results, average, qc_pass): self.response_plot.plot(average.time_values, average.data, pen='b') psp = StackedPsp() t = average.time_values v = psp.eval(x=t, **results.best_values) pen = {'color': (0, 150, 0) if qc_pass else (255, 100, 0), 'width': 2} self.response_plot.plot(t, v, pen=pen)
def show_expected_fit(self, fit_params, qc_pass): psp = StackedPsp() t = np.linspace(-10e-3, 20e-3, 1000) v = psp.eval(x=t, **fit_params) pen = { 'color': (0, 150, 0) if qc_pass else (255, 100, 0), 'dash': [5, 5] } self.response_plot.plot(t, v, pen=pen, zValue=10)
def plot_all(self): self.plots.clear() self.plots.set_shape(len(self.sorted_recs), 1) psp = StackedPsp() stim_keys = sorted(list(self.sorted_recs.keys())) for i, stim_key in enumerate(stim_keys): prs = self.sorted_recs[stim_key] plt = self.plots[i, 0] plt.setTitle("%s %0.0f Hz %0.2f s" % stim_key) for recording in prs: pulses = sorted(list(prs[recording].keys())) for pulse_n in pulses: rec = prs[recording][pulse_n] # spike-align pulse + offset for pulse number spike_t = rec.stim_pulse.first_spike_time if spike_t is None: spike_t = rec.stim_pulse.onset_time + 1e-3 qc_pass = rec.pulse_response.in_qc_pass if rec.synapse.synapse_type == 'in' else rec.pulse_response.ex_qc_pass pen = (255, 255, 255, 100) if qc_pass else (100, 0, 0, 100) t0 = rec.pulse_response.data_start_time - spike_t ts = TSeries(data=rec.data, t0=t0, sample_rate=db.default_sample_rate) c = plt.plot(ts.time_values, ts.data, pen=pen) # arrange plots nicely shift = (pulse_n * 35e-3 + (30e-3 if pulse_n > 8 else 0), 0) c.setPos(*shift) if not qc_pass: c.setZValue(-10) continue # evaluate recorded fit for this response fit_par = rec.pulse_response_fit if fit_par.fit_amp is None: continue fit = psp.eval( x=ts.time_values, exp_amp=fit_par.fit_exp_amp, exp_tau=fit_par.fit_decay_tau, amp=fit_par.fit_amp, rise_time=fit_par.fit_rise_time, decay_tau=fit_par.fit_decay_tau, xoffset=fit_par.fit_latency, yoffset=fit_par.fit_yoffset, rise_power=2, ) c = plt.plot(ts.time_values, fit, pen=(0, 255, 0, 100)) c.setZValue(10) c.setPos(*shift)
def fit_psp(response, mode='ic', sign='any', xoffset=11e-3, yoffset=(0, 'fixed'), mask_stim_artifact=True, method='leastsq', fit_kws=None, **kwds): t = response.time_values y = response.data if mode == 'ic': amp = .2e-3 amp_max = 100e-3 rise_time = 5e-3 decay_tau = 50e-3 elif mode == 'vc': amp = 20e-12 amp_max = 500e-12 rise_time = 1e-3 decay_tau = 4e-3 else: raise ValueError('mode must be "ic" or "vc"') amps = [(amp, 0, amp_max), (-amp, -amp_max, 0)] if sign == '-': amps = amps[1:] elif sign == '+': amps = amps[:1] elif sign != 'any': raise ValueError('sign must be "+", "-", or "any"') psp = StackedPsp() base_params = { 'xoffset': (xoffset, 10e-3, 15e-3), 'yoffset': yoffset, 'rise_time': (rise_time, rise_time/2., rise_time*2.), 'decay_tau': (decay_tau, decay_tau/10., decay_tau*10.), 'exp_amp': 'amp * amp_ratio', 'amp_ratio': (1, 0, 10), 'rise_power': (2, 'fixed'), } if 'rise_time' in kwds: rt = kwds.pop('rise_time') if not isinstance(rt, tuple): rt = (rt, rt/2., rt*2.) base_params['rise_time'] = rt if 'decay_tau' in kwds: dt = kwds.pop('decay_tau') if not isinstance(dt, tuple): dt = (dt, dt/2., dt*2.) base_params['decay_tau'] = dt base_params.update(kwds) params = [] for amp, amp_min, amp_max in amps: p2 = base_params.copy() p2['amp'] = (amp, amp_min, amp_max) params.append(p2) dt = response.dt weight = np.ones(len(y)) #weight[:int(10e-3/dt)] = 0.5 if mask_stim_artifact: # Use zero weight for fit region around the stimulus artifact weight[int(10e-3/dt):int(12e-3/dt)] = 0 weight[int(12e-3/dt):int(19e-3/dt)] = 30 if fit_kws is None: fit_kws = {'xtol': 1e-4, 'maxfev': 300, 'nan_policy': 'omit'} if 'weight' not in fit_kws: fit_kws['weights'] = weight best_fit = None best_score = None for p in params: fit = psp.fit(y, x=t, params=p, fit_kws=fit_kws, method=method) err = np.sum(fit.residual**2) if best_fit is None or err < best_score: best_fit = fit best_score = err fit = best_fit # nrmse = fit.nrmse() if 'baseline_std' in response.meta: fit.snr = abs(fit.best_values['amp']) / response.meta['baseline_std'] fit.err = fit.rmse() / response.meta['baseline_std'] # print fit.best_values # print "RMSE:", fit.rmse() # print "NRMSE:", nrmse # print "SNR:", snr return fit
def load_saved_fit(self, record): data = record.notes pair_params = {'Synapse call': data['synapse_type'], 'Gap junction call': data['gap_junction']} self.ctrl_panel.update_user_params(**pair_params) self.warnings = data.get('fit_warnings', []) self.ctrl_panel.output_params.child('Warnings').setValue('\n'.join(self.warnings)) self.ctrl_panel.output_params.child('Comments', '').setValue(data.get('comments', '')) # some records may be stored with no fit if a synapse is not present. if 'fit_parameters' not in data: return self.fit_params = data['fit_parameters'] self.ctrl_panel.update_fit_params(data['fit_parameters']['fit']) self.output_fit_parameters = data['fit_parameters']['fit'] self.initial_fit_parameters = data['fit_parameters']['initial'] initial_vc_latency = ( data['fit_parameters']['initial']['vc']['-55'].get('xoffset') or data['fit_parameters']['initial']['vc']['-70'].get('xoffset') or 1e-3 ) initial_ic_latency = ( data['fit_parameters']['initial']['ic']['-55'].get('xoffset') or data['fit_parameters']['initial']['ic']['-70'].get('xoffset') or 1e-3 ) latency_diff = np.diff([initial_vc_latency, initial_ic_latency])[0] if abs(latency_diff) < 100e-6: self.latency_superline.set_value(initial_vc_latency, block_fit=True) else: fit_pass_vc = [data['fit_pass']['vc'][str(h)] for h in holdings] fit_pass_ic = [data['fit_pass']['ic'][str(h)] for h in holdings] if any(fit_pass_vc): self.latency_superline.set_value(initial_vc_latency, block_fit=True) elif any(fit_pass_ic): self.latency_superline.set_value(initial_ic_latency, block_fit=True) else: self.latency_superline.set_value(initial_vc_latency, block_fit=True) for mode in modes: for holding in holdings: fit_pass = data['fit_pass'][mode][str(holding)] self.ctrl_panel.output_params.child('Fit parameters', str(holding) + ' ' + mode.upper(), 'Fit Pass').setValue(fit_pass) fit_params = copy.deepcopy(data['fit_parameters']['fit'][mode][str(holding)]) if fit_params: fit_params.pop('nrmse', None) if mode == 'ic': p = StackedPsp() if mode == 'vc': p = Psp() # make a list of spike-aligned postsynaptic tseries tsl = PulseResponseList(self.sorted_responses[mode, holding]['qc_pass']).post_tseries(align='spike', bsub=True) if len(tsl) == 0: continue # average all together avg = tsl.mean() fit_params.setdefault('exp_tau', fit_params['decay_tau']) fit_psp = p.eval(x=avg.time_values, **fit_params) fit_tseries = avg.copy(data=fit_psp) if mode == 'vc': self.vc_plot.plot_fit(fit_tseries, holding, fit_pass=fit_pass) if mode == 'ic': self.ic_plot.plot_fit(fit_tseries, holding, fit_pass=fit_pass)
def fit_psp(response, mode='ic', sign='any', #Note this will not be used if *amp* input is specified method='leastsq', fit_kws=None, stacked=True, rise_time_mult_factor=10., #Note this will not be used if *rise_time* input is specified weight='default', amp_ratio='default', # the following are parameters that can be fit amp='default', decay_tau='default', rise_power='default', rise_time='default', exp_amp='default', xoffset='default', yoffset='default', ): """Fit psp. function to the equation This function make assumptions about where the cross talk happens as traces have been aligned to the pulses and response.t0 has been set to zero Parameters ---------- response : neuroanalysis.data.Trace class Contains data on trace waveform. mode : string either 'ic' for current clamp or 'vc' for voltage clamp sign : string Specifies the sign of the PSP deflection. Must be '+', '-', or any. If *amp* is specified, value will be irrelevant. method : string Method lmfit uses for optimization rise_time_mult_factor: float Parameter that goes into the default calculation rise time. Note that if an input for *rise_time* is provided this input will be irrelevant. stacked : True or False If true, use the StackedPsp function which assumes there is still left over voltage decay from previous events. If False, use Psp function which assumes the region of the waveform before the event is at baseline. fit_kws : dictionary Additional key words that are fed to lmfit exp_amp : string function that is fed to lmfit The parameters below are fed to the psp function. Each value in the key:value dictionary pair must be a tuple. In general the structure of the tuple is of the form, (initial conditions, lower boundary, higher boundary). The initial conditions can be either a number or a list of numbers specifying several initial conditions. The initial condition may also be fixed by replacing the lower higher boundary combination with 'fixed'. Examples: amplitude=(10, 0, 20) amplitude=(10, 'fixed') amplitude=([5,10, 20], 0, 20) amplitude=([5,10, 20], 'fixed') xoffset : scalar Horizontal shift between begin (positive shifts to the right) yoffset : scalar Vertical offset rise_time : scalar Time from beginning of psp until peak decay_tau : scalar Decay time constant amp : scalar The peak value of the psp rise_power : scalar Exponent for the rising phase; larger values result in a slower activation amp_ratio : scalar if *stacked* this is used to set up the ratio between the residual decay amplitude and the height of the PSP. Returns ------- fit: lmfit.model.ModelResult Best fit """ # extracting these for ease of use t=response.time_values y=response.data dt = response.dt # set initial conditions depending on whether in voltage or current clamp # note that sign of these will automatically be set later on based on the # the *sign* input if mode == 'ic': amp_init = .2e-3 amp_max = 100e-3 rise_time_init = 5e-3 decay_tau_init = 50e-3 elif mode == 'vc': amp_init = 20e-12 amp_max = 500e-12 rise_time_init = 1e-3 decay_tau_init = 4e-3 else: raise ValueError('mode must be "ic" or "vc"') # Set up amplitude initial values and boundaries depending on whether *sign* are positive or negative if sign == '-': amps = (-amp_init, -amp_max, 0) elif sign == '+': amps = (amp_init, 0, amp_max) elif sign =='any': warnings.warn("You are not specifying the predicted sign of your psp. This may slow down or mess up fitting") amps = (0, -amp_max, amp_max) else: raise ValueError('sign must be "+", "-", or "any"') # initial condition, lower boundry, upper boundry base_params = { 'xoffset': (14e-3, -float('inf'), float('inf')), 'yoffset': (0, -float('inf'), float('inf')), 'rise_time': (rise_time_init, rise_time_init/rise_time_mult_factor, rise_time_init*rise_time_mult_factor), 'decay_tau': (decay_tau_init, decay_tau_init/10., decay_tau_init*10.), 'rise_power': (2, 'fixed'), 'amp': amps } # specify fitting function and set up conditions if not isinstance(stacked, bool): raise Exception("Stacked must be True or False") if stacked: psp = StackedPsp() base_params.update({ #TODO: figure out the bounds on these 'exp_amp': 'amp * amp_ratio', 'amp_ratio': (0, -100, 100), }) else: psp = Psp() # override defaults with input for bp in base_params.keys(): if eval(bp)!='default': base_params[bp]=eval(bp) # set weighting that if weight == 'default': #use default weighting # THIS CODE IS DEPENDENT ON THE DATA BEING INPUT IN A CERTAIN WAY THAT IS NOT TESTED weight = np.ones(len(y))*10. #set everything to ten initially weight[int(10e-3/dt):int(12e-3/dt)] = 0. #area around stim artifact weight[int(12e-3/dt):int(19e-3/dt)] = 30. #area around steep PSP rise elif weight is False: #do not weight any part of the stimulus weight = np.ones(len(y)) elif 'weight' in vars(): #works if there is a value specified in weight if len(weight) != len(y): raise Exception('the weight and array vectors are not the same length') # arguement to be passed through to fitting function fit_kws={'weights': weight} # convert initial parameters into a list of dictionaries to be consumed by psp.fit() param_dict_list= create_all_fit_param_combos(base_params) # cycle though different parameters sets and chose best one best_fit = None best_score = None for p in param_dict_list: fit = psp.fit(y, x=t, params=p, fit_kws=fit_kws, method=method) err = np.sum(fit.residual**2) # note: using this because normalized (nrmse) is not necessary to comparing fits within the same data set if best_fit is None or err < best_score: best_fit = fit best_score = err fit = best_fit # nrmse = fit.nrmse() if 'baseline_std' in response.meta: fit.snr = abs(fit.best_values['amp']) / response.meta['baseline_std'] fit.err = fit.nrmse() / response.meta['baseline_std'] return fit
def fit_psp( response, mode='ic', sign='any', #Note this will not be used if *amp* input is specified method='leastsq', fit_kws=None, stacked=True, rise_time_mult_factor=10., #Note this will not be used if *rise_time* input is specified weight='default', amp_ratio='default', # the following are parameters that can be fit amp='default', decay_tau='default', rise_power='default', rise_time='default', exp_amp='default', xoffset='default', yoffset='default', ): """Fit psp. function to the equation This function make assumptions about where the cross talk happens as traces have been aligned to the pulses and response.t0 has been set to zero Parameters ---------- response : neuroanalysis.data.Trace class Contains data on trace waveform. mode : string either 'ic' for current clamp or 'vc' for voltage clamp sign : string Specifies the sign of the PSP deflection. Must be '+', '-', or any. If *amp* is specified, value will be irrelevant. method : string Method lmfit uses for optimization rise_time_mult_factor: float Parameter that goes into the default calculation rise time. Note that if an input for *rise_time* is provided this input will be irrelevant. stacked : True or False If true, use the StackedPsp function which assumes there is still left over voltage decay from previous events. If False, use Psp function which assumes the region of the waveform before the event is at baseline. fit_kws : dictionary Additional key words that are fed to lmfit exp_amp : string function that is fed to lmfit The parameters below are fed to the psp function. Each value in the key:value dictionary pair must be a tuple. In general the structure of the tuple is of the form, (initial conditions, lower boundary, higher boundary). The initial conditions can be either a number or a list of numbers specifying several initial conditions. The initial condition may also be fixed by replacing the lower higher boundary combination with 'fixed'. Examples: amplitude=(10, 0, 20) amplitude=(10, 'fixed') amplitude=([5,10, 20], 0, 20) amplitude=([5,10, 20], 'fixed') xoffset : scalar Horizontal shift between begin (positive shifts to the right) yoffset : scalar Vertical offset rise_time : scalar Time from beginning of psp until peak decay_tau : scalar Decay time constant amp : scalar The peak value of the psp rise_power : scalar Exponent for the rising phase; larger values result in a slower activation amp_ratio : scalar if *stacked* this is used to set up the ratio between the residual decay amplitude and the height of the PSP. Returns ------- fit: lmfit.model.ModelResult Best fit """ # extracting these for ease of use t = response.time_values y = response.data dt = response.dt # set initial conditions depending on whether in voltage or current clamp # note that sign of these will automatically be set later on based on the # the *sign* input if mode == 'ic': amp_init = .2e-3 amp_max = 100e-3 rise_time_init = 5e-3 decay_tau_init = 50e-3 elif mode == 'vc': amp_init = 20e-12 amp_max = 500e-12 rise_time_init = 1e-3 decay_tau_init = 4e-3 else: raise ValueError('mode must be "ic" or "vc"') # Set up amplitude initial values and boundaries depending on whether *sign* are positive or negative if sign == '-': amps = (-amp_init, -amp_max, 0) elif sign == '+': amps = (amp_init, 0, amp_max) elif sign == 'any': warnings.warn( "You are not specifying the predicted sign of your psp. This may slow down or mess up fitting" ) amps = (0, -amp_max, amp_max) else: raise ValueError('sign must be "+", "-", or "any"') # initial condition, lower boundry, upper boundry base_params = { 'xoffset': (14e-3, -float('inf'), float('inf')), 'yoffset': (0, -float('inf'), float('inf')), 'rise_time': (rise_time_init, rise_time_init / rise_time_mult_factor, rise_time_init * rise_time_mult_factor), 'decay_tau': (decay_tau_init, decay_tau_init / 10., decay_tau_init * 10.), 'rise_power': (2, 'fixed'), 'amp': amps } # specify fitting function and set up conditions if not isinstance(stacked, bool): raise Exception("Stacked must be True or False") if stacked: psp = StackedPsp() base_params.update({ #TODO: figure out the bounds on these 'exp_amp': 'amp * amp_ratio', 'amp_ratio': (0, -100, 100), }) else: psp = Psp() # override defaults with input for bp in base_params.keys(): if eval(bp) != 'default': base_params[bp] = eval(bp) # set weighting that if weight == 'default': #use default weighting # THIS CODE IS DEPENDENT ON THE DATA BEING INPUT IN A CERTAIN WAY THAT IS NOT TESTED weight = np.ones(len(y)) * 10. #set everything to ten initially weight[int(10e-3 / dt):int(12e-3 / dt)] = 0. #area around stim artifact weight[int(12e-3 / dt):int(19e-3 / dt)] = 30. #area around steep PSP rise elif weight is False: #do not weight any part of the stimulus weight = np.ones(len(y)) elif 'weight' in vars(): #works if there is a value specified in weight if len(weight) != len(y): raise Exception( 'the weight and array vectors are not the same length') # arguement to be passed through to fitting function fit_kws = {'weights': weight} # convert initial parameters into a list of dictionaries to be consumed by psp.fit() param_dict_list = create_all_fit_param_combos(base_params) # cycle though different parameters sets and chose best one best_fit = None best_score = None for p in param_dict_list: fit = psp.fit(y, x=t, params=p, fit_kws=fit_kws, method=method) err = np.sum( fit.residual**2 ) # note: using this because normalized (nrmse) is not necessary to comparing fits within the same data set if best_fit is None or err < best_score: best_fit = fit best_score = err fit = best_fit # nrmse = fit.nrmse() if 'baseline_std' in response.meta: fit.snr = abs(fit.best_values['amp']) / response.meta['baseline_std'] fit.err = fit.nrmse() / response.meta['baseline_std'] return fit
def set_data(self, data, show_spikes=False, subtract_baseline=True): self.spike_plot.setVisible(show_spikes) self.spike_plot.enableAutoRange(False, False) self.data_plot.enableAutoRange(False, False) psp = StackedPsp() for recording in data: pulses = sorted(list(data[recording].keys())) for pulse_n in pulses: rec = data[recording][pulse_n] # spike-align pulse + offset for pulse number spike_t = rec.StimPulse.first_spike_time if spike_t is None: spike_t = rec.StimPulse.onset_time + 1e-3 qc_pass = rec.PulseResponse.in_qc_pass if rec.Synapse.synapse_type == 'in' else rec.PulseResponse.ex_qc_pass pen = (255, 255, 255, 100) if qc_pass else (200, 50, 0, 100) t0 = rec.PulseResponse.data_start_time - spike_t ts = TSeries(data=rec.data, t0=t0, sample_rate=db.default_sample_rate) c = self.data_plot.plot(ts.time_values, ts.data, pen=pen) # arrange plots nicely y0 = 0 if not subtract_baseline else ts.time_slice(None, 0).median() shift = (pulse_n * 35e-3 + (30e-3 if pulse_n > 8 else 0), -y0) zval = 0 if qc_pass else -10 c.setPos(*shift) c.setZValue(zval) if show_spikes: t0 = rec.spike_data_start_time - spike_t spike_ts = TSeries(data=rec.spike_data, t0=t0, sample_rate=db.default_sample_rate) c = self.spike_plot.plot(spike_ts.time_values, spike_ts.data, pen=pen) c.setPos(*shift) c.setZValue(zval) # evaluate recorded fit for this response fit_par = rec.PulseResponseFit if fit_par.fit_amp is None: continue fit = psp.eval( x=ts.time_values, exp_amp=fit_par.fit_exp_amp, exp_tau=fit_par.fit_decay_tau, amp=fit_par.fit_amp, rise_time=fit_par.fit_rise_time, decay_tau=fit_par.fit_decay_tau, xoffset=fit_par.fit_latency, yoffset=fit_par.fit_yoffset, rise_power=2, ) pen = (0, 255, 0, 100) if qc_pass else (50, 150, 0, 100) c = self.data_plot.plot(ts.time_values, fit, pen=pen) c.setZValue(10) c.setPos(*shift) if not qc_pass: print( "qc fail: ", rec.PulseResponse.meta.get('qc_failures', 'no qc failures recorded')) self.spike_plot.enableAutoRange(True, True) self.data_plot.enableAutoRange(True, True)
def _plot_pulse_response(self, rec): pr = rec.PulseResponse base_ts = pr.get_tseries('baseline', align_to='spike') if base_ts is not None: self.data_plots[1].plot(base_ts.time_values, base_ts.data) pre_ts = pr.get_tseries('pre', align_to='spike') # If there is no presynaptic spike time, plot spike in red and bail out if pre_ts is None: pre_ts = pr.get_tseries('pre', align_to='pulse') self.spike_plots[0].plot(pre_ts.time_values, pre_ts.data, pen=(255, 0, 0, 100)) return post_ts = pr.get_tseries('post', align_to='spike') self.spike_plots[0].plot(pre_ts.time_values, pre_ts.data) self.data_plots[0].plot(post_ts.time_values, post_ts.data) # evaluate recorded fit for this response fit_par = rec.PulseResponseFit # If there is no fit, bail out here if fit_par is None: return spsp = StackedPsp() fit = spsp.eval( x=post_ts.time_values, exp_amp=fit_par.fit_exp_amp, exp_tau=fit_par.fit_decay_tau, amp=fit_par.fit_amp, rise_time=fit_par.fit_rise_time, decay_tau=fit_par.fit_decay_tau, xoffset=fit_par.fit_latency, yoffset=fit_par.fit_yoffset, rise_power=2, ) self.data_plots[0].plot(post_ts.time_values, fit, pen=(0, 255, 0, 100)) # plot with reconvolved amplitude fit = spsp.eval( x=post_ts.time_values, exp_amp=fit_par.fit_exp_amp, exp_tau=fit_par.fit_decay_tau, amp=fit_par.dec_fit_reconv_amp, rise_time=fit_par.fit_rise_time, decay_tau=fit_par.fit_decay_tau, xoffset=fit_par.fit_latency, yoffset=fit_par.fit_yoffset, rise_power=2, ) self.data_plots[0].plot(post_ts.time_values, fit, pen=(200, 255, 0, 100)) # plot deconvolution clamp_mode = pr.recording.patch_clamp_recording.clamp_mode if clamp_mode == 'ic': decay_tau = self.loaded_pair.synapse.psp_decay_tau lowpass = 2000 else: decay_tau = self.loaded_pair.synapse.psc_decay_tau lowpass = 6000 dec = deconv_filter(post_ts, None, tau=decay_tau, lowpass=lowpass, remove_artifacts=False, bsub=True) self.dec_plots[0].plot(dec.time_values, dec.data) # plot deconvolution fit psp = Psp() fit = psp.eval( x=dec.time_values, exp_tau=fit_par.dec_fit_decay_tau, amp=fit_par.dec_fit_amp, rise_time=fit_par.dec_fit_rise_time, decay_tau=fit_par.dec_fit_decay_tau, xoffset=fit_par.dec_fit_latency, yoffset=fit_par.dec_fit_yoffset, rise_power=1, ) self.dec_plots[0].plot(dec.time_values, fit, pen=(0, 255, 0, 100))