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)
Пример #2
0
 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)
Пример #4
0
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
Пример #5
0
    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
Пример #8
0
    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)
Пример #9
0
    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))