1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 from __future__ import with_statement
20
21 from cStringIO import StringIO
22 from qubx.hist import *
23 from qubx.model import *
24 from qubx_single.rates import *
25 import qubx.fit
26 import qubx.pyenv
27 import qubx.pyenvGTK
28 import qubx.table
29 import qubx.toolspace
30 import numpy
31 import qubx.fast.model
32 from qubx.fast.fast_utils import c_double, c_double_p, cdata, buffer_as_array_of_int
33 from qubx.settings import Property, Propertied
34
35 NBIN_DUR = 50
36 LBL_TDEAD = 'Tdead (ms)'
37
38 MAX_CHANNEL_COUNT = 8
39 MAX_DURHIST_CLASSES = 32
40
41 COLOR_TCRIT = ('qubx_single.dur_hist.tcrit', (1, .2, .2, .7))
42 ColorInfo[COLOR_TCRIT[0]].label = 'Figures:DurHist Tcrit line'
43
44
45 @Propertied(Property('bins', 96, 'number of bins'),
46 Property('smooth', True, 'True to spread short events among adjacent bins, adjusting for sampling error'),
47 Property('sqrt', True, 'True to display sqrt(y axis)'))
49 __explore_featured = ['lo', 'hi', 'auto', 'nbTimeConst', 'signals', 'controls', 'save_as',
50 'copy_to_clipboard', 'copy_image_to_clipboard', 'save_to', 'fit_all_exponentials']
52 HistFace.__init__(self, 'DurHist', 'QubX.Figures.DurHist')
53 self.__ref = Reffer()
54 self.propertied_connect_settings('qubx_single.dur_hist')
55 self.__lo = 0.0
56 self.__hi = 1.0
57 self.__auto = True
58 self.__in_all_exp = False
59 self.__last_path = ''
60 self.file_i_bins = 0
61 self.file_i_bars = 1
62 self.file_right_edge = True
63 self.file_sqrt = True
64 self.file_norm = False
65 self.cancel_all_exp = False
66 self.OnChangePage += self.__ref(self.__onChangePage)
67 self.OnAddPlot += self.__ref(self.__onAddPlot)
68 self.OnRemovingPlot += self.__ref(self.__onAddPlot)
69 self.nbTimeConst = qubx.notebook.NbItems("All time constants", self.global_name and '%s.nbTimeConst'%self.global_name or '')
70 self.signals = SinglePlex(SingleSignals(self.QubX.DataSource, self.QubX.Data, self.QubX.Modeling.Stimulus))
71 self.signals.OnInvalidate += self.__ref(self.__on_invalidata)
72 self.QubX.Modeling.MILRates.model_prep.OnInvalidate += self.__ref(self.__on_invalimodel)
73 self.QubX.Modeling.MILRates.rates.OnInvalidate += self.__ref(self.__on_invalirates)
74
75 self.controls = qubx.faces.Face("DurHist", global_name='QubX.About.DurHist')
76 self.controls.show()
77 line = gtk.HBox(True)
78 line.show()
79 lbl = gtk.Label('Bins:')
80 lbl.show()
81 line.pack_start(lbl)
82 self.txtBins = qubx.GTK.NumEntry(self.bins, acceptIntGreaterThan(1))
83 self.propertied_connect_NumEntry('bins', self.txtBins)
84 self.txtBins.show()
85 line.pack_start(self.txtBins)
86 self.controls.pack_start(line, False, True)
87 line = gtk.HBox(True)
88 line.show()
89 line = gtk.HBox()
90 line.show()
91 self.chkAuto = gtk.CheckButton('auto')
92 self.chkAuto.set_active(self.__auto)
93 self.chkAuto.connect('toggled', self.__onToggleAuto)
94 self.chkAuto.show()
95 line.pack_start(self.chkAuto, False, True)
96 lbl = gtk.Label('lo:')
97 lbl.show()
98 line.pack_start(lbl, False, True)
99 self.txtLo = qubx.GTK.NumEntry(self.__lo, format='%.2g')
100 self.txtLo.set_width_chars(4)
101 self.txtLo.OnChange += self.__ref(self.__onChangeLo)
102 self.txtLo.show()
103 line.pack_start(self.txtLo, False, True)
104 lbl = gtk.Label('hi:')
105 lbl.show()
106 line.pack_start(lbl, False, True)
107 self.txtHi = qubx.GTK.NumEntry(self.__hi, format='%.2g')
108 self.txtHi.set_width_chars(4)
109 self.txtHi.OnChange += self.__ref(self.__onChangeHi)
110 self.txtHi.show()
111 line.pack_start(self.txtHi, False, True)
112 self.controls.pack_start(line, False, True)
113 self.chkSmooth = gtk.CheckButton('Smooth')
114 self.propertied_connect_check('smooth', self.chkSmooth)
115 self.chkSmooth.show()
116 self.controls.pack_start(self.chkSmooth, False, True)
117 self.chkSqrt = gtk.CheckButton('Sqrt ordinate')
118 self.propertied_connect_check('sqrt', self.chkSqrt)
119 self.chkSqrt.show()
120 self.controls.pack_start(self.chkSqrt, False, True)
121 line = gtk.HBox()
122 line.show()
123 self.btnSave = gtk.Button('Save...')
124 self.btnSave.connect('clicked', self.__onClickSave)
125 self.btnSave.show()
126 line.pack_start(self.btnSave, False, True)
127 self.btnCopy = gtk.Button('Copy')
128 self.btnCopy.connect('clicked', self.__onClickCopy)
129 self.btnCopy.show()
130 line.pack_start(self.btnCopy, False, True)
131 self.btnCopyImage = gtk.Button('Image...')
132 self.btnCopyImage.connect('clicked', self.__onClickCopyImage)
133 self.btnCopyImage.show()
134 line.pack_start(self.btnCopyImage, False, True)
135 self.controls.pack_start(line, False, True)
136 scr = gtk.ScrolledWindow()
137 scr.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
138 scr.show()
139 self.txtStim = gtk.TextView()
140 self.outStim = qubx.GTK.TextViewAppender(self.txtStim)
141 self.txtStim.show()
142 scr.add(self.txtStim)
143 self.controls.pack_end(scr, True, True)
144
145 self.__need_data = True
146 self.__need_model = True
147 self.__need_rates = True
148 self.robot.model = QubModel()
149 self.robot.OnInterrupt += self.__ref(self.__onInterrupt)
150
151
152 self.robot.on_start_item = lambda: None
153 self.robot.on_finish_item = lambda: None
154
155 gobject.idle_add(self.update)
156
157 self.Ns = self.Nc = 0
158 self.path = None
164 redo = x != self.__auto
165 self.__auto = x
166 self.chkAuto.set_active(x)
167 if redo:
168 self.__need_data = True
169 self.update()
170 auto = property(lambda self: self.__auto, lambda self, x: self.set_auto(x))
171 - def set_lo(self, x, auto=False):
172 self.__lo = x
173 self.txtLo.setValue(x)
174 if not auto:
175 self.auto = False
176 self.__need_data = True
177 self.update()
178 lo = property(lambda self: self.__lo, lambda self, x: self.set_lo(x))
179 - def set_hi(self, x, auto=False):
180 self.__hi = x
181 self.txtHi.setValue(x)
182 if not auto:
183 self.auto = False
184 self.__need_data = True
185 self.update()
186 hi = property(lambda self: self.__hi, lambda self, x: self.set_hi(x))
207 buf = cStringIO.StringIO()
208 self.save_to(buf)
209 gtk.clipboard_get('CLIPBOARD').set_text(buf.getvalue())
219 pixmap = gdk.Pixmap(self.window, w, h, -1)
220 ctx = pixmap.cairo_create()
221 ctx.set_source_rgb(1,1,1)
222 ctx.paint()
223 self.plots[c].draw_to_context(ctx, w, h)
224 del ctx
225 pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, w, h)
226 pixbuf.get_from_drawable(pixmap, gdk.colormap_get_system(), 0, 0, 0, 0, -1, -1)
227 clipboard = gtk.clipboard_get('CLIPBOARD')
228 clipboard.set_image(pixbuf)
229 del pixmap
230 del pixbuf
231 qubx.pyenv.env.gc_collect_on_idle()
232 self.plots[c].redraw_canvas()
243 if self.__in_all_exp:
244 self.cancel_all_exp = True
245 cancel()
247 self.__need_data = True
248
249 self.update()
251 self.__need_model = True
252
253 self.update()
255 self.__need_rates = True
256
257 self.update()
259 sc = scls
260 if sc == None:
261 sc = self.page
262 for c,plot in enumerate(self.plots):
263 hist = self.histrec[sc][c]
264 f.write('Binmax\tlog10(Binmax)\t')
265 if self.sqrt:
266 f.write('sqrt(count%i/total)\t'%(c+1))
267 else:
268 f.write('count%i/total\t'%(c+1))
269 if hist.pdf != None:
270 f.write('PDF%i\t'%(c+1))
271 f.write('\t'*len(hist.components))
272 f.write('\n')
273 for b in xrange(self.bins):
274 for c,plot in enumerate(self.plots):
275 hist = self.histrec[sc][c]
276 f.write('%.8g\t' % hist.bins[b])
277 f.write('%.8g\t' % log10(hist.bins[b]))
278 f.write('%.8g\t' % hist.bars[b])
279 if hist.pdf != None:
280 f.write('%.8g\t' % hist.pdf[b])
281 for m in hist.components:
282 f.write('%.8g\t' % m[b])
283 f.write('\n')
284 - def __onChangePage(self, index):
285 if (0 <= index < len(self.histrec)) and len(self.histrec[index]):
286 self.txtStim.get_buffer().set_text('\n\n'.join([self.histrec[index][0].txt_stim] + [hh.txt_time_const for hh in self.histrec[index]]))
288 plot = self.plots[index]
289 ref = plot.xref = Reffer()
290 plot.OnDraw += ref(bind_with_args_before(self.__onDrawPlot, index))
291 if index == 0:
292 self.plots[0].subNotebook.items.append(self.nbTimeConst)
293 plot.nbTimeConst = qubx.notebook.NbDynText('Time constants', '%s.plots[%i].nbTimeConst' % (self.global_name, index), bind(self.__nb_time_const, index))
294 plot.subNotebook.items.append(plot.nbTimeConst)
295 self.nbTimeConst.items.insert(index, plot.nbTimeConst)
296 plot.layAllExponentials = qubx.toolspace.Layer(x=-20, y=-2.5*qubx.fit_space.LINE_EMS, w=19, h=qubx.fit_space.LINE_EMS, cBG=plot.controls.cLayer)
297 plot.actAllExponentials = bind(self.__onClickAllExponentials, plot)
298 plot.layAllExponentials.add_sublayer(qubx.toolspace.SubLayer_Label('Fit all exponentials...', 0, 1, x=0, y=0, w=15, h=qubx.fit_space.LINE_EMS,
299 color=plot.controls.cButton, action=plot.actAllExponentials))
300 plot.layAllExponentials.add_sublayer(qubx.toolspace.SubLayer_PNG(os.path.join(qubx.global_namespace.app_path, 'icons', 'folder.png'), x=16, y=0, w=qubx.fit_space.LINE_EMS, h=qubx.fit_space.LINE_EMS, tooltip="Open histogram from a file for fitting...",
301 action=ref(bind(self.__open_file, index))))
302 plot.layAllExponentials.add_sublayer(qubx.toolspace.SubLayer_Label('?', 0, 1, x=18, y=0, w=1, h=qubx.fit_space.LINE_EMS, tooltip="About this algorithm...",
303 color=plot.controls.cButton, action=ref(self.__show_all_exponentials_info)))
304 plot.controls.add_layer(plot.layAllExponentials)
305 plot.layAllExpoLLR = qubx.toolspace.Layer(x=-16, y=-8*qubx.fit_space.LINE_EMS, w=15, h=qubx.fit_space.LINE_EMS, cBG=plot.controls.cLayer)
306 plot.layAllExpoLLR.add_sublayer(qubx.toolspace.SubLayer_Label('LLR: ', 1, 0, x=0, y=0, w=6, h=qubx.fit_space.LINE_EMS, color=plot.controls.cText))
307 plot.subAllExpoLLR = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=0, w=8, h=qubx.fit_space.LINE_EMS, color=plot.controls.cNumber)
308 plot.layAllExpoLLR.add_sublayer(plot.subAllExpoLLR)
309 plot.showingLLR = false
310 plot.layFileInfo = qubx.toolspace.Layer(x=-25, y=2, w=19, h=qubx.fit_space.LINE_EMS, cBG=plot.controls.cLayer)
311 plot.subFileName = qubx.toolspace.SubLayer_Label('', 0, 1, x=0, y=0, w=17, h=qubx.fit_space.LINE_EMS,
312 color=plot.controls.cText,
313 action=ref(bind(self.__open_file, index)))
314 plot.layFileInfo.add_sublayer(plot.subFileName)
315 plot.subFileClose = qubx.toolspace.SubLayer_Label('x', 0, 1, x=18, y=0, w=1, h=qubx.fit_space.LINE_EMS,
316 color=plot.controls.cButton,
317 action=ref(bind(self.__close_file, index)))
318 plot.layFileInfo.add_sublayer(plot.subFileClose)
319 plot.has_file = false
340
341 self.robot.progress = 0
342 self.robot.new_data = self.__need_data
343 if self.__need_data:
344 segs, mcls, scls, names, sampling_ms, td_ms = self.robot.gui_call_recv(self.signals.request, self.robot)
345 shown_sc = max(0, min(int(self.page), len(scls)-1))
346 self.robot_update_data(segs, mcls, scls, names, sampling_ms, td_ms, shown_sc)
347 self.robot_update_pdfs()
349 self.robot.names = names
350 sampling = sampling_ms * 1e-3
351 auto, lo, hi = self.auto, self.lo, self.hi
352 lo -= td_ms
353 hi -= td_ms
354 lo *= 1e-3
355 hi *= 1e-3
356 if not segs: return
357 self.robot.progress = 20
358 per_scls = events_per_scls(segs, mcls, scls)
359 if not per_scls: return
360 self.robot.progress = 30
361 if auto:
362 t_min = [len(du) and min(du) or 0.0 for cl, du in per_scls]
363 t_max = [len(du) and max(du) or 0.0 for cl, du in per_scls]
364 if len(t_min):
365 with self.robot.main_hold:
366 self.set_lo(t_min[shown_sc]*1e3+td_ms, auto=True)
367 self.set_hi(t_max[shown_sc]*1e3+td_ms, auto=True)
368 else:
369 t_min = len(scls) * [lo]
370 t_max = len(scls) * [hi]
371 self.robot.Nc = min(MAX_DURHIST_CLASSES, max(len(cl) and max(cl) or 0 for cl, du in per_scls) + 1)
372 stimulus = [ dict([(name, scval) for name, scval in izip(names, scvals)]) for scvals in scls ]
373
374 Hist = self.smooth and DurHist_Smooth_lib or DurHist_lib
375 cap_stim = []
376 txt_stim = []
377 for sc in xrange(len(per_scls)):
378 buf_stim = StringIO()
379 cap = ""
380 for i, nm in enumerate(names):
381 if i > 0:
382 buf_stim.write('%s: %.6g\n' % (nm, stimulus[sc][nm]))
383 if i > 1:
384 cap = cap + ', '
385 cap = cap + '%.3g' % stimulus[sc][nm]
386 cap = cap and (' at '+cap)
387 txt_stim.append(buf_stim.getvalue())
388 cap_stim.append(cap)
389
390 def make_rec(hist_out, sc, c):
391 bins, bars = hist_out
392 logbins = numpy.log10(bins)
393 if self.sqrt:
394 bars = numpy.sqrt(bars)
395 caption = 'DurHist of class %d%s'%(c, cap_stim[sc])
396 return HistRec(caption, caption, logbins, bars, linbins=bins,
397 nb_headers=['log10_ms', 'count_per_n'], nb_xlabel='log10(ms)', nb_ylabel='count / n',
398 data_colorref=qubx.modelGTK.COLOR_CLASS(c), txt_stim=txt_stim[sc], txt_time_const="")
399 hist_sc_cl = []
400 for sc, data in enumerate(per_scls):
401 bins = DurHist_Bins([data], td_ms, t_min[sc], t_max[sc], self.bins)
402 hist_of_sc = []
403 for c in xrange(self.robot.Nc):
404 if (c < len(self.plots)) and self.plots[c].has_file:
405 hist_of_sc.append(self.histrec[sc][c])
406 else:
407 hist_of_sc.append(make_rec(Hist(c, [data], td_ms, sampling, bins), sc, c))
408 hist_sc_cl.append(hist_of_sc)
409
410 self.robot.progress = 45
411 with self.robot.main_hold:
412 self.stimulus = stimulus
413 self.sig_names = names
414 self.histrec = hist_sc_cl
415 self.Ns = len(scls)
416 self.td = td_ms
417 self.__need_data = False
418
419 gobject.idle_add(self.set_page_count, self.Ns)
420 gobject.idle_add(self.update_plots)
421
423 self.robot.progress = 50
424 if self.robot.new_data or self.__need_model or self.__need_rates:
425 if self.__need_model:
426 self.robot.model.undoStack.clear()
427 mt = self.QubX.Modeling.MILRates.model_prep.val.modeltree
428 if mt.find('States').find('State').isNull:
429 return
430 self.robot.model.from_tree( self.QubX.Modeling.MILRates.model_prep.val.modeltree )
431 elif self.__need_rates:
432 self.__need_rates = False
433 rrates = self.robot.model.rates
434 K0, K1, K2 = self.QubX.Modeling.MILRates.rates.val
435 if not (K0 is None):
436 for i in xrange(rrates.size):
437 a, b = [rrates.get(i, field) for field in ['From', 'To']]
438 rrates.set(i, 'k0', K0[a,b])
439 rrates.set(i, 'k1', K1[a,b])
440 rrates.set(i, 'k2', K2[a,b])
441 self.robot.progress = 70
442 self.robot.model.channelCount = min(MAX_CHANNEL_COUNT, self.robot.model.channelCount)
443 single_model = ModelToFast(self.robot.model, self.robot.names, printer)
444 multi_model = qubx.fast.model.MultiModel(single_model)
445 multi_model.update()
446 mm = multi_model.multi_model
447 clazz = buffer_as_array_of_int(mm[0].clazz, mm[0].Nstate)
448
449 classmap = sorted(list(set(st.Class for st in self.robot.model.states)))
450
451 stimclasses_ = numpy.zeros(shape=(self.Ns, len(self.robot.names)), dtype='float64')
452 for s in xrange(self.Ns):
453 for ni, nm in enumerate(self.robot.names):
454 stimclasses_[s,ni] = self.stimulus[s][nm]
455
456 stimclasses = cdata(stimclasses_, c_double_p)
457 ratesm = qubx.fast.model.StimRates(mm, self.Ns, len(self.robot.names), stimclasses, None, 0.0)
458 ratesm.setup_QQ()
459 for s in xrange(self.Ns):
460 Q = numpy.matrix(ratesm.QQ[s])
461
462 for c in xrange(self.plot_count):
463 if self.robot.model.channelCount == 1:
464 if c in classmap:
465 cl = classmap.index(c)
466 else:
467 continue
468 elif c in clazz:
469 cl = c
470 else:
471 continue
472 hist = self.histrec[s][c]
473 if (not (hist.bins is None)) and (len(hist.bins) > 1):
474 hist.pdf, hist.components, hist.time_const_tau, hist.time_const_amp, hist.tcrit = PDF(Q, clazz, cl, self.td, hist.linbins, not self.QubX.Modeling.MILRates.optimizing)
475 if self.sqrt:
476 hist.pdf = sqrt(hist.pdf)
477 hist.components = [sqrt(cc) for cc in hist.components]
478 hist.txt_time_const = "Class %i:\nTime Constants: (Tau [ms], Amp)\n" % c + \
479 "\n".join(["%.4g, %.4g" % (1e3*tc_tau, tc_amp) for (tc_tau, tc_amp) in izip(hist.time_const_tau, hist.time_const_amp)])
480 if hist.tcrit:
481 hist.txt_time_const += "\n\nTcrit:\n" + ", ".join("%.4g" % (1e3*crit) for crit in hist.tcrit) + "\n\n"
482
483 single_model.dispose()
484 self.__need_model = self.__need_rates = False
485
486 gobject.idle_add(self.update_plots)
503 pool = multiprocessing.Pool()
504 def map_func(func, args):
505 result = [x for x in pool.imap(func, args)]
506 return result
507 try:
508 expo = AllExponentials()
509 params = [1.0] * len(expo.params)
510 if is_sqrt:
511 yy = yy**2
512 for pct in expo.run_generator(xx, yy, params, map_func=map_func, significance=significance):
513 gobject.idle_add(self.__show_expo, plot, is_sqrt, params[:], pct > 99.5)
514 except:
515 traceback.print_exc()
516 pool.close()
517 gobject.idle_add(self.__done_expo, plot, expo.report_buf.getvalue(), expo.llr)
518 - def __show_expo(self, plot, is_sqrt, params, can_fit):
519 N = int(round(params[0]))
520 robot = plot.controls.robot
521 robot.set_curve(AllExponentials_Display, "%i %sExponential%s, see Landowne et al, 2013"%(N, is_sqrt and "(sqrt) " or "", (N>1) and "s" or ""))
522 for i in xrange(N):
523 robot.set_param("tau%i"%(i+1), params[2*i+1], 1e-16, UNSET_VALUE, can_fit)
524 robot.set_param("area%i"%(i+1), params[2*i+2], 0.0, UNSET_VALUE, can_fit)
537 plot = self.plots[index]
538 self.__last_path, fname = os.path.split(path)
539 headers, types, columns = qubx.table.read_table_text(open(path, 'r').read())
540 i_bins, i_bars, right_edge, is_sqrt, is_norm = self.prompt_file_columns(headers, types, columns)
541 if i_bins is None:
542 return
543 self.__close_file(index, False)
544 plot.subFileName.label = fname if (len(fname) < 15) else (fname[:15]+'...')
545 if not plot.has_file:
546 plot.has_file = True
547 plot.controls.add_layer(plot.layFileInfo)
548 plot.is_sqrt = is_sqrt
549 bins = columns[i_bins]
550 bars = columns[i_bars]
551 if is_norm:
552 total = 0.0
553 for x in bars:
554 total += x
555 if total:
556 bars = [x/total for x in bars]
557 if not right_edge:
558 w = (bins[2] - bins[1])
559 bins = [b+w for b in bins]
560 linbins = numpy.power(10, bins)
561 self.histrec[self.page][index] = HistRec(fname, fname, bins, bars, linbins=linbins)
562 self.update_plots()
570 dlg = gtk.Dialog('Import duration histogram...', self.parent_window, gtk.DIALOG_MODAL)
571 h = pack_item(gtk.HBox(), dlg.vbox)
572 pack_label('Bin column: ', h)
573 print headers, types, len(columns)
574 colChoices = ['%s: %s, ...' % (name, ', '.join(['%.3g'%v for v in columns[i][:3]])) for i, name in enumerate(headers)]
575 print colChoices
576 mnuBin = pack_item(qubx.GTK.StaticComboList(colChoices), h)
577 mnuBin.active_i = self.file_i_bins
578 chkRight = pack_check('contains right edge of each bin (otherwise, the left edge)', dlg.vbox, self.file_right_edge)
579 h = pack_item(gtk.HBox(), dlg.vbox)
580 pack_label('Bar column: ', h)
581 mnuBar = pack_item(qubx.GTK.StaticComboList(colChoices), h)
582 mnuBar.active_i = self.file_i_bars
583 chkSqrt = pack_check('contains sqrt(count/total) (otherwise, count/total)', dlg.vbox, self.file_sqrt)
584 chkNorm = pack_check('normalize (divide by sum)', dlg.vbox, self.file_norm)
585 dlg.add_buttons('OK', gtk.RESPONSE_ACCEPT, 'Cancel', gtk.RESPONSE_REJECT)
586 response = dlg.run()
587 if response == gtk.RESPONSE_ACCEPT:
588 self.file_i_bins = mnuBin.active_i
589 self.file_i_bars = mnuBar.active_i
590 self.file_right_edge = chkRight.get_active()
591 self.file_sqrt = chkSqrt.get_active()
592 self.file_norm = chkNorm.get_active()
593 dlg.destroy()
594 if response == gtk.RESPONSE_ACCEPT:
595 return self.file_i_bins, self.file_i_bars, self.file_right_edge, self.file_sqrt, self.file_norm
596 return None, None, None, None, None
598 global ALL_EXPONENTIALS_MAX_N, ALL_EXPONENTIALS_AREA_CUTOFF, ALL_EXPONENTIALS_NEAR_THRESHOLD, ALL_EXPONENTIALS_SIGNIFICANCE, ALL_EXPONENTIALS_FINAL_CUTOFF
599 result = qubx.pyenvGTK.prompt_entries(
600 [
601 ('Based on:', None, None, None),
602 ('Exponential Sum-Fitting of Dwell-Time Distributions without Specifying Starting Parameters', None, None, None),
603 ('David Landowne, Bin Yuan, and Karl L. Magleby', None, None, None),
604 ('Biophysical Journal Volume 104 June 2013 2383-2391', None, None, None),
605 ('Max components:', ALL_EXPONENTIALS_MAX_N, acceptIntGreaterThan(1), str),
606 ('Area cutoff:', ALL_EXPONENTIALS_AREA_CUTOFF, acceptFloatGreaterThan(0), '%.3g'),
607 ('Nearness threshold:', ALL_EXPONENTIALS_NEAR_THRESHOLD, acceptFloatGreaterThan(0), '%.3g'),
608 ('Significance:', ALL_EXPONENTIALS_SIGNIFICANCE, acceptFloatGreaterThan(0), '%.2f'),
609 ('Final area cutoff ("step 11"--enter 0 to skip):', ALL_EXPONENTIALS_FINAL_CUTOFF, acceptFloatGreaterThan(0), '%.3g')
610 ],
611 'About "Fit all exponentials"...'
612 )
613 if not (result is None):
614 ALL_EXPONENTIALS_MAX_N = result[4]
615 ALL_EXPONENTIALS_AREA_CUTOFF = result[5]
616 ALL_EXPONENTIALS_NEAR_THRESHOLD = result[6]
617 ALL_EXPONENTIALS_SIGNIFICANCE = result[7]
618 ALL_EXPONENTIALS_FINAL_CUTOFF = result[8]
619
621 Ns = len(scls)
622 per_scls = [ ([], []) for x in scls ]
623 for cl, du in segs:
624 for c, d in izip(cl, du):
625 mc, sc = mcls[c]
626 if mc >= 0:
627 per_scls[sc][0].append(mc)
628 per_scls[sc][1].append(d)
629 return [(numpy.array(cc, dtype='int32'), numpy.array(dd, dtype='float32')) for cc, dd in per_scls]
630
633 tmax = t_max
634 if tmax == None:
635 tmax = max([ max([dd for cc,dd in itertools.izip(classes, durations) if cc==c]) for classes, durations in data ])
636 tmax = tmax * 1e3 + td_ms
637 tmin = t_min
638 if tmin == None:
639 tmin = min([ min([dd for cc,dd in itertools.izip(classes, durations) if cc==c]) for classes, durations in data ])
640 tmin = tmin * 1e3 + td_ms
641 N = nbin
642 bins = zeros(shape=(N,), dtype='float32')
643 if tmax == tmin:
644 return bins
645 dx = log10(tmax/tmin)/(N-1)
646 r = pow(10.0, dx)
647 bins[0] = tmin
648 for i in xrange(1,N):
649 bins[i] = bins[i-1] * r
650 for i in reversed(range(1,N)):
651 if bins[i] == bins[i-1]:
652 bins[i-1:N-1] = bins[i:N]
653 N -= 1
654 bins.resize(N)
655 return bins
656
657 -def DurHist(c, data, td_ms, sampling, bins):
658 N = len(bins)
659 bars = zeros(shape=(N,))
660 tot_count = 0
661 for classes, durations in data:
662 for i in xrange(len(durations)):
663 if classes[i] != c: continue
664 dur = durations[i] * 1e3 + td_ms
665 last = 0.0
666 for b in xrange(N):
667 here = bins[b]
668 if last < dur <= here:
669 bars[b] += 1.0
670 last = here
671 tot_count += 1
672 if tot_count:
673 bars /= tot_count
674 return bins, bars
675
677 cc, dd = data[0]
678 return bins, durhist_calhist(c, cc, dd*1e3, bins, td_ms)
679
683
684 -def PDF(Q, clazz, a, td, bins, wantTcrit=True):
685 together = zeros(shape=bins.shape)
686 components = []
687 taus = []
688 amps = []
689
690 try:
691 Q = QtoQe(Q, clazz, td*1e-3)
692 A = clazz == a
693 Z = clazz != a
694 Qaa = Q[ix_(A, A)]
695 Qaz = Q[ix_(A, Z)]
696 Qza = Q[ix_(Z, A)]
697 Qzz = Q[ix_(Z, Z)]
698 W, V = linalg.eig( (Qaa.I*Qaz*Qzz.I*Qza).T )
699 i_unit = argmin(abs(W-1))
700 pe = V[:,i_unit].T
701 pe /= sum(pe)
702
703 W, V = linalg.eig(Qaa)
704 Vi = V.I
705 spectre = matrix(zeros(shape=Qaa.shape))
706 for i in xrange(Qaa.shape[0]):
707 for j in xrange(Qaa.shape[0]):
708 for k in xrange(Qaa.shape[0]):
709 spectre[j,k] = V[j,i] * Vi[i,k]
710 coeff = sum(pe * spectre * Qaz) / W[i]
711 itau = W[i]
712 component = zeros(shape=bins.shape)
713 last = bins[0] * bins[0] / bins[1]
714 for b in xrange(len(bins)):
715 next = bins[b]
716 component[b] = coeff * (exp(itau*(next-td)*1e-3) - exp(itau*(last-td)*1e-3))
717 together[b] += component[b]
718 last = next
719 components.append(component)
720 taus.append(-1.0/itau)
721 amps.append(-coeff)
722 except linalg.LinAlgError:
723 pass
724 sortix = [i for i in xrange(len(taus))]
725 sortix.sort(key=lambda i: taus[i])
726 taus = [taus[i] for i in sortix]
727 amps = [amps[i] for i in sortix]
728 crits = SolveTCrits(taus, amps) if wantTcrit else []
729 return together, components, taus, amps, crits
730
732
733
734 curve = qubx.fit.Curve("a1 * exp(-Tc / t1) + a2 * exp(-Tc / t2) - a2")
735 iA1, iT1, iA2, iT2, iTc = [curve.params.index(x) for x in ["a1", "t1", "a2", "t2", "Tc"]]
736 for i in xrange(len(curve.params)):
737 if i != iTc:
738 curve.can_fit[i] = False
739 fitter = qubx.fit.SimplexFitter()
740 xx = yy = array([0.0], dtype='float32')
741 crits = []
742 params = [0.0] * len(curve.params)
743 for i in xrange(len(taus)-1):
744 params[iA1] = amps[i]
745 params[iT1] = taus[i]
746 params[iA2] = amps[i+1]
747 params[iT2] = taus[i+1]
748 params[iTc] = (taus[i] + taus[i+1]) / 2
749
750
751 result, ssr, iterations, fit = fitter(curve, params, xx, yy)
752 crits.append(result[iTc])
753 return crits
754
755
756
757 intersect = lambda a0, a1, b0, b1: (max(a0, b0), min(a1, b1))
758 tri_rising_y = lambda peak, x: (x - peak + 1)
759 tri_falling_y = lambda peak, x: - ( x - peak - 1)
761 x0, x1 = intersect(peak-1, peak, fro, to)
762 if x0 < x1:
763 y0 = tri_rising_y(peak, x0)
764 y1 = tri_rising_y(peak, x1)
765 return (0.5*(x1-x0)*(y0+y1))
766 return 0.0
768 x0, x1 = intersect(peak, peak+1, fro, to)
769 if x0 < x1:
770 y0 = tri_falling_y(peak, x0)
771 y1 = tri_falling_y(peak, x1)
772 return (0.5*(x1-x0)*(y0+y1))
773 return 0.0
774 tri_area = lambda peak, fro, to: tri_rising_area(peak, fro, to) + tri_falling_area(peak, fro, to)
775
776 WAY_TOO_MANY_SAMPLES = 65536
779 if (count == 0) or (len(bins) == 0): return
780 a = 0.0
781 for i, b in enumerate(bins):
782 bars[i] += count * tri_area(samples, a/sampling_ms, b/sampling_ms)
783 a = b
784
786 td = td_ms * 1e-3
787 sampling_ms = sampling * 1e3
788 N = len(bins)
789 bars = zeros(shape=(N,))
790
791 sampleCounts = []
792
793 tot_count = 0
794 for classes, durations in data:
795 for i in xrange(len(durations)):
796 if classes[i] != c: continue
797 tot_count += 1
798 samples = int(round((durations[i] + td) / sampling))
799 if samples > WAY_TOO_MANY_SAMPLES:
800 spread_events(bins, bars, samples, 1, sampling_ms)
801 else:
802 while len(sampleCounts) <= samples:
803 sampleCounts.append(0)
804 sampleCounts[samples] += 1
805
806
807
808 for samples, count in enumerate(sampleCounts):
809 spread_events(bins, bars, samples, count, sampling_ms)
810 if tot_count:
811 bars /= tot_count
812 return bins, bars
813
818 qubx.GTK.CopyDialog.__init__(self, 'Copy histogram image', parent)
819 line = gtk.HBox(True)
820 line.show()
821 lbl = gtk.Label('Class:')
822 lbl.show()
823 line.pack_start(lbl)
824 self.txtClass = qubx.GTK.NumEntry(1, acceptInt)
825 self.txtClass.set_width_chars(11)
826 self.txtClass.show()
827 line.pack_start(self.txtClass)
828 self.vbox.pack_start(line, False, True)
829 - def run(self, view, Nclass):
835
836 TheDurHistCopyDialog = DurHistCopyDialog()
840 - def __init__(self, datasrc, Data, stimulus):
871 - def get(self, receiver):
872 data = qubx.pyenv.env.globals['QubX'].Data.view
873 if not data:
874 gobject.idle_add(receiver, None, None, None, None)
875 return
876
877 for i in xrange(data.file.constants.size):
878 if LBL_TDEAD == data.file.constants.get(i, 'Name'):
879 td = data.file.constants.get(i, 'Value')
880 break
881 else:
882 td = data.file.sampling * 1e3
883 data.file.constants.append({'Name':LBL_TDEAD, 'Value':td})
884 data.file.constants.select(data.file.constants.size-1, 'Value', sender=self)
885
886 segsrc = self.datasrc.get_segmentation()
887 sig_names = [data.file.signals.get(segsrc[0].signal, 'Name')] if segsrc else []
888 signals = [ [] ]
889 for seg in segsrc:
890 ff, ll, cc, dd = self.datasrc.get_idealization(seg, mark_excluded=True, get_fragments=True, get_durations=True)
891 aa = data.file.ideal[segsrc[0].signal].seg[seg.index].amp
892 signals[0].append( (cc, dd, aa) )
893 def receive_stim(stim_names, stimidl):
894 sig_names.extend(stim_names)
895 for segs_flcda in stimidl:
896 signals.append( [(cc, dd, aa) for ff,ll,cc,dd,aa in segs_flcda] )
897 gobject.idle_add(receiver, signals, sig_names, qubx.pyenv.env.globals['QubX'].Data.file.sampling * 1e3, td)
898 self.stimulus.call_with_stimulus_idl(receive_stim, [(seg.f, seg.l) for seg in segsrc])
899
909 - def get(self, receiver):
910 def receive_signals(signals, names, sampling_ms, td_ms):
911 segs, mcls, scls = ProcessSignals(td_ms, signals)
912 gobject.idle_add(receiver, segs, mcls, scls, names, sampling_ms, td_ms)
913 self.signals.request(receive_signals)
914
915
916
917
918
919
920 ALL_EXPONENTIALS_MAX_N = 20
921 ALL_EXPONENTIALS_AREA_CUTOFF = 1e-5
922 ALL_EXPONENTIALS_NEAR_THRESHOLD = 0.02
923 ALL_EXPONENTIALS_FIT_REPS = 5
924 ALL_EXPONENTIALS_FIT_ITERS = 30
925 ALL_EXPONENTIALS_SIGNIFICANCE = 0.05
926 ALL_EXPONENTIALS_FINAL_CUTOFF = 1e-5
929 return log((1.0-.0005)/sig)
930
934
936 """This class implements a method to find all significant exponential components of a duration histogram, published in
937
938 Exponential Sum-Fitting of Dwell-Time Distributions without Specifying Starting Parameters
939 David Landowne,* Bin Yuan, and Karl L. Magleby*
940 Biophysical Journal Volume 104 June 2013 2383-2391
941 """
942 - def __init__(self, expr="0.0", locals={}):
960 - def eval(self, param_vals, xx, vvv=[]):
979
980
982 """Adjusts can_fit, returns initial param values spaced evenly between min(xx) and max(xx) on log axis."""
983 N = ALL_EXPONENTIALS_MAX_N
984 area = 1.0 / N
985 params = [area] * len(self.params)
986 params[0] = float(N)
987 if x0 is None:
988 X0, X1 = xx[0], xx[-1]
989 else:
990 X0, X1 = log10(x0), log10(x1)
991 t0 = X0
992 m = (X1 - X0) / (N - 1)
993 for i in xrange(N):
994 params[2*i+1] = 10**(t0 + i*m)
995 self.can_fit[2*i+1] = False
996 self.can_fit[2*i+2] = True
997 return params
998
999
1016
1018 """Adjusts can_fit, returns updated param values with the smallest expos removed."""
1019 N = int(round(params[0]))
1020 params_out = [0] * len(params)
1021 i_out = 0
1022 for i_in in xrange(N):
1023 if params[2*i_in+2] >= cutoff:
1024 params_out[2*i_out+1], params_out[2*i_out+2] = params[2*i_in+1], params[2*i_in+2]
1025 i_out += 1
1026 else:
1027 self.report('%d. Deleting insignificant component at'%stepno,params[2*i_in+1])
1028 self.report(params)
1029 params_out[0] = float(i_out)
1030 for i in xrange(i_out):
1031 self.can_fit[2*i+1] = True
1032 self.can_fit[2*i+2] = True
1033 for i in xrange(i_out, ALL_EXPONENTIALS_MAX_N):
1034 self.can_fit[2*i+1] = False
1035 self.can_fit[2*i+2] = False
1036 return params_out
1037
1038
1040 """Adjusts can_fit, returns updated, fitted param values with near-identical time constants combined into one expo, and final ssr"""
1041 N = [int(round(params[0]))]
1042 params_out = params[:]
1043
1044 expos = [(params[2*i+1], params[2*i+2]) for i in xrange(N[0])]
1045 expos.sort()
1046 for tau, area in expos:
1047 params_out[2*i+1], params_out[2*i+2] = tau, area
1048
1049 def combine(i):
1050 t1, a1, t2, a2 = params_out[2*i+1:2*i+5]
1051 params_out[2*i+1] = t1*a1/(a1+a2) + t2*a2/(a1+a2)
1052 params_out[2*i+2] = a1+a2
1053 for j in xrange(i+1,N[0]-1):
1054 params_out[2*j+1:2*j+3] = params_out[2*j+3:2*j+5]
1055 N[0] -= 1
1056 params_out[0] = float(N[0])
1057 self.can_fit[2*N[0]+1] = self.can_fit[2*N[0]+2] = False
1058 params_out[2*N[0]+1] = params_out[2*N[0]+2] = 0.0
1059 for i in reversed(xrange(N[0]-1)):
1060 t0 = params_out[2*i+1]
1061 if abs((params_out[2*i+3] - t0) / t0) < ALL_EXPONENTIALS_NEAR_THRESHOLD:
1062 self.report('7. Combining close components at',t0,'and',params_out[2*i+3])
1063 self.report(params)
1064 combine(i)
1065 params_out[:], ssr, iterations, ff = fitter(self, params_out, xx, yy, vvv, ww)
1066 return params_out, ssr
1067
1069 """One at a time, removes each exponential and re-fits; adjusts can_fit, returns reduced params, ssr of trial with max likelihood."""
1070 N = int(round(params[0]))
1071 ssr_params = map_func(all_expo_fit_without, [(xx, yy, vvv, ww, params, self.can_fit, i) for i in xrange(N)])
1072 ssr_without = [ssr for ssr, params_w in ssr_params]
1073 best_i = numpy.argmin(ssr_without)
1074 self.report('8. Removing component at',params[2*best_i+1])
1075 self.report(params)
1076 params_out = ssr_params[best_i][1]
1077
1078 N -= 1
1079 for j in xrange(best_i, N):
1080 params_out[2*j+1:2*j+3] = params_out[2*j+3:2*j+5]
1081 params_out[0] = float(N)
1082 params_out[2*N+1] = params_out[2*N+2] = 0.0
1083 self.can_fit[2*N+1] = self.can_fit[2*N+2] = False
1084 return params_out, ssr_without[best_i]
1085
1087 params_fewer, ssr_fewer = self.get_fit_with_fewer(params, fitter, xx, yy, vvv, ww, map_func)
1088 ll = ll_of_ssr(ssr_fewer, len(xx))
1089 llr = ll0 - ll
1090 return llr, params_fewer, ssr_fewer
1091
1092 - def get_minimal_fit(self, params, fitter, xx, yy, vvv, ww, ssr0, significance):
1093 """Adjusts can_fit, returns fully reduced params, ssr."""
1094 target_llr = llr_of_significance(significance or ALL_EXPONENTIALS_SIGNIFICANCE)
1095 ll0 = ll_of_ssr(ssr0, len(xx))
1096 if params[0] > 1:
1097 params_fewer, ssr = self.get_fit_with_fewer(params, fitter, xx, yy, vvv, ww)
1098 ll = ll_of_ssr(ssr, len(xx))
1099 llr = ll0 - ll
1100 self.llr = llr
1101 if llr <= target_llr:
1102 return self.get_minimal_fit(params_fewer, fitter, xx, yy, vvv, ww, ssr0, significance)
1103 N = int(round(params[0]))
1104 self.can_fit[2*(N-1)+1] = self.can_fit[2*(N-1)+2] = True
1105 return params, ssr0
1106
1107
1108
1110 """Returns params, stripped of brief erroneous components."""
1111 N = int(round(params[0]))
1112 params_out = params[:]
1113 left, right = 10**xx[0], 10**xx[-1]
1114 def fitted_area(i):
1115 tau, area = params[2*i+1], params[2*i+2]
1116 return area * (exp(-left / tau) - exp(-right / tau))
1117 i_out = 0
1118 for i in xrange(N):
1119 if (params[2*i+1] < left) and (fitted_area(i) < ALL_EXPONENTIALS_AREA_CUTOFF):
1120 self.report('10. Deleting brief erroneous component at',params[2*i+1])
1121 self.report(params)
1122 N -= 1
1123 self.can_fit[2*N+1] = self.can_fit[2*N+2] = False
1124 params_out[2*N+1] = params_out[2*N+2] = 0.0
1125 else:
1126 params_out[2*i_out+1:2*i_out+3] = params[2*i+1:2*i+3]
1127 i_out += 1
1128 params_out[0] = float(i_out)
1129 return params_out
1130 - def run_generator(self, xx, yy, params, ww=None, fitter=None, map_func=map, significance=None):
1141 vvv = []
1142 params[:] = self.get_initial_params(xx)
1143 yield 20.0
1144 DurHist = qubx.global_namespace.QubX.Figures.DurHist
1145 if DurHist.cancel_all_exp: return
1146 params[:], ssr, iterations, ff = fit(self, params, xx, yy, vvv, ww)
1147 yield 30.0
1148 if DurHist.cancel_all_exp: return
1149 params[:] = self.get_params_minus_ends(params)
1150 params[:], ssr, iterations, ff = fit(self, params, xx, yy, vvv, ww)
1151 yield 40.0
1152 if DurHist.cancel_all_exp: return
1153 params[:] = self.get_params_minus_negligible(params, ALL_EXPONENTIALS_AREA_CUTOFF, 5)
1154 yield 50.0
1155 if DurHist.cancel_all_exp: return
1156 params[:], ssr, iterations, ff = fit(self, params, xx, yy, vvv, ww)
1157 yield 60.0
1158 if DurHist.cancel_all_exp: return
1159 params[:], ssr = self.get_params_combined(params, fit, xx, yy, vvv, ww, ssr)
1160 yield 70.0
1161 if DurHist.cancel_all_exp: return
1162
1163
1164 target_llr = llr_of_significance(significance or ALL_EXPONENTIALS_SIGNIFICANCE)
1165 ll0 = ll_of_ssr(ssr, len(xx))
1166 N = int(round(params[0]))
1167 while N > 1:
1168 llr, params_fewer, ssr_fewer = self.try_fit_with_fewer(params, fit, xx, yy, vvv, ww, ll0, map_func)
1169 if llr > target_llr:
1170 self.report('...failed, llr =',llr,', keeping component')
1171 break
1172 self.report('...success, llr =',llr)
1173 self.llr = llr
1174 params[:], ssr = params_fewer, ssr_fewer
1175 N = int(round(params[0]))
1176 yield 90 - N
1177 if DurHist.cancel_all_exp: return
1178 self.can_fit[2*(N-1)+1] = self.can_fit[2*(N-1)+2] = True
1179
1180 yield 90.0
1181 if DurHist.cancel_all_exp: return
1182 params[:] = self.get_params_minus_brief_errs(params, xx)
1183 params[:], ssr, iterations, ff = fit(self, params, xx, yy, vvv, ww)
1184 yield 99.0
1185 if ALL_EXPONENTIALS_FINAL_CUTOFF:
1186 params[:] = self.get_params_minus_negligible(params, ALL_EXPONENTIALS_FINAL_CUTOFF, 11)
1187 params[:], ssr, iterations, ff = fit(self, params, xx, yy, vvv, ww)
1188 if DurHist.cancel_all_exp: return
1189 yield 100.0
1190 - def run(self, xx, yy, ww=None, fitter=None, map_func=map, significance=None):
1199
1201 - def __init__(self, expr='1 (sqrt) Exponential, to use: click "Fit all exponentials" in the DurHist panel', locals={}):
1202 qubx.fit.BaseCurve.__init__(self)
1203 self.name = 'AllExponentials'
1204 self.params = []
1205 self.lo = []
1206 self.hi = []
1207 self.can_fit = []
1208 self.param_defaults = []
1209 self.can_resample = False
1210 self.last_fit = []
1211
1212 self.expr = expr
1213 self.N = int(re.search(r"([0-9]+) ", expr).group(1))
1214 self.sqrt = 'sqrt' in expr
1215 Ninv = 1.0 / self.N
1216 for i in xrange(self.N):
1217 self.params.extend(["tau%i"%(i+1), "area%i"%(i+1)])
1218 self.lo.extend([1e-16, 0.0])
1219 self.hi.extend([UNSET_VALUE, UNSET_VALUE])
1220 self.can_fit.extend([True, True])
1221 self.param_defaults.extend([(i+1)*Ninv, Ninv])
1222 - def eval(self, param_vals, xx, vvv=[]):
1223
1224
1225
1226 r = qubx.fit.calc_r(xx)
1227 xp = numpy.power(10.0, xx)
1228 xp /= r
1229 yy_all = numpy.zeros(shape=xx.shape, dtype=xx.dtype)
1230 for i in xrange(self.N):
1231 tau, area = param_vals[2*i:2*i+2]
1232 if tau > 0.0:
1233 amp = area / tau
1234 xc = xp / (- tau)
1235 yy = numpy.exp(xc)
1236 xc *= r
1237 yy -= numpy.exp(xc)
1238 yy *= amp * tau
1239 yy_all += yy
1240 if self.sqrt:
1241 self.last_fit = numpy.sqrt(yy_all)
1242 else:
1243 self.last_fit = yy_all
1244 return self.last_fit
1245 qubx.fit.Curves['AllExponentials'] = AllExponentials_Display
1248 try:
1249
1250 xx, yy, vvv, ww, params, can_fit, i = args
1251 fitter = qubx.fit.LMFitter()
1252 p = params[:]
1253 p[2*i+2] = 0.0
1254 c = AllExponentials()
1255 c.can_fit[:] = can_fit[:]
1256 c.can_fit[2*i+1] = c.can_fit[2*i+2] = False
1257 fit_params, ssr, iterations, ff = fitter(c, p, xx, yy, vvv, ww)
1258 return ssr, fit_params
1259 except:
1260 traceback.print_exc()
1261 return 0.0
1262