Package qubx :: Module hill
[hide private]
[frames] | no frames]

Source Code for Module qubx.hill

  1  """Auto-fit of Hill equation. 
  2   
  3  Copyright 2008-2014 Research Foundation State University of New York  
  4  This file is part of QUB Express.                                           
  5   
  6  QUB Express is free software; you can redistribute it and/or modify           
  7  it under the terms of the GNU General Public License as published by  
  8  the Free Software Foundation, either version 3 of the License, or     
  9  (at your option) any later version.                                   
 10   
 11  QUB Express is distributed in the hope that it will be useful,                
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of        
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
 14  GNU General Public License for more details.                          
 15   
 16  You should have received a copy of the GNU General Public License,    
 17  named LICENSE.txt, in the QUB Express program directory.  If not, see         
 18  <http://www.gnu.org/licenses/>.                                       
 19   
 20  """ 
 21   
 22  from __future__ import with_statement 
 23   
 24  import os 
 25  import sys 
 26  import traceback 
 27  import numpy 
 28  import gobject 
 29  import cairo 
 30  import gtk 
 31  import qubx.GTK 
 32  import qubx.pyenv 
 33  import qubx.fast.data 
 34  import qubx.fit_space 
 35  import qubx.faces 
 36  import qubx.notebook 
 37  import qubx.notebookGTK 
 38  import qubx.settings 
 39  import qubx.task 
 40  import qubx.dataGTK 
 41   
 42  from gtk import gdk, keysyms 
 43  from itertools import izip, count, chain 
 44  from qubx.util_types import * 
 45  from qubx.accept import acceptFloatGreaterThan 
 46  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled 
 47  from qubx.tree import node_data_or_set_def 
 48   
 49  HILL_MAX_RESULTS = 100 
 50   
 51  HILL_MEASURE_PEAK = 0 
 52  HILL_MEASURE_MEAN = 1 
 53  HILL_MEASURE_EQUIL = 2 
 54   
