def fit_responses(self, latency=None): if latency is None: latency_window = [0.5e-3, 10e-3] else: latency_window = [latency - 100e-6, latency + 100e-6] with pg.ProgressDialog("curve fitting..", maximum=len(modes) * len(holdings)) as dlg: self.last_fit = {} for mode in modes: for holding in holdings: self.fit_pass = False sign = self.signs[mode][holding].get( self.ctrl_panel.user_params['Synapse call'], self.signs[mode][holding].get( self.ctrl_panel.user_params['Polysynaptic call'], 0)) # ofp, x_offset, best_fit = fit_avg_response(self.traces, mode, holding, latency, sign) prs = self.sorted_responses[mode, holding]['qc_pass'] if len(prs) == 0: dlg += 1 continue fit, avg = fit_avg_pulse_response( prs, latency_window=latency_window, sign=sign) fit_ts = avg.copy(data=fit.best_fit) self.last_fit[mode, holding] = fit self.initial_fit_parameters[mode][str( holding)]['xoffset'] = latency self.output_fit_parameters[mode][str( holding)]['nrmse'] = fit.nrmse() self.output_fit_parameters[mode][str(holding)].update( fit.best_values) self.fit_pass = fit.nrmse() < self.nrmse_thresh self.ctrl_panel.output_params.child( 'Fit parameters', str(holding) + ' ' + mode.upper(), 'Fit Pass').setValue(self.fit_pass) if mode == 'vc': self.vc_plot.plot_fit(fit_ts, holding, self.fit_pass) elif mode == 'ic': self.ic_plot.plot_fit(fit_ts, holding, self.fit_pass) dlg += 1 if dlg.wasCanceled(): raise Exception("User canceled fit") self.fit_params['initial'] = self.initial_fit_parameters self.fit_params['fit'] = self.output_fit_parameters self.ctrl_panel.update_fit_params(self.fit_params['fit']) self.generate_warnings()
def get_pair_avg_fits(pair, session, notes_session=None, ui=None, max_ind_freq=50): """Return PSP fits to averaged responses for this pair. Fits are performed against average PSPs in 4 different categories: IC -70mV, IC -55mV, VC -70mV, and VC -55mV. All PSPs in these categories are averaged together regardless of their position in a pulse train, so we expect the amplitudes of these averages to be affected by any short-term depression/facilitation present at the synapse. As such, these fits are not ideal for measuring the amplitude of the synapse; however, they do provide good estimates of rise time and decay tau. Operations are: - Query all pulse responses for this pair, where the pulse train frequency was no faster than max_ind_freq - Sort responses by clamp mode and holding potential, with the latter in two bins: -80 to -60 mV and -60 to -45 mV. Responses are further separated into qc pass/fail for each bin. QC pass criteria: - PR must have exactly one presynaptic spike with detectable latency - Either PR.ex_qc_pass or .in_qc_pass must be True, depending on clamp mode / holding - Generate average response for qc-passed pulses responses in each mode/holding combination - Fit averages to PSP curve. If the latency was manually annotated for this synapse, then the curve fit will have its latency constrained within ±100 μs. - Compare to manually verified fit parameters; if these are not a close match OR if the manual fits were already failed, then *fit_qc_pass* will be False. Returns ------- results : dict {(mode, holding): { 'responses': .., 'average': .., 'initial_latency': .., 'fit_result': .., 'fit_qc_pass': .., 'fit_qc_pass_reasons': .., 'expected_fit_params': .., 'expected_fit_pass': .., 'avg_baseline_noise': .., }, ...} """ prof = pg.debug.Profiler(disabled=True, delayed=False) prof(str(pair)) results = {} # query and sort pulse responses with induction frequency 50Hz or slower records = response_query(session=session, pair=pair, max_ind_freq=max_ind_freq).all() prof('query prs') pulse_responses = [rec[0] for rec in records] # sort into clamp mode / holding bins sorted_responses = sort_responses(pulse_responses) prof('sort prs') # load expected PSP curve fit parameters from notes DB notes_rec = notes_db.get_pair_notes_record(pair.experiment.ext_id, pair.pre_cell.ext_id, pair.post_cell.ext_id, session=notes_session) prof('get pair notes') if ui is not None: ui.show_pulse_responses(sorted_responses) ui.show_data_notes(notes_rec) prof('update ui') for (clamp_mode, holding), responses in sorted_responses.items(): if len(responses['qc_pass']) == 0: results[clamp_mode, holding] = None continue if notes_rec is None: notes = None sign = 0 init_latency = None latency_window = (0.5e-3, 10e-3) else: notes = notes_rec.notes if notes.get('fit_parameters') is None: init_latency = None latency_window = (0.5e-3, 10e-3) else: init_latency = notes['fit_parameters']['initial'][clamp_mode][str(holding)]['xoffset'] latency_window = (init_latency - 100e-6, init_latency + 100e-6) # Expected response sign depends on synapse type, clamp mode, and holding: sign = 0 if notes['synapse_type'] == 'ex': sign = -1 if clamp_mode == 'vc' else 1 elif notes['synapse_type'] == 'in' and holding == -55: sign = 1 if clamp_mode == 'vc' else -1 prof('prepare %s %s' % (clamp_mode, holding)) fit_result, avg_response = fit_avg_pulse_response(responses['qc_pass'], latency_window, sign) prof('fit avg') # measure baseline noise avg_baseline_noise = avg_response.time_slice(avg_response.t0, avg_response.t0+7e-3).data.std() # compare to manually-verified results if notes is None: qc_pass = False reasons = ['no data notes entry'] expected_fit_params = None expected_fit_pass = None elif notes['fit_pass'][clamp_mode][str(holding)] is not True: qc_pass = False reasons = ['data notes fit failed qc'] expected_fit_params = None expected_fit_pass = False else: expected_fit_params = notes['fit_parameters']['fit'][clamp_mode][str(holding)] expected_fit_pass = True qc_pass, reasons = check_fit_qc_pass(fit_result, expected_fit_params, clamp_mode) if not qc_pass: print("%s %s %s: %s" % (str(pair), clamp_mode, holding, '; '.join(reasons))) if ui is not None: ui.show_fit_results(clamp_mode, holding, fit_result, avg_response, qc_pass) results[clamp_mode, holding] = { 'responses': responses, 'average': avg_response, 'initial_latency': init_latency, 'fit_result': fit_result, 'fit_qc_pass': qc_pass, 'fit_qc_pass_reasons': reasons, 'expected_fit_params': expected_fit_params, 'expected_fit_pass': expected_fit_pass, 'avg_baseline_noise': avg_baseline_noise, } return results
def get_pair_avg_fits(pair, session, notes_session=None, ui=None): """Return PSP fits to averaged responses for this pair. Operations are: - query all pulse responses for this pair - sort responses by clamp mode and holding potential - generate average response for each mode/holding combination - fit averages to PSP curve Returns ------- results : dict {(mode, holding): { 'traces': , 'average', 'fit_params', 'initial_latency', 'fit_qc_pass', 'expected_fit_params', 'avg_baseline_noise', }, } """ prof = pg.debug.Profiler(disabled=True, delayed=False) prof(str(pair)) results = {} # query and sort pulse responses records = response_query(session=session, pair=pair).all() prof('query prs') pulse_responses = [rec[0] for rec in records] sorted_responses = sort_responses(pulse_responses) prof('sort prs') notes_rec = notes_db.get_pair_notes_record(pair.experiment.ext_id, pair.pre_cell.ext_id, pair.post_cell.ext_id, session=notes_session) prof('get pair notes') if ui is not None: ui.show_pulse_responses(sorted_responses) ui.show_data_notes(notes_rec) prof('update ui') for (clamp_mode, holding), responses in sorted_responses.items(): if len(responses['qc_pass']) == 0: results[clamp_mode, holding] = None continue if notes_rec is None: notes = None sign = 0 init_latency = None latency_window = (0.5e-3, 8e-3) else: notes = notes_rec.notes if notes.get('fit_parameters') is None: init_latency = None latency_window = (0.5e-3, 8e-3) else: init_latency = notes['fit_parameters']['initial'][clamp_mode][str(holding)]['xoffset'] latency_window = (init_latency - 100e-6, init_latency + 100e-6) # Expected response sign depends on synapse type, clamp mode, and holding: sign = 0 if notes['synapse_type'] == 'ex': sign = -1 if clamp_mode == 'vc' else 1 elif notes['synapse_type'] == 'in' and holding == -55: sign = 1 if clamp_mode == 'vc' else -1 prof('prepare %s %s' % (clamp_mode, holding)) fit_result, avg_response = fit_avg_pulse_response(responses['qc_pass'], latency_window, sign) prof('fit avg') # measure baseline noise avg_baseline_noise = avg_response.time_slice(avg_response.t0, avg_response.t0+7e-3).data.std() # load up expected fit results and compare to manually-verified # results if notes is None: qc_pass = False reasons = ['no data notes entry'] expected_fit_params = None expected_fit_pass = None elif notes['fit_pass'][clamp_mode][str(holding)] is not True: qc_pass = False reasons = ['data notes fit failed qc'] expected_fit_params = None expected_fit_pass = False else: expected_fit_params = notes['fit_parameters']['fit'][clamp_mode][str(holding)] expected_fit_pass = True qc_pass, reasons = check_fit_qc_pass(fit_result, expected_fit_params, clamp_mode) if not qc_pass: print("%s %s %s: %s" % (str(pair), clamp_mode, holding, '; '.join(reasons))) if ui is not None: ui.show_fit_results(clamp_mode, holding, fit_result, avg_response, qc_pass) results[clamp_mode, holding] = { 'responses': responses, 'average': avg_response, 'initial_latency': init_latency, 'fit_result': fit_result, 'fit_qc_pass': qc_pass, 'fit_qc_pass_reasons': reasons, 'expected_fit_params': expected_fit_params, 'expected_fit_pass': expected_fit_pass, 'avg_baseline_noise': avg_baseline_noise, } return results