55 -class HillFace(qubx.faces.Face):
56 """Measures, displays, updates the dose-response curve for frontmost data, and fits to the Hill equation. 57 58 @ivar fit: L{qubx.fit_space.FitSpace} 59 @ivar controls: L{qubx.faces.Face} for QubX.About 60 @ivar measure: method for finding response; one of HILL_MEASURE_PEAK, HILL_MEASURE_MEAN, HILL_MEASURE_EQUIL 61 @ivar filter_kHz: if measure == HILL_MEASURE_PEAK, applies this filter to minimize measurement noise effect on the one-point peak 62 @ivar mean_dur: if measure == HILL_MEASURE_MEAN, number of milliseconds of data to average each segment 63 @ivar mean_from: if measure == HILL_MEASURE_MEAN, number of milliseconds to skip at the beginning of each segment 64 """ 65 66 __explore_featured = ['profile', 'fit', 'layNotebook', 'subNotebook', 'nbChart', 'nbPicture', 'nbParams', 'controls', 67 'measure', 'filter_kHz', 'mean_dur', 'mean_from'] 68
69 - def __init__(self, name='Hill', global_name='QubX.Figures.Hill'):
70 qubx.faces.Face.__init__(self, name, global_name) 71 self.__ref = Reffer() 72 cat = qubx.settings.SettingsMgr['Hill'] 73 self.profile = cat.active 74 cat.OnSet = self.__ref(self.__onUpdateProfile) 75 self.__measure = node_data_or_set_def(self.profile, 'Measure', HILL_MEASURE_PEAK) 76 self.__filter_kHz = node_data_or_set_def(self.profile, 'Filter_kHz', 1.0) 77 self.__mean_dur = node_data_or_set_def(self.profile, 'Mean_dur', 10.0) 78 self.__mean_from = node_data_or_set_def(self.profile, 'Mean_from', 500.0) 79 self.__dose = self.__model_Peq = None 80 self.fit = pack_item(qubx.fit_space.FitSpace(label=name, global_name='%s.fit' % global_name), self, expand=True) 81 self.fit.caption = "log10(Ligand) v. fraction open, fitted to the Hill equation" 82 self.fit.controls.robot.OnStats += self.__ref(self.__onStats) 83 self.fit.controls.robot.OnException += self.__ref(self.__onRobotException) 84 self.fit.controls.robot.OnEndFit += self.__ref(self.__onEndFit) 85 self.fit.controls.robot.set_expr('L**Hill / (L**Hill + EC50**Hill)') 86 self.fit.controls.robot.set_data(numpy.arange(10, dtype='float32'), numpy.zeros(shape=(10,), dtype='float32')) 87 self.fit.OnOverlay += self.__ref(self.__onOverlayFit) 88 self.layNotebook = qubx.toolspace.Layer(x=1, y=-5.5-qubx.fit_space.LINE_EMS, w=2, h=2, cBG=qubx.toolspace.COLOR_CLEAR) 89 self.subNotebook = qubx.notebookGTK.SubLayer_Notebook(x=0, y=0, w=2, h=2) 90 self.layNotebook.add_sublayer(self.subNotebook) 91 self.fit.add_layer(self.layNotebook) 92 self.nbChart = qubx.notebook.NbChart('Hill chart', 'QubX.Figures.Hill.nbChart', lambda: 'Hill', 93 self.fit.controls.nb_get_shape, self.fit.controls.nb_get_headers, 94 get_col=self.fit.controls.nb_get_col, get_type=lambda: float, 95 get_series=self.fit.nb_get_series) 96 self.subNotebook.items.append(self.nbChart) 97 self.nbPicture = qubx.notebookGTK.NbPicture('Hill picture', 'QubX.Figures.Hill.nbPicture', self.fit.nb_picture_get_shape, 98 self.fit.nb_picture_draw) 99 self.subNotebook.items.append(self.nbPicture) 100 self.nbParams = qubx.notebook.NbDynText('Parameters', 'QubX.Figures.Hill.nbParams', self.fit.controls.nb_get_param_text) 101 self.nbParams.std_err_est = None 102 self.subNotebook.items.append(self.nbParams) 103 self.__nb_next_stats = False 104 qubx.notebook.Notebook.register_auto('Hill_fit.Parameters', 'Hill Parameters, on Fit') 105 qubx.notebook.Notebook.register_auto('Hill_fit.Results', 'Hill Results, on Fit') 106 qubx.notebook.Notebook.register_auto('Hill_fit.Chart', 'Hill chart, on Fit') 107 qubx.notebook.Notebook.register_auto('Hill_fit.Picture', 'Hill picture, on Fit') 108 109 self.QubX = qubx.pyenv.env.globals['QubX'] 110 self.QubX.OnQuit += self.__ref(lambda: self.fit.dispose()) 111 self.controls = qubx.faces.Face(name, global_name='QubX.About.%s' % name) 112 self.controls.set_size_request(100, 100) 113 self.controls.show() 114 pack_label('Dose-Response:', self.controls) 115 self.lblVs = pack_label('log10(Ligand) v. fraction open', self.controls) 116 pack_label('Measure each level at:', self.controls) 117 line = pack_item(gtk.HBox(), self.controls) 118 self.chkPeak = pack_radio('Peak filtered at ', line, active=False, on_toggle=bind_with_args_before(self.__onToggleMeasure, HILL_MEASURE_PEAK)) 119 self.txtFilter = pack_item(qubx.GTK.NumEntry(self.__filter_kHz, acceptFloatGreaterThan(0.0), width_chars=4), line) 120 self.txtFilter.OnChange += self.__ref(self.__onChangeFilter) 121 pack_label('kHz', line) 122 line = pack_item(gtk.HBox(), self.controls) 123 self.chkMean = pack_radio('Mean of ', line, group=self.chkPeak, active=False, on_toggle=bind_with_args_before(self.__onToggleMeasure, HILL_MEASURE_MEAN)) 124 self.txtMeanDur = pack_item(qubx.GTK.NumEntry(self.__mean_dur, acceptFloatGreaterThan(0.0), width_chars=5), line) 125 self.txtMeanDur.OnChange += self.__ref(self.__onChangeMeanDur) 126 pack_label('ms', line) 127 line = pack_item(gtk.HBox(), self.controls) 128 pack_label('ms', line, at_end=True) 129 self.txtMeanFrom = pack_item(qubx.GTK.NumEntry(self.__mean_from, acceptFloatGreaterThan(0.0), width_chars=5), line, at_end=True) 130 self.txtMeanFrom.OnChange += self.__ref(self.__onChangeMeanFrom) 131 pack_label('starting at ', line, at_end=True) 132 self.chkEquil = pack_radio('Equilibrium', self.controls, group=self.chkPeak, active=False, on_toggle=bind_with_args_before(self.__onToggleMeasure, HILL_MEASURE_EQUIL)) 133 pack_label('The blue trace is occupancy prob.', pack_item(gtk.HBox(), self.controls)) 134 pack_label('of class 1 (red) calculated from', pack_item(gtk.HBox(), self.controls)) 135 pack_label('the model.', pack_item(gtk.HBox(), self.controls)) 136 self.datasrc = self.QubX.DataSource 137 self.datasrc.OnChangeSamples += self.__ref(self.__onChangeSamples) 138 self.models = ModelTracker() 139 for evt in (self.models.OnSwitch, self.models.OnChange, self.models.OnSetRate): 140 evt += self.__ref(bind(self.__onChangeModel)) 141 gobject.idle_add(self.__onChangeSamples) 142 self.__serial_data = 0 143 self.set_measure(self.__measure)
144 - def set_measure(self, x):
145 self.__measure = x 146 if self.__measure == HILL_MEASURE_PEAK: 147 self.chkPeak.set_active(True) 148 elif self.__measure == HILL_MEASURE_MEAN: 149 self.chkMean.set_active(True) 150 elif self.__measure == HILL_MEASURE_EQUIL: 151 self.chkEquil.set_active(True) 152 self.profile['Measure'].data = x 153 self.__onChangeSamples()
154 measure = property(lambda self: self.__measure, lambda self, x: self.set_measure(x))
155 - def set_filter_kHz(self, x):
156 self.__filter_kHz = x 157 self.txtFilter.value = x 158 self.profile['Filter_kHz'].data = x 159 self.__onChangeSamples()
160 filter_kHz = property(lambda self: self.__filter_kHz, lambda self, x: self.set_filter_kHz(x))
161 - def set_mean_dur(self, x):
162 self.__mean_dur = x 163 self.txtMeanDur.value = x 164 self.profile['Mean_dur'].data = x 165 self.__onChangeSamples()
166 mean_dur = property(lambda self: self.__mean_dur, lambda self, x: self.set_mean_dur(x))
167 - def set_mean_from(self, x):
168 self.__mean_from = x 169 self.txtMeanFrom.value = x 170 self.profile['Mean_from'].data = x 171 self.__onChangeSamples()
172 mean_from = property(lambda self: self.__mean_from, lambda self, x: self.set_mean_from(x))
173 - def __onToggleMeasure(self, chk, val):
174 if chk.get_active(): 175 qubx.pyenv.env.OnScriptable('%s.measure = %s' % (self.global_name, repr(val))) 176 self.measure = val
177 - def __onChangeFilter(self, txt, val):
178 qubx.pyenv.env.OnScriptable('%s.filter_kHz = %s' % (self.global_name, repr(val))) 179 self.filter_kHz = val
180 - def __onChangeMeanDur(self, txt, val):
181 qubx.pyenv.env.OnScriptable('%s.mean_dur = %s' % (self.global_name, repr(val))) 182 self.mean_dur = val
183 - def __onChangeMeanFrom(self, txt, val):
184 qubx.pyenv.env.OnScriptable('%s.mean_from = %s' % (self.global_name, repr(val))) 185 self.mean_from = val
186 - def __onUpdateProfile(self, settings, updates):
187 if updates.find('Measure').data: 188 self.measure = updates['Measure'].data[0] 189 if updates.find('Filter_kHz').data: 190 self.filter_kHz = updates['Filter_kHz'].data[0] 191 if updates.find('Mean_dur').data: 192 self.mean_dur = updates['Mean_dur'].data[0] 193 if updates.find('Mean_from').data: 194 self.mean_from = updates['Mean_from'].data[0]
195
196 - def onShow(self, showing):
197 if showing: 198 self.QubX.About.append_face(self.controls) 199 self.QubX.About.show_face(self.face_name) 200 self.__onChangeSamples() 201 else: 202 self.QubX.About.remove_face( self.QubX.About.index(self.face_name) )
203 - def __onChangeModel(self):
204 if self.__dose is None: 205 return 206 from_mdl = qubx.modelGTK.calc_Pe_of_stim(self.models.view, {self.__dose_name : self.__dose}, 1) 207 peq = [fm['Peq'] for fm in from_mdl] 208 peq -= numpy.min(peq) 209 max_peq = numpy.max(peq) 210 peq /= max_peq 211 self.__model_Peq = peq 212 self.fit.redraw_canvas(False)
213 - def __onOverlayFit(self, context, w, h):
214 if not (self.__model_Peq is None): 215 self.fit.draw_extra_series(context, w, h, self.__model_Peq)
216 - def __onRobotException(self, robot, typ, val, trace):
217 traceback.print_exception(typ, val, trace)
218 - def __onEndFit(self):
219 self.__nb_next_stats = True
220 - def __onStats(self, correlation, is_pseudo, std_err_est, ssr, r2, runs_prob):
221 if self.showing and (self.__fit_fails < 2) and (numpy.isinf(r2) or numpy.isnan(r2) or (r2 < 0)): 222 self.__fit_fails += 1 223 self.fit.controls.robot.set_param('Hill', 1.0) 224 self.fit.controls.robot.set_param('EC50', 1.0) 225 self.fit.controls.robot.fit() 226 elif self.__nb_next_stats: 227 self.__nb_next_stats = False 228 qubx.notebook.Notebook.send(qubx.notebook.NbText("""Hill fit finished. 229 \tSSR: %(ssr).6g 230 \tR^2: %(r2).6g 231 \tWald-Wolfowitz runs probability: %(runs_prob).6g 232 \tCorrelation: 233 %(correlation)s 234 """ % locals()), auto_id='Hill_fit.Results') 235 self.nbParams.std_err_est = std_err_est 236 qubx.notebook.Notebook.send(self.nbParams, auto_id='Hill_fit.Parameters') 237 self.nbParams.std_err_est = None 238 qubx.notebook.Notebook.send(self.nbChart, auto_id='Hill_fit.Chart') 239 qubx.notebook.Notebook.send(self.nbPicture, auto_id='Hill_fit.Picture')
240 - def __onChangeSamples(self, *args):
241 self.on_change_data()
242 - def on_change_data(self):
243 if (not self.QubX.Data.view) or (not self.showing): return 244 self.__fit_fails = 0 245 self.__serial_data += 1 246 self.fit.controls.robot.do(self.robot_read_data, self.__serial_data)
247 - def robot_read_data(self, serial):
248 if serial < self.__serial_data: return 249 with self.fit.controls.robot.main_hold: 250 rates = self.models.model.rates 251 ligands = [rates.get(i, 'Ligand') for i in xrange(rates.size)] 252 volts = [rates.get(i, 'Voltage') for i in xrange(rates.size)] 253 names = [nm for nm in chain(ligands, volts) if nm] 254 data = self.QubX.Data.view 255 series = [data.file.signals.get(i, 'Name') for i in xrange(data.file.signals.size)] 256 self.stim_ix = None 257 self.is_log = True 258 for i,nm in enumerate(series): 259 if nm in ligands: 260 self.stim_ix = i 261 self.is_log = True 262 break 263 elif nm in volts: 264 self.stim_ix = i 265 self.is_log = False 266 break 267 if not (self.stim_ix is None): 268 gobject.idle_add(self.lblVs.set_label, '%s v. fraction open' % ((self.is_log and 'log10(%s)' or '%s') % series[self.stim_ix])) 269 gobject.idle_add(self.fit.set_caption, '%s v. fraction open, fitted to the Hill equation' % ((self.is_log and 'log10(%s)' or '%s') % series[self.stim_ix])) 270 measure = self.__measure 271 filter_kHz = self.__filter_kHz 272 segments = self.datasrc.get_segmentation() 273 Nseg = len(segments) 274 Ntail = 1 275 if segments: 276 if self.measure == HILL_MEASURE_MEAN: 277 Ntail = segments[0].n 278 n_sub = int(round(self.mean_dur * 1e-3 / data.file.sampling)) 279 off_sub = int(round(self.mean_from * 1e-3 / data.file.sampling)) 280 segments = [data.get_segmentation_indexed(seg.f + off_sub, seg.f + off_sub + n_sub - 1, signal=seg.signal)[0] 281 for seg in segments] 282 else: 283 Ntail = min(100, segments[0].n / 5) 284 sign = 1 285 if self.models.model: 286 classes = self.models.model.classes 287 sign = classes.get(1, 'Amp') - classes.get(0, 'Amp') 288 if (self.stim_ix is None) or (not Nseg): 289 self.__dose = None 290 return 291 if measure == HILL_MEASURE_PEAK: 292 dr = qubx.fast.data.DoseResponse_Peak(segments[0].sampling, filter_kHz, sign) 293 else: 294 dr = qubx.fast.data.DoseResponse_Equil(Ntail) 295 for seg in reversed(segments): 296 for dose, response in izip(self.datasrc.gen_samples(reversed(seg.chunks), self.fit.controls.robot.main_hold, self.stim_ix), 297 self.datasrc.gen_samples(reversed(seg.chunks), self.fit.controls.robot.main_hold)): 298 if dose.included: 299 dr.add_data(dose.samples, response.samples) 300 if measure == HILL_MEASURE_PEAK: 301 dose, response = dr.result(HILL_MAX_RESULTS) 302 else: 303 dose, response, resp_std = dr.result(HILL_MAX_RESULTS) 304 small_doses = dose[dose>0.0] 305 zero_ish = min(small_doses) / numpy.e if len(small_doses) else 1e-8 306 if self.is_log: 307 logdose = numpy.log10(dose) 308 logdose[dose <= 0.0] = numpy.log10(zero_ish) 309 else: 310 logdose = dose 311 if sign > 0: 312 response -= numpy.min(response) 313 max_resp = numpy.max(response) 314 response /= max_resp 315 else: 316 response -= numpy.max(response) 317 max_resp = numpy.min(response) 318 response /= max_resp 319 if measure != HILL_MEASURE_PEAK: 320 resp_std /= max_resp 321 self.fit.controls.robot.set_data(logdose, response, [response, dose], ['I', 'L']) 322 self.fit.controls.robot.set_param('EC50', numpy.mean(dose)) 323 self.__dose = dose 324 self.__dose_name = series[self.stim_ix] 325 gobject.idle_add(self.__onChangeModel) 326 gobject.idle_add(self.fit.controls.robot.fit)
327 328
329 -class ModelTracker(object):
330 """Follows the frontmost L{qubx.model.QubModel} and L{qubx.modelGTK.ModelView}, 331 with events when anything changes. 332 333 @ivar OnSwitch: L{WeakEvent}(QubX.Models, model) when a new file is brought to front 334 @ivar OnChange: L{WeakEvent}(model) when something in the model changes (except a rate constant) 335 @ivar OnSetRate: L{WeakEvent}(rate_index, field_name, value, prev, is_undoing) 336 """
337 - def __init__(self):
338 self.__ref = Reffer() 339 QubX = qubx.pyenv.env.globals['QubX'] 340 self.Models = QubX.Models 341 self.Models.OnSwitch += self.__ref(self.__onSwitchModel) 342 self.__model = self.__view = None 343 self.OnSwitch = WeakEvent() 344 self.OnChange = WeakEvent() 345 self.OnSetRate = WeakEvent() 346 self.__onSwitchModel(self.Models, self.Models.file)
347 - def __onSwitchModel(self, models, model):
348 if model == self.__model: return 349 if self.__model: 350 for event in (self.__model.OnSetChannelCount, 351 self.__model.OnSetIeqFv, 352 self.__model.OnSetVRev, 353 self.__model.states.OnSet, 354 self.__model.states.OnInsert, 355 self.__model.states.OnRemoved, 356 self.__model.classes.OnSet, 357 self.__model.rates.OnInsert, 358 self.__model.rates.OnRemoved, 359 self.__model.constraints_kin.OnEdit): 360 event -= self.__ref(self.__onChangeModel) 361 self.__model.rates.OnSet -= self.__ref(self.__onSetRate) 362 self.__model = model 363 self.__view = models.view 364 if self.__model: 365 for event in (self.__model.OnSetChannelCount, 366 self.__model.OnSetIeqFv, 367 self.__model.OnSetVRev, 368 self.__model.states.OnSet, 369 self.__model.states.OnInsert, 370 self.__model.states.OnRemoved, 371 self.__model.classes.OnSet, 372 self.__model.rates.OnInsert, 373 self.__model.rates.OnRemoved, 374 self.__model.constraints_kin.OnEdit): 375 event += self.__ref(self.__onChangeModel) 376 self.__model.rates.OnSet += self.__ref(self.__onSetRate) 377 self.OnSwitch(model)
378 model = property(lambda self: self.__model) 379 view = property(lambda self: self.__view)
380 - def __onChangeModel(self, *args):
381 self.OnChange(self.__model)
382 - def __onSetRate(self, r, field, val, prev, undoing):
383 self.OnSetRate(r, field, val, prev, undoing)
384