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

Source Code for Module qubx.modelGTK

   1  """Panel which shows an open qub model. 
   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  import qubx.accept 
  23  import qubx.data_types 
  24  import qubx.dataGTK 
  25  import qubx.fit 
  26  import qubx.treeGTK 
  27  import qubx.pyenv 
  28  import numpy 
  29  import scipy 
  30  import scipy.optimize 
  31   
  32  from qubx.toolspace import * 
  33  from qubx.GTK import * 
  34  from qubx.util_types import * 
  35  from qubx.model import * 
  36  import qubopt.model 
  37  from math import * 
  38  import gc 
  39  import qubx.settings 
  40  from qubx.settings import Property, Propertied 
  41  expm = scipy.linalg.matfuncs.expm 
  42   
  43  any = __builtins__['any'] 
  44   
  45  Tools = ToolRegistry() 
  46   
  47   
  48  COLOR_STATE_BORDER = ('modelGTK.state.border', (0, 0, 0, 1)) 
  49  ColorInfo[COLOR_STATE_BORDER[0]].label = 'Model state border' 
  50  COLOR_STATE_LABEL = ('modelGTK.state.label', (1, 1, 1, 1)) 
  51  ColorInfo[COLOR_STATE_LABEL[0]].label = 'Model state label' 
  52  COLOR_RATE = ('modelGTK.rate', (0, 0, 0, 1)) 
  53  ColorInfo[COLOR_RATE[0]].label = 'Model rate label' 
  54  COLOR_EFFECTIVE_RATE = ('modelGTK.rate.effective', (.5,0,.5,1)) 
  55  ColorInfo[COLOR_EFFECTIVE_RATE[0]].label = 'Model rate label (effective K)' 
  56  COLOR_BG = ('modelGTK.bg', (1, 1, .75, 1)) 
  57  ColorInfo[COLOR_BG[0]].label = 'Model background' 
  58   
  59  COLOR_CHANCOUNT = ('modelGTK.channelCount', (1,1,1,1)) 
  60  ColorInfo[COLOR_CHANCOUNT[0]].label = 'Model channel count' 
  61  COLOR_BUTTON = ('modelGTK.button', (0,0,0,.85)) 
  62  ColorInfo[COLOR_BUTTON[0]].label = 'Model layer background' 
  63  COLOR_BUTTON_TEXT = ('modelGTK.button.text', LAYER_FG[1]) 
  64  ColorInfo[COLOR_BUTTON_TEXT[0]].label = 'Model layer text' 
  65  COLOR_BUTTON_GRAY = ('modelGTK.button.gray', (1,1,1,.4)) 
  66  ColorInfo[COLOR_BUTTON_GRAY[0]].label = 'Model layer text inactive' 
  67  COLOR_BUTTON_HOVER = ('modelGTK.button.hover', (1, .9, .9, 1)) 
  68  ColorInfo[COLOR_BUTTON_HOVER[0]].label = 'Model button text mouseover' 
  69  COLOR_QUESTION_BORDER = ('modelGTK.button.border', (1,0,0,1)) 
  70  ColorInfo[COLOR_QUESTION_BORDER[0]].label = 'Model hint "?" border' 
  71   
  72  CLICK_SLOP = 2 
  73   
  74  MENU_TIMEOUT = 360 # ms 
  75  MENU_STRAYDIUS = 2 # pixels 
  76   
  77  IGNORE_MENU_RELEASE = 5 
78 79 80 -def distance(x1, y1, x2, y2):
81 """Returns the Euclidean distance between (x1, y1) and (x2, y2).""" 82 return sqrt((x2 - x1)**2 + (y2 - y1)**2)
83
84 -def default_static(x):
85 """Returns a function f; every time you call f() it returns x.""" 86 return lambda: x
87
88 89 -class QubModelView(ToolSpace):
90 """Displays and edits one L{qubx.model.QubModel}. 91 92 @ivar deftool: the standard mouse controller 93 """ 94 95 __explore_featured = ['controls', 'models_table', 'cBG', 'layExtMenu', 'mnuExt', 'subExtMenu', 'subNotebook', 'nbPicture', 'nbPictureNO', 96 'nbQ', 'nbA', 'nbPeq', 'nbKeq', 'nbDelG', 'nbMFP', 'nbQflux', 'layIeqFv', 'chkIeqFv', 'layVrev', 'lblVrev', 97 'layChannelCount', 'lblChannelCount', 'layRedo', 'lblRedo', 'layUndo', 'lblUndo', 'deftool', 'dispose', 98 'file', 'get_stimulus', 'do_scroll_channelCount', 'copy_image', 'copy_tree', 'paste_tree', 99 'find_state', 'find_rate', 'add_state', 'del_state', 'add_rate', 'del_rate', 'make_start_state', 100 'set_state_color', 'add_constraint_kin'] 101
102 - def __init__(self, models_table=None, menu_width=32):
103 """@param models_table: the L{qub.model.QubModels} table-like object.""" 104 ToolSpace.__init__(self, can_focus=True) 105 self.controls = self.layerset = LayerSet() # for compatibility with FilesFace; ideally all/most layers go here 106 self.models_table = models_table 107 self.set_size_request(200, 70) 108 self.ref = Reffer() 109 self.OnDraw += self.ref(self._onDrawModel), 'ModelView.onDrawModel' 110 self._file = None 111 self._stateRects = [] 112 self._rateRectss = [] 113 self._rateRegions = [] 114 self.cBG = COLOR_BG 115 self.__data_stimulus = None 116 self.optimizing = False 117 118 self.layExtMenu = Layer(menu_width+4.5, -4, 3, 3, COLOR_CLEAR) 119 self.add_layer(self.layExtMenu) 120 self.mnuExt = gtk.Menu() 121 self.subExtMenu = SubLayer_MenuLines(self.mnuExt, 'Modeling Tools', self.ref(self.__onPopupExt), x=.25, y=.25, w=2.5, h=2.5) 122 self.layExtMenu.add_sublayer(self.subExtMenu) 123 self.layNotebook = Layer(menu_width+7.5, -4, 3, 3, COLOR_CLEAR) 124 self.add_layer(self.layNotebook) 125 self.subNotebook = qubx.notebookGTK.SubLayer_Notebook(x=.25, y=.25, w=2.5, h=2.5) 126 self.layNotebook.add_sublayer(self.subNotebook) 127 self.nbPicture = qubx.notebookGTK.NbPicture(mnu_caption='Model image', global_name='QubX.Models.view.nbPicture', get_shape=self.__nb_get_shape, draw=self.__nb_draw) 128 self.subNotebook.items.append(self.nbPicture) 129 self.nbPictureNO = qubx.notebookGTK.NbPicture(mnu_caption='Model image, no overlays', global_name='QubX.Models.view.nbPictureNO', get_shape=self.__nb_get_shape, draw=self.__nb_draw_no_overlays) 130 self.subNotebook.items.append(self.nbPictureNO) 131 self.nbQ = qubx.notebook.NbTable('Q matrix', 'QubX.Models.view.nbQ', lambda: 'Q matrix', 132 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Q'), 133 self.__nb_get_row_Q, get_type=lambda: float) 134 self.subNotebook.items.append(self.nbQ) 135 self.nbA = qubx.notebook.NbTable('A matrix', 'QubX.Models.view.nbA', lambda: 'A matrix', 136 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('A'), 137 self.__nb_get_row_A, get_type=lambda: float) 138 self.subNotebook.items.append(self.nbA) 139 self.nbPeq = qubx.notebook.NbTable('Peq', 'QubX.Models.view.nbPeq', lambda: 'Peq', 140 self.__nb_get_vector_shape, lambda: self.__nb_get_matrix_headers('Peq'), 141 self.__nb_get_col_Peq, get_type=lambda: float) 142 self.subNotebook.items.append(self.nbPeq) 143 self.nbKeq = qubx.notebook.NbTable('Eq constants', 'QubX.Models.view.nbKeq', lambda: 'Eq constants', 144 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Eq constants'), 145 self.__nb_get_row_Eq_constants, get_type=lambda: float) 146 self.subNotebook.items.append(self.nbKeq) 147 self.nbDelG = qubx.notebook.NbTable('Delta free energy', 'QubX.Models.view.nbDelG', lambda: 'Delta free energy (log10)', 148 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Delta free energy (log10)'), 149 self.__nb_get_row_dfe, get_type=lambda: float) 150 self.subNotebook.items.append(self.nbDelG) 151 self.nbMFP = qubx.notebook.NbTable('Mean first passage', 'QubX.Models.view.nbMFP', lambda: 'Mean first passage', 152 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Mean first passage'), 153 self.__nb_get_row_mfp, get_type=lambda: float) 154 self.subNotebook.items.append(self.nbMFP) 155 self.nbQflux = qubx.notebook.NbTable('Qflux', 'QubX.Models.view.nbQflux', lambda: 'Qflux', 156 self.__nb_get_matrix_shape, lambda: self.__nb_get_matrix_headers('Qflux'), 157 self.__nb_get_row_Qflux, get_type=lambda: float) 158 self.subNotebook.items.append(self.nbQflux) 159 self.subNotebook.menuitems.append(build_menuitem('Picture options...', self.ref(lambda item: qubx.global_namespace.QubX.Models.nbPicturePrefs.run()))) 160 161 self.layIeqFv = Layer(-16, 1, 15, 2, COLOR_BUTTON) 162 self.chkIeqFv = SubLayer_Check(x=1, y=.5, w=1, h=1) 163 self.chkIeqFv.OnToggle += self.ref(self._onToggleIeqFv) 164 self.layIeqFv.add_sublayer(self.chkIeqFv) 165 self.layIeqFv.add_sublayer(SubLayer_Label('I = f(V)', 0, 1, action=self.ref(self._onClickIeqFv), x=2, y=0, w=7, h=2)) 166 self.layIeqFv.add_sublayer(SubLayer_Label('?', 0, 1, action=self.ref(self._onClickAboutIeqFv), x=12.5, y=.25, w=1.5, h=1.5, 167 border=1, cBorder=COLOR_QUESTION_BORDER, color=COLOR_BUTTON_TEXT, hover_color=COLOR_BUTTON_HOVER)) 168 self.add_layer(self.layIeqFv) 169 self.layVrev = Layer(-16, 3, 15, 2, COLOR_BUTTON) 170 self.layVrev.add_sublayer(SubLayer_Label('Vrev [mV]:', -1, 1, action=self.ref(self._onClickVrev), x=2.5, y=.5, w=6.5, h=1)) 171 self.lblVrev = SubLayer_Label('0.0', 0, 1, color=COLOR_CHANCOUNT, action=self.ref(self._onClickVrev), x=9, y=.5, w=6, h=1) 172 self.layVrev.add_sublayer(self.lblVrev) 173 self._showing_vRev = False # not adding until IeqFv==True 174 self.layChannelCount = Layer(-20, -4, 19, 3, COLOR_BUTTON) 175 self.layChannelCount.add_sublayer(SubLayer_Label('Channel Count: ', 1, 1, action=self.ref(self._onClickChannelCount), 176 x=2, y=.5, w=11, h=2, scroll=self.ref(self.__onScrollChannelCount))) 177 self.lblChannelCount = SubLayer_Label('1', 0, 1, color=COLOR_CHANCOUNT, action=self.ref(self._onClickChannelCount), 178 x=13, y=.5, w=5, h=2, scroll=self.ref(self.__onScrollChannelCount)) 179 self.layChannelCount.add_sublayer(self.lblChannelCount) 180 self.add_layer(self.layChannelCount) 181 182 self.layRedo = Layer(-8, -8, 7, 3, COLOR_BUTTON) 183 self.lblRedo = SubLayer_Label('Redo', 0, 1, color=COLOR_BUTTON_TEXT, hover_color=COLOR_BUTTON_HOVER, action=self.ref(self._onClickRedo), 184 x=1, y=.5, w=5, h=2) 185 self.layRedo.add_sublayer(self.lblRedo) 186 self.add_layer(self.layRedo) 187 188 self.layUndo = Layer(-8, -12, 7, 3, COLOR_BUTTON) 189 self.lblUndo = SubLayer_Label('Undo', 0, 1, color=COLOR_BUTTON_TEXT, hover_color=COLOR_BUTTON_HOVER, action=self.ref(self._onClickUndo), 190 x=1, y=.5, w=5, h=2) 191 self.layUndo.add_sublayer(self.lblUndo) 192 self.add_layer(self.layUndo) 193 194 self.defer_scriptable_scroll = qubx.pyenv.DeferredScriptableScroll() 195 196 self.deftool = self.tool = QubModelDefaultTool() 197 198 self.chkBalanceLoops = gtk.CheckButton('') 199 self.chkBalanceLoops.set_tooltip_text("Maintains detailed balance on all cycles") 200 self.chkBalanceLoops.connect('toggled', self.__onToggleBalanceLoops) 201 202 try: 203 qubx.global_namespace.QubX.DataSource.OnChangeSel += self.ref(self._onChangeSel) 204 except: 205 pass
206 - def dispose(self):
207 self.file = None 208 self.ref.clear()
209 210 file = property(lambda self: self._file, lambda self, x: self.set_file(x))
211 - def set_file(self, x):
212 if self._file == x: return 213 tool = self.tool 214 self.tool = None 215 if self._file: 216 # unsbuscribe events 217 self._file.OnSetChannelCount -= self.ref(self._onSetChannelCount) 218 self._file.OnSetIeqFv -= self.ref(self._onSetIeqFv) 219 self._file.OnSetVRev -= self.ref(self._onSetVRev) 220 self._file.OnSetK0Format -= self.ref(self._onChange) 221 self._file.OnSetK1Format -= self.ref(self._onChange) 222 self._file.undoStack.OnChange -= self.ref(self._onUndoChange) 223 self._file.states.OnSet -= self.ref(self._onSetState) 224 self._file.rates.OnSet -= self.ref(self._onSetRate) 225 self._file.classes.OnSet -= self.ref(self._onChange) 226 self._file.states.OnInsert -= self.ref(self._onChange) 227 self._file.rates.OnAfterInsert -= self.ref(self._onInsertedRate) 228 self._file.classes.OnInsert -= self.ref(self._onChange) 229 self._file.states.OnRemoving -= self.ref(self._onChange) 230 self._file.rates.OnRemoving -= self.ref(self._onRemovingRate) 231 self._file.classes.OnRemoving -= self.ref(self._onChange) 232 self._file = x 233 if self._file: 234 if not ('Peq' in self._file.states.fields): 235 self._file.states.add_field('Peq', 0.0, acceptNothing, '%.3f', '') 236 self._file.states.add_field('TimeConstant', 0.0, acceptNothing, '%.3f', 'ms') 237 # subscribe events 238 self._file.OnSetChannelCount += self.ref(self._onSetChannelCount), 'ModelsView.onSetChannelCount' 239 self._file.OnSetIeqFv += self.ref(self._onSetIeqFv) 240 self._file.OnSetVRev += self.ref(self._onSetVRev) 241 self._file.OnSetK0Format += self.ref(self._onChange) 242 self._file.OnSetK1Format += self.ref(self._onChange) 243 self._file.undoStack.OnChange += self.ref(self._onUndoChange), 'ModelsView.onUndoChange' 244 self._file.states.OnSet += self.ref(self._onSetState), 'ModelView.onChange [states]' 245 self._file.rates.OnSet += self.ref(self._onSetRate) 246 self._file.classes.OnSet += self.ref(self._onChange), 'ModelView.onChange [classes]' 247 self._file.states.OnInsert += self.ref(self._onChange), 'ModelView.onChange [states insert]' 248 self._file.rates.OnAfterInsert += self.ref(self._onInsertedRate) 249 self._file.classes.OnInsert += self.ref(self._onChange), 'ModelView.onChange [classes insert]' 250 self._file.states.OnRemoving += self.ref(self._onChange), 'ModelView.onChange [states del]' 251 self._file.rates.OnRemoving += self.ref(self._onRemovingRate), 'ModelView.onChange [rates del]' 252 self._file.classes.OnRemoving += self.ref(self._onChange), 'ModelView.onChange [classes del]' 253 self._onSetChannelCount(self.file, self.file.channelCount) 254 self._onSetIeqFv(self.file, self.file.IeqFv) 255 self._onSetVRev(self.file, self.file.vRev) 256 self._onUndoChange(self.file.undoStack) 257 for i in xrange(self._file.rates.size): 258 self.__compute_k(i) 259 self.chkBalanceLoops.set_active(self.file.balance_loops) 260 self.tool = tool 261 self.redraw_canvas()
262 - def get_stimulus(self, dataview=None):
263 """Returns a dict {name : value} of all model-var values at the dataview crosshair.""" 264 stimulus = self.file.get_stimulus() # dict{name : default} 265 if dataview: 266 f = dataview.get_center_sample() 267 for nm in stimulus.keys(): 268 stimulus[nm] = dataview.file.get_stimulus_at(nm, f) 269 return stimulus
270 - def _onChange(self, *args):
271 self.redraw_canvas()
272 - def _onSetState(self, i, field, val, prev, undoing):
273 if field in ('x', 'y', 'Label', 'Class'): 274 self._onChange()
275 - def _onSetRate(self, i, field, val, prev, undoing):
276 if not self.optimizing: 277 if field in ('k0', 'k1', 'k2', 'Ligand', 'Voltage', 'Pressure'): 278 self.__compute_k(i) 279 self._onChange() 280 self.__update_states()
281 - def _onInsertedRate(self, i, undoing):
282 self.__compute_k(i) 283 self._onChange() 284 if (i % 2): 285 self.__update_states()
286 - def _onRemovingRate(self, i, undoing):
287 if not (i % 2): 288 self.__update_states()
289 - def __update_states(self):
290 stimulus = self.__data_stimulus 291 states = self._file.states 292 Q = qubx.model.ModelToQ(self._file, stimulus) 293 Peq = qubx.model.QtoPe(Q) 294 ksum = [0.0] * states.size 295 for rate in self._file.rates: 296 ksum[rate.From] += Q[rate.From,rate.To] 297 for i in xrange(states.size): 298 states[i, 'Peq'] = Peq[i] 299 states[i, 'TimeConstant'] = ksum[i] and (1e3 / ksum[i])
300 - def _onChangeSel(self):
301 self.__data_stimulus = self.get_stimulus(qubx.global_namespace.QubX.Data.view) 302 for i in xrange(len(self._file.rates)): 303 self.__compute_k(i) 304 if self.__data_stimulus: 305 self.__update_states()
306 - def __compute_k(self, r):
307 stimulus = self.__data_stimulus 308 nstate = self._file.states.size 309 rr = self._file.rates 310 rate = rr.get(r, 'k0') 311 if stimulus: 312 lig, volt, press = rr.get(r, 'Ligand'), rr.get(r, 'Voltage'), rr.get(r, 'Pressure') 313 if lig and stimulus.has_key(lig): 314 rate *= stimulus[lig] 315 if volt and stimulus.has_key(volt): 316 rate *= exp(stimulus[volt] * rr.get(r, 'k1')) 317 if press and stimulus.has_key(press): 318 rate *= exp(stimulus[press] * rr.get(r, 'k2')) 319 if not rate: 320 rate = MIN_TOTAL_RATE 321 rr[r, 'K'] = rate
322 - def _onSetChannelCount(self, file, val):
323 if file == self.file: 324 self.lblChannelCount.label = str(val)
325 - def _onClickChannelCount(self, x, y, e):
326 if self.models_table: 327 self.models_table.select(self.models_table.entries.index(self.file), 'Channel Count', self)
328 - def __onScrollChannelCount(self, x, y, e, offset):
329 self.do_scroll_channelCount(offset, e.state & gdk.CONTROL_MASK, e.state & gdk.SHIFT_MASK)
330 - def do_scroll_channelCount(self, offset, fine=False, coarse=False):
331 self.defer_scriptable_scroll('cc', 332 'QubX.Models.file.channelCount = scrolled_int(QubX.Models.file.channelCount, %s, lo=1)', 333 offset, fine, coarse, undoStack=self.file.undoStack) 334 self.file.set_channelCount(scrolled_int(self.file.channelCount, offset, fine, coarse, lo=1), seal=False)
335 - def _onSetIeqFv(self, file, val):
336 if file == self.file: 337 self.chkIeqFv.active = val 338 if val and not self._showing_vRev: 339 self.add_layer(self.layVrev) 340 self._showing_vRev = True 341 elif self._showing_vRev and not val: 342 self.remove_layer(self.layVrev) 343 self._showing_vRev = False
344 - def _onClickIeqFv(self, x, y, e):
345 if self.file: 346 self.file.IeqFv = not self.file.IeqFv
347 - def _onToggleIeqFv(self, chk, active):
348 if self.file: 349 self.file.IeqFv = active 350 qubx.pyenv.env.OnScriptable('QubX.Models.file.IeqFv = %s' % repr(active))
351 - def _onClickAboutIeqFv(self, x, y, e):
352 TheIeqFvAboutBox.present()
353 - def _onSetVRev(self, file, val):
354 if file == self.file: 355 self.lblVrev.label = '%.2g' % val
356 - def _onClickVrev(self, x, y, e):
357 if self.models_table: 358 self.models_table.select(self.models_table.entries.index(self.file), 'vRev', self)
359 - def _onUndoChange(self, undoStack):
363 - def _onClickUndo(self, x, y, e):
364 if self.file: 365 if e.state & gdk.SHIFT_MASK: # hold shift for fine-grained undo 366 qubx.pyenv.env.OnScriptable('QubX.Models.file.undoStack.undo(single_item=True)') 367 self.file.undoStack.undo(True) 368 else: 369 qubx.pyenv.env.OnScriptable('QubX.Models.file.undoStack.undo()') 370 self.file.undoStack.undo() 371 self.tool = self.deftool
372 - def _onClickRedo(self, x, y, e):
373 self.file.undoStack.redo() 374 self.tool = self.deftool 375 qubx.pyenv.env.OnScriptable('QubX.Models.file.undoStack.redo()')
376 - def copy_image(self, width, height, zoom, copy_overlays=True): # retained in case of old scripts
377 pixmap = gdk.Pixmap(self.window, width, height, -1) 378 ctx = pixmap.cairo_create() 379 ctx.scale(zoom, zoom) 380 zw, zh = int(round(width/zoom)), int(round(height/zoom)) 381 self.draw_to_context(ctx, zw, zh, overlay=copy_overlays) 382 del ctx 383 pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, width, height) 384 pixbuf.get_from_drawable(pixmap, gdk.colormap_get_system(), 0, 0, 0, 0, -1, -1) 385 clipboard = gtk.clipboard_get('CLIPBOARD') 386 clipboard.set_image(pixbuf) 387 del pixmap 388 del pixbuf 389 qubx.pyenv.env.gc_collect_on_idle() 390 self.redraw_canvas() # re-calculate geometry for mouse
391 - def copy_tree(self):
392 qubx.treeGTK.Copy( self.file.as_tree() )
393 - def paste_tree(self):
394 self.file.from_tree( qubx.treeGTK.Paste() ) 395 self.file.undoStack.seal_undo('paste')
396 - def _onDrawModel(self, context, w, h):
397 pp = qubx.global_namespace.QubX.Models.nbPicturePrefs 398 if not pp.running: 399 pp = None 400 if pp and not pp.color: 401 context.set_source_rgba(1, 1, 1, 1) 402 else: 403 context.set_source_rgba(* self.appearance.color(self.cBG)) 404 context.paint() 405 if not self.file: return 406 407 ss = self.file.states 408 if pp: 409 Pr = [1.0] * len(ss) 410 if pp.states_proportional_Peq: 411 Pr = QtoPe(ModelToQ(self.file, self.get_stimulus( qubx.global_namespace.QubX.Data.view ))) 412 else: 413 Pr = [ss[i, 'Pr'] for i in xrange(len(ss))] 414 if not any(p for p in Pr): 415 Pr = [1.0/max(1, len(ss))] * len(ss) 416 sp_min, sp_max = pp.states_proportional_min_size/100, pp.states_proportional_max_size/100 417 sp_d = sp_max - sp_min 418 if pp.states_proportional_log: 419 sp_log_lo, sp_log_hi = pp.states_proportional_min, pp.states_proportional_max 420 sp_log = lambda x: log(min(0.999999, max(sp_log_lo/10, x))) 421 sp_loglo = sp_log(sp_log_lo) 422 sp_log_scale = sp_d / (sp_log(sp_log_hi) - sp_loglo) 423 sp_factor = [sqrt(sp_log_scale*(sp_log(p) - sp_loglo) + sp_min) for p in Pr] 424 else: 425 sp_factor = [sqrt(p*sp_d + sp_min) for p in Pr] # sqrt because we want area to change by pct, but we're applying it to side-length 426 427 if pp.state_font: 428 context.set_font_size(pp.state_font_size) 429 430 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M') 431 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 432 433 arrowhead_l = 1.5*width 434 sw2 = 1.5*width 435 if pp and not pp.state_boxes: 436 sw2 /= 2 437 self._stateRects = [] 438 sw2max = sw2 439 for i in xrange(ss.size): 440 x = ss.get(i, 'x') * w / 100.0 441 y = ss.get(i, 'y') * h / 100.0 442 context.set_source_rgba(* self.appearance.color(COLOR_CLASS(ss.get(i, 'Class')))) 443 if pp and pp.states_proportional: 444 context.set_font_size(max(1, int(round(pp.state_font_size*sp_factor[i])))) 445 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M') 446 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 447 sw2i = 1.5*width 448 if not pp.state_boxes: 449 sw2i /= 2 450 if not pp.states_proportional_font: 451 context.set_font_size(pp.state_font and pp.state_font_size or self.appearance.font_size) 452 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M') 453 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 454 else: 455 sw2i = sw2 456 sw2max = max(sw2i, sw2max) 457 458 self._stateRects.append(RotRect(x-sw2i-CLICK_SLOP, y-sw2i-CLICK_SLOP, x+sw2i+CLICK_SLOP, y+sw2i+CLICK_SLOP, state=i)) 459 context.rectangle(x-sw2i, y-sw2i, 2*sw2i, 2*sw2i) 460 if (pp is None) or (pp.color and pp.state_fill): 461 context.fill_preserve() 462 if (pp is None) or pp.state_boxes: 463 if pp and not pp.color: 464 context.set_source_rgba(0, 0, 0, 1) 465 else: 466 context.set_source_rgba(* self.appearance.color(COLOR_STATE_BORDER)) 467 context.stroke() 468 else: 469 context.new_path() # clear the rect 470 if pp and not pp.color: 471 context.set_source_rgba(0, 0, 0, 1) 472 else: 473 context.set_source_rgba(* self.appearance.color(COLOR_STATE_LABEL)) 474 if (pp is None) or pp.state_labels: 475 lbl = ss.get(i, 'Label') or str(i) 476 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(lbl) 477 context.move_to(x - xbearing - width/2, y - ybearing - height/2) 478 context.show_text(lbl) 479 sr = distance(0, 0, sw2max, sw2max) 480 481 self._rateRectss = [] 482 self._rateRegions = [] 483 def setcolor_rate(): 484 if pp and not pp.color: 485 context.set_source_rgba(0, 0, 0, 1) 486 else: 487 context.set_source_rgba(* self.appearance.color(COLOR_RATE))
488 def setcolor_rate_eff(): 489 if pp and not pp.color: 490 context.set_source_rgba(0, 0, 0, 1) 491 else: 492 context.set_source_rgba(* self.appearance.color(COLOR_EFFECTIVE_RATE)) 493 setcolor_rate() 494 if pp and pp.rate_font: 495 context.set_font_size(pp.rate_font_size) 496 else: 497 context.set_font_size(self.appearance.font_size) 498 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('M') 499 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 500 # reserve up to fheight for line_width 501 lw = [2] 502 if pp and pp.arrows_proportional: 503 ap_log = pp.arrows_proportional_log 504 ap_min, ap_max = pp.arrows_proportional_min, pp.arrows_proportional_max 505 if ap_log: 506 ap_min, ap_max = log(ap_min), log(ap_max) 507 def set_line_width(k): 508 # returns a number between 1 and ((fheight/2) - 1), in proportion to [log] distance of k between ap_min and ap_max 509 if ap_log: 510 k = log(k) 511 lw[0] = 1.0 + (fheight/2.0 - 2.0) * max(0.0, min(1.0, (k - ap_min) / (ap_max - ap_min))) 512 context.set_line_width(lw[0]) 513 else: 514 context.set_line_width(2) 515 516 rr = self.file.rates 517 for i in xrange(rr.size): 518 rects = {} 519 regions = collections.defaultdict() 520 a, b = rr.get(i, 'From'), rr.get(i, 'To') 521 ax, bx = ss.get(a, 'x'), ss.get(b, 'x') 522 ay, by = ss.get(a, 'y'), ss.get(b, 'y') 523 up = ax <= bx 524 if not up: 525 a,b,ax,bx,ay,by = b,a,bx,ax,by,ay 526 x0, y0 = ax * w/100.0, ay * h/100.0 527 x1, y1 = bx * w/100.0, by * h/100.0 528 theta = atan2(y1 - y0, x1 - x0) 529 rad = distance(x0, y0, x1, y1) 530 lA, lB = sr + 1, rad - sr - 1 531 lM = (lA + lB) / 2.0 532 # set up rotated, translated context along positive x axis 533 context.save() 534 context.translate(x0, y0) 535 context.rotate(theta) 536 if pp and pp.arrows_proportional: 537 set_line_width(rr.get(i, 'k0')) 538 if up: 539 # right arrowhead 540 context.save() 541 context.translate(lB, -lw[0]/2-1-fheight/4.0) 542 context.rotate(pi/4) 543 context.translate(0, lw[0]/2) 544 context.move_to(-arrowhead_l-lw[0], 0) 545 context.line_to(0, 0) 546 context.stroke() 547 context.restore() 548 # main line 549 context.move_to(lA, -1-fheight/4.0) 550 context.line_to(lB, -1-fheight/4.0) 551 context.stroke() 552 else: 553 # left arrowhead 554 context.save() 555 context.translate(lA, lw[0]/2+1+fheight/4.0) 556 context.rotate(pi/4) 557 context.translate(0, -lw[0]/2) 558 context.move_to(0, 0) 559 context.line_to(arrowhead_l+lw[0], 0) 560 context.stroke() 561 context.restore() 562 # main line 563 context.move_to(lA, 1+fheight/4.0) 564 context.line_to(lB, 1+fheight/4.0) 565 context.stroke() 566 else: 567 if up: 568 # right arrowhead 569 context.save() 570 context.translate(lB, -lw[0]/2) 571 context.rotate(pi/4) 572 context.translate(0, lw[0]/2) 573 context.move_to(-arrowhead_l-lw[0], 0) 574 context.line_to(0, 0) 575 context.stroke() 576 context.restore() 577 # main line 578 context.move_to(lM, 0) 579 context.line_to(lB, 0) 580 context.stroke() 581 else: 582 # left arrowhead 583 context.save() 584 context.translate(lA, lw[0]/2) 585 context.rotate(pi/4) 586 context.translate(0, -lw[0]/2) 587 context.move_to(0, 0) 588 context.line_to(arrowhead_l+lw[0], 0) 589 context.stroke() 590 context.restore() 591 # main line 592 context.move_to(lA, 0) 593 context.line_to(lM, 0) 594 context.stroke() 595 rects['line'] = RotRect(lA-CLICK_SLOP, -2-CLICK_SLOP, lB+CLICK_SLOP, 2+CLICK_SLOP, x0, y0, theta, rate=i, part="line") 596 # texts 597 p = rr.get(i, 'Ligand') and 'L' or '' 598 q = rr.get(i, 'Voltage') and 'V' or '' 599 press = rr.get(i, 'Pressure') and 'P' or '' 600 fmt = rr.get(i, 'Format') or self.file.k0format 601 fmt = fmt and qubx.accept.acceptFormat(fmt) or rr.format['k0'] 602 k0 = qubx.accept.proper_minus(fmt(rr.get(i, 'k0'))) 603 pq = '%s%s' % (p,q) 604 fmt = self.file.k1format and qubx.accept.acceptFormat(self.file.k1format) or rr.format['k1'] 605 k1 = qubx.accept.proper_minus(fmt(rr.get(i, 'k1'))) 606 k2 = qubx.accept.proper_minus(fmt(rr.get(i, 'k2'))) 607 eff_k = fmt(rr.get(i, 'K')) 608 if up: 609 ty0 = -fheight/2 - fdescent + 1 610 ty1 = ty0 - fheight 611 else: 612 ty0 = fheight/2 + fascent + 1 613 ty1 = ty0 + fheight 614 if pq or press: 615 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(eff_k) 616 tx0 = rad/2 - width/2 617 if (pp is None) or (pp.rate_labels and pp.effective_rate_labels): 618 setcolor_rate_eff() 619 context.move_to(tx0 - xbearing, ty1) 620 context.show_text(eff_k) 621 setcolor_rate() 622 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(pq) 623 tx0 = rad/2 - width/2 624 if (pp is None) or pp.rate_labels: 625 context.move_to(tx0 - xbearing, ty0) 626 context.show_text(pq) 627 rects['flags'] = RotRect(tx0 - 5 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + 5 + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='flags') 628 pq0, pq1 = tx0, tx0 + width 629 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(k0) 630 tx0 = pq0 - width - 5 631 if (pp is None) or pp.rate_labels: 632 context.move_to(tx0 - xbearing, ty0) 633 context.show_text(k0) 634 rects['k0'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k0') 635 regions['k0'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k0') 636 tx0 = pq1 + 5 637 if q: 638 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(k1) 639 if (pp is None) or pp.rate_labels: 640 context.move_to(tx0 - xbearing, ty0) 641 context.show_text(k1) 642 rects['k1'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k1') 643 regions['k1'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k1') 644 tx0 += width + self.appearance.emsize 645 if press: 646 pk2 = "%s %s" % (press, k2) 647 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(pk2) 648 if (pp is None) or pp.rate_labels: 649 context.move_to(tx0 - xbearing, ty0) 650 context.show_text(pk2) 651 rects['k2'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k2') 652 regions['k2'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part='k2') 653 regions.default_factory = default_static(RotRect(pq0 - width, ty0 + ybearing, pq1 + width, ty0 + ybearing + height, x0, y0, theta, rate=i)) 654 else: 655 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(k0) 656 tx0 = rad/2 - width/2 657 if (pp is None) or pp.rate_labels: 658 context.move_to(tx0 - xbearing, ty0) 659 context.show_text(k0) 660 rects['k0'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part="k0") 661 regions['k0'] = RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part="k0") 662 regions.default_factory = default_static(RotRect(tx0 - CLICK_SLOP, ty0 + ybearing - CLICK_SLOP, tx0 + width + CLICK_SLOP, ty0 + ybearing + height + CLICK_SLOP, x0, y0, theta, rate=i, part="k0")) 663 # done 664 context.restore() 665 self._rateRectss.append(rects) 666 self._rateRegions.append(regions)
667 - def find_state(self, px, py):
668 for r in self._stateRects: 669 if r.contains(px, py): 670 return r.state, r 671 return None, None
672 - def find_rate(self, px, py):
673 for rr in self._rateRectss: 674 for r in rr.itervalues(): 675 if r.contains(px, py): 676 return r.rate, r.part, r 677 return None, "", None
678 - def __nb_get_shape(self):
679 pp = qubx.global_namespace.QubX.Models.nbPicturePrefs 680 if pp.auto_size: 681 return self.dim 682 return (pp.width, pp.height)
683 - def __nb_draw(self, context, w, h):
684 pp = qubx.global_namespace.QubX.Models.nbPicturePrefs 685 pp.running = True 686 self.draw_to_context(context, w, h, overlay=True) 687 pp.running = False
688 - def __nb_draw_no_overlays(self, context, w, h):
689 pp = qubx.global_namespace.QubX.Models.nbPicturePrefs 690 pp.running = True 691 self.draw_to_context(context, w, h, overlay=False) 692 pp.running = False
693 - def __nb_get_matrix_shape(self):
694 return (self.file.states.size, self.file.states.size)
695 - def __nb_get_vector_shape(self):
696 return (1, self.file.states.size)
697 - def __nb_get_matrix_headers(self, lbl):
698 hdrs = [""] * self.file.states.size 699 hdrs[0] = lbl 700 return hdrs
701 - def __nb_Q(self):
702 QubX = qubx.pyenv.env.globals['QubX'] 703 return ModelToQ(self.file, self.get_stimulus( QubX.Data.view ))
704 - def __nb_A(self):
705 QubX = qubx.pyenv.env.globals['QubX'] 706 return expm(QubX.Data.file.sampling * self.__nb_Q())
707 - def __nb_Keq(self):
708 Q = self.__nb_Q() 709 Keq = numpy.identity(Q.shape[0]) 710 for i in xrange(Q.shape[0]): 711 for j in xrange(i+1,Q.shape[0]): 712 if Q[i,j] and Q[j,i]: 713 Keq[i,j] = Q[i,j] / Q[j,i] 714 Keq[j,i] = 1.0 / Keq[i,j] 715 return Keq
716 - def __nb_get_row_Q(self, r):
717 Q = array(self.__nb_Q()) 718 return [float(x) for x in Q[r,:].flatten()]
719 - def __nb_get_row_A(self, r):
720 A = array(self.__nb_A()) 721 return [float(x) for x in A[r,:].flatten()]
722 - def __nb_get_col_Peq(self, c):
723 Peq = array(QtoPe( self.__nb_Q() )).flatten() 724 return [float(x) for x in Peq]
725 - def __nb_get_row_Eq_constants(self, r):
726 Keq = array(self.__nb_Keq()) 727 return [float(x) for x in Keq[r,:].flatten()]
728 - def __nb_get_row_dfe(self, r):
729 Keq = self.__nb_Keq() 730 delG = numpy.zeros(shape=Keq.shape) 731 for i in xrange(Keq.shape[0]): 732 for j in xrange(Keq.shape[1]): 733 if Keq[i,j]: 734 delG[i,j] = numpy.log10(Keq[i,j]) 735 return [float(x) for x in delG[r,:].flatten()]
736 - def __nb_get_row_mfp(self, r):
737 QubX = qubx.pyenv.env.globals['QubX'] 738 dt = QubX.Data.file.sampling 739 Q = self.__nb_Q() 740 P = QtoPe(Q) 741 A = expm(dt * Q) 742 mfp = qubx.model.MeanFirstPassage(A, P, dt) 743 return [float(x) for x in array(mfp[r,:]).flatten()]
744 - def __nb_get_row_Qflux(self, r):
745 Q = self.__nb_Q() 746 Peq = QtoPe(Q) 747 W = numpy.matrix(numpy.diag(Peq)) 748 Qflux = W * Q 749 return [float(x) for x in array(Qflux[r,:]).flatten()]
750 - def add_state(self, x, y):
751 self.file.states.append({'Class' : 0, 'x' : x, 'y' : y}) 752 self.file.states.select(self.file.states.size-1, sender=self) 753 self.file.undoStack.seal_undo('add state')
754 - def del_state(self, index):
755 self.file.states.remove(index) 756 self.file.undoStack.seal_undo('remove state')
757 - def add_rate(self, from_state, to_state):
758 rr = self.file.rates 759 rr.append({'From' : from_state, 'To' : to_state}) 760 rr.append({'From' : to_state, 'To' : from_state}) 761 rr.select(rr.size - 2) 762 self.file.undoStack.seal_undo('add connection')
763 - def del_rate(self, index):
764 # delete both halves 765 r = 2 * (index / 2) 766 self.file.rates.remove(r+1) 767 self.file.rates.remove(r) 768 self.file.undoStack.seal_undo('delete connection')
769 - def make_start_state(self, index):
770 ss = self.file.states 771 for i in xrange(ss.size): 772 ss.set(i, 'Pr', (i == index) and 1.0 or 0.0) 773 self.file.undoStack.seal_undo('make start state')
774 - def set_state_color(self, index, color):
775 self.file.states.set(index, 'Class', color) 776 self.file.undoStack.seal_undo('change state color')
777 - def add_constraint_kin(self, cns):
778 self.file.constraints_kin.append(cns) 779 self.file.constraints_kin.select(self.file.constraints_kin.size-1, sender=self) 780 self.file.undoStack.seal_undo('add constraint')
781 - def __onPopupExt(self):
782 self.mnuExt.foreach(lambda item: self.mnuExt.remove(item)) 783 tools = Tools.by_cat['other'] 784 for tool_label in sorted(tools.keys()): 785 action, tool_class = tools[tool_label] 786 build_menuitem(tool_label, action or self.ref(bind(lambda: self.set_tool(tool_class()))), menu=self.mnuExt) 787 return True
788 - def __onToggleBalanceLoops(self, chk):
789 qubx.pyenv.env.OnScriptable('QubX.Model.file.balance_loops = %s' % repr(chk.get_active())) 790 self.file.balance_loops = chk.get_active()
791
792 793 794 -class QubModelDefaultTool(Tool):
795 """Basic mouse interaction for L{QubModelView}."""
796 - def __init__(self):
797 Tool.__init__(self) 798 self.__ref = Reffer() 799 self.x = self.y = -100 800 self._menuWaiting = False 801 self._popup_bg = gtk.Menu() 802 self._item_NewState = build_menuitem('New State...', self.__ref(self._onItemNewState), menu=self._popup_bg) 803 self._popup_rate = gtk.Menu() 804 self._item_DelRate = build_menuitem('Delete Connection', self.__ref(self._onItemDelRate), menu=self._popup_rate) 805 self._item_LigandSens = build_menuitem('Ligand-sensitive', self.__ref(self._onItemLigandSens), item_class=gtk.CheckMenuItem, menu=self._popup_rate) 806 self._item_VoltageSens = build_menuitem('Voltage-sensitive', self.__ref(self._onItemVoltageSens), item_class=gtk.CheckMenuItem, menu=self._popup_rate) 807 self._item_PressureSens = build_menuitem('Pressure-sensitive', self.__ref(self._onItemPressureSens), item_class=gtk.CheckMenuItem, menu=self._popup_rate) 808 self.__ignore_sens_event = False 809 # placeholder = gtk.Menu() # using placeholder menus for Add/Del constraint since submenus will be dynamically attached onNeedPopup 810 self._item_AddConstraint = build_menuitem('Add Constraint', submenu=gtk.Menu(), menu=self._popup_rate) 811 self._item_DelConstraint = build_menuitem('Del Constraint', submenu=gtk.Menu(), menu=self._popup_rate) 812 self._toolitems_rate = [] 813 self._popup_k0cns = gtk.Menu() 814 build_menuitem('Fix Rate', self.__ref(self._onItemFixRate), menu=self._popup_k0cns) 815 self.__ref(build_menuitem('Scale Rates...', self.__ref(self._onItemScaleRates), menu=self._popup_k0cns)) 816 build_menuitem('Generalized...', self.__ref(self._onItemGenConstraint), menu=self._popup_k0cns) 817 self._popup_k1cns = gtk.Menu() 818 build_menuitem('Fix voltage exp', self.__ref(self._onItemFixExp), menu=self._popup_k1cns) 819 build_menuitem('Scale voltage exps...', self.__ref(self._onItemScaleExps), menu=self._popup_k1cns) 820 build_menuitem('Generalized...', self.__ref(self._onItemGenConstraint), menu=self._popup_k1cns) 821 self._popup_k2cns = gtk.Menu() 822 build_menuitem('Fix pressure exps', self.__ref(self._onItemFixPress), menu=self._popup_k2cns) 823 build_menuitem('Scale pressure exps...', self.__ref(self._onItemScalePress), menu=self._popup_k2cns) 824 build_menuitem('Generalized...', self.__ref(self._onItemGenConstraint), menu=self._popup_k2cns) 825 self._popup_state = gtk.Menu() 826 self._item_ConnectTo = build_menuitem('Connect to...', self.__ref(self._onItemConnectTo), menu=self._popup_state) 827 self._item_MakeStart = build_menuitem('Make Start State', self.__ref(self._onItemMakeStart), menu=self._popup_state) 828 self._item_ChangeColor = build_menuitem('Change Color...', self.__ref(self._onItemChangeColor), menu=self._popup_state) 829 self._item_Grab = build_menuitem('Grab Amp...', self.__ref(self._onItemGrab), menu=self._popup_state) 830 self._item_GrabAll = build_menuitem('Grab All Amps...', self.__ref(self._onItemGrabAll), menu=self._popup_state) 831 self._item_ReGrab = build_menuitem('Re-Grab Amps', self.__ref(self._onItemReGrab), menu=self._popup_state) 832 self._item_DelState = build_menuitem('Delete State', self.__ref(self._onItemDelState), menu=self._popup_state) 833 self._toolitems_state = [] 834 self.scroll_time = None 835 self.drag_state = self.drag_rate = self.drag_field = None 836 self._hilites = []
837 - def onActivate(self):
838 Tool.onActivate(self) 839 if self.space.file: 840 self.space.file.states.OnSelect += self.ref(self._onSelectState), 'DefTool.onSelectState' 841 self.space.file.rates.OnSelect += self.ref(self._onSelectRate), 'DefTool.onSelectRate' 842 self.space.file.constraints_kin.OnSelect += self.ref(self._onSelectConstraint), 'DefTool.onSelectConstraint'
843 - def onDeactivate(self):
844 Tool.onDeactivate(self) 845 if self.space.file: 846 self.space.file.states.OnSelect -= self.ref(self._onSelectState) 847 self.space.file.rates.OnSelect -= self.ref(self._onSelectRate) 848 self.space.file.constraints_kin.OnSelect -= self.ref(self._onSelectConstraint)
849 - def _onSelectState(self, i, field, sender):
850 if (sender != self) and self.space: 851 if not (i is None): 852 self._hilites = [self.space._stateRects[i]] 853 self.space.redraw_canvas(False)
854 - def _onSelectRate(self, i, field, sender):
855 if (sender != self) and self.space: 856 if not (i is None): 857 self._hilites = [self.space._rateRegions[i][field]] 858 self.space.redraw_canvas(False)
859 - def _onSelectConstraint(self, i, field, sender):
860 if (sender != self) and self.space and (not (i is None)): 861 try: # for each Type, identify included rates and add appropriate part(s) to hilites 862 cc = self.space.file.constraints_kin 863 rr = self.space.file.rates 864 typ = cc.get(i, 'Type') 865 states = tuple(cc.get(i, 'States')) 866 if typ == 'FixRate': 867 self._hilites = [self.space._rateRegions[self.space.file.rate_connecting[states]]['k0']] 868 elif typ == 'FixExp': 869 self._hilites = [self.space._rateRegions[self.space.file.rate_connecting[states]]['k1']] 870 elif typ == 'FixPress': 871 self._hilites = [self.space._rateRegions[self.space.file.rate_connecting[states]]['k2']] 872 elif typ == 'ScaleRate': 873 h = [] 874 for r in (self.space.file.rate_connecting[(a,b)] for a,b in [(states[0], states[1]), (states[2], states[3])]): 875 h.append(self.space._rateRegions[r]['k0']) 876 self._hilites = h 877 elif typ == 'ScaleExp': 878 h = [] 879 for r in (self.space.file.rate_connecting[(a,b)] for a,b in [(states[0], states[1]), (states[2], states[3])]): 880 h.append(self.space._rateRegions[r]['k1']) 881 self._hilites = h 882 elif typ == 'ScalePress': 883 h = [] 884 for r in (self.space.file.rate_connecting[(a,b)] for a,b in [(states[0], states[1]), (states[2], states[3])]): 885 h.append(self.space._rateRegions[r]['k2']) 886 self._hilites = h 887 elif typ in ['LoopBal', 'LoopImbal']: 888 if states[0] == states[-1]: 889 ss = states 890 else: 891 ss = list(states) + [states[0]] 892 h = [] 893 for i,a in enumerate(ss[:-1]): 894 r = self.space.file.rate_connecting[(a, ss[i+1])] 895 h.append(self.space._rateRegions[r]['']) 896 r = self.space.file.rate_connecting[(ss[i+1], a)] 897 h.append(self.space._rateRegions[r]['']) 898 self._hilites = h 899 except: 900 traceback.print_exc() 901 self._hilites = [] # highlighting doesn't matter so much 902 self.space.redraw_canvas(False)
903 - def onOverlay(self, context, w, h):
904 context.set_source_rgba(0, .4, 1, .3) # (.3,.5,.3,.25) 905 for rect in self._hilites: 906 x,y,th,l,t,r,b = rect.x0, rect.y0, rect.theta, rect.l, rect.t, rect.r, rect.b 907 context.save() 908 context.translate(x,y) 909 context.rotate(th) 910 bighalf = max(r-l, b-t) / 2 911 rad = distance(0, 0, bighalf, bighalf) + 1 912 context.arc((l+r)/2, (t+b)/2, rad, 0, 2*pi) 913 context.fill() 914 context.restore()
915 - def onPress(self, x, y, e):
916 self.seal_scroll() 917 self.x, self.y = x, y 918 gobject.timeout_add(MENU_TIMEOUT, self._onMenuTimeout, 919 Anon(x=e.x, y=e.y, button=e.button, time=e.time)) 920 rate, part, rect = self.space.find_rate(x, y) 921 self.drag_rate = rate 922 self.drag_field = None 923 if not (rate is None): 924 if part == 'k0': 925 self.drag_field = 'k0' 926 elif part == 'k1': 927 self.drag_field = 'k1' 928 elif part == 'k2': 929 self.drag_field = 'k2' 930 self.drag_state = None 931 self._menuWaiting = True 932 return 933 # not a rate; how about a state? 934 self.drag_state, rect = self.space.find_state(x, y) 935 if not (self.drag_state is None): 936 self._menuWaiting = True 937 return 938 # in the background: 939 # self.onNeedPopup(x, y, e) 940 self._popup_bg.popup(None, None, None, 0, e.time) 941 self.space.release_button(e)
942 - def onRelease(self, x, y, e):
943 if self._menuWaiting != IGNORE_MENU_RELEASE: 944 if not (self.drag_state is None): 945 if distance(self.x, self.y, x, y): 946 state = self.space.file.states.get_row(self.drag_state) 947 self.space.file.undoStack.seal_undo('move state') 948 qubx.pyenv.env.OnScriptable('QubX.Models.view.deftool.move_state(%i, %s, %s)' 949 % (self.drag_state, repr(state.x), repr(state.y))) 950 self.space.file.states.select(self.drag_state, sender=self.space) 951 elif not (self.drag_rate is None): 952 self.space.file.rates.select(self.drag_rate, self.drag_field, self.space) 953 else: 954 self._hilites = [] 955 self._menuWaiting = False
956 - def move_state(self, index, x, y):
957 self.space.file.states[index] = {'x': x, 'y': y} 958 self.space.file.undoStack.seal_undo('move state')
959 - def onDblClick(self, x, y, e):
960 pass
961 - def onDrag(self, x, y, e):
962 self._menuWaiting = self._menuWaiting and (distance(self.x, self.y, x, y) <= MENU_STRAYDIUS) 963 if self.drag_state is None: return 964 w, h = self.space.dim 965 i = self.drag_state 966 self.space.file.states.set(i, 'x', min(100.0, max(0.0, x * 100.0 / w))) 967 self.space.file.states.set(i, 'y', min(100.0, max(0.0, y * 100.0 / h)))
968 - def onRoll(self, x, y, e):
969 self.x, self.y = x, y 970 self.space.redraw_canvas(False)
971 - def onScroll(self, x, y, e, amount):
972 rate, part, rect = self.space.find_rate(x, y) 973 if (rate < 0) or not (part in ['k0', 'k1', 'k2']): return 974 rr = self.space.file.rates 975 rr[rate, part] = scrolled_digits(rr[rate, part], amount, e.state & gdk.CONTROL_MASK, e.state & gdk.SHIFT_MASK) 976 already_scrolling = bool(self.scroll_time) 977 self.scroll_time = datetime.datetime.now() 978 if not already_scrolling: 979 gobject.timeout_add(500, self.timeout_scroll, rate, part)
980 - def timeout_scroll(self, rate, part):
981 if not self.scroll_time: # handled e.g. prior to another event 982 return 983 delta = datetime.datetime.now() - self.scroll_time 984 ms = 1e3 * delta.seconds + 1e-3 * delta.microseconds 985 if ms > 400: 986 self.seal_scroll(rate, part) 987 else: 988 gobject.timeout_add(500-int(ms), self.timeout_scroll, rate, part)
989 - def seal_scroll(self, rate=None, part=None):
990 if self.scroll_time: 991 self.space.file.undoStack.seal_undo('edit rate') 992 self.scroll_time = None 993 if not (rate is None): 994 qubx.pyenv.env.OnScriptable('QubX.Models.file.rates[%i, %s] = %s' 995 % (rate, repr(part), repr(self.space.file.rates.get(rate, part))))
996 - def onNeedPopup(self, x, y, e):
997 self.seal_scroll() 998 self.x, self.y = x, y 999 rate, part, rect = self.space.find_rate(x, y) 1000 self.drag_rate = rate 1001 if not (rate is None): 1002 self.__ignore_sens_event = True 1003 self._item_LigandSens.set_active(bool(self.space.file.rates[rate, 'Ligand'])) 1004 self._item_VoltageSens.set_active(bool(self.space.file.rates[rate, 'Voltage'])) 1005 self._item_PressureSens.set_active(bool(self.space.file.rates[rate, 'Pressure'])) 1006 self.__ignore_sens_event = False 1007 if part == 'k0': 1008 field = 'k0' 1009 self._item_AddConstraint.set_submenu(self._popup_k0cns) 1010 elif part == 'k1': 1011 field = 'k1' 1012 self._item_AddConstraint.set_submenu(self._popup_k1cns) 1013 elif part == 'k2': 1014 field = 'k2' 1015 self._item_AddConstraint.set_submenu(self._popup_k2cns) 1016 else: 1017 field = None 1018 # screws up submenus: self.space.file.rates.select(rate, field, self.space) 1019 self._item_AddConstraint.set_sensitive(bool(field)) 1020 1021 in_constraints = self.space.file.constraints_kin.list_involving(self.space.file.rates.get(rate, 'From'), self.space.file.rates.get(rate, 'To'), field) 1022 self._item_DelConstraint.set_sensitive(bool(in_constraints)) 1023 menu_dc = gtk.Menu() 1024 for c in in_constraints: 1025 build_menuitem('%s %s' % (self.space.file.constraints_kin.get(c, 'Type'), 1026 self.space.file.constraints_kin.format['States'](self.space.file.constraints_kin.get(c, 'States'))), 1027 self.__ref(bind_with_args_before(self._onItemDelConstraint, c)), menu=menu_dc) 1028 self._item_DelConstraint.set_submenu(menu_dc) 1029 1030 for it, act in self._toolitems_rate: 1031 self._popup_rate.remove(it) 1032 self._toolitems_rate = [] 1033 tools = Tools.by_cat['rate'] 1034 for tool_label in sorted(tools.keys()): 1035 action, tool_class = tools[tool_label] 1036 act = bind(action, self.space, rate) # build_menuitem uses weakref; must hold a ref to bound callback 1037 self._toolitems_rate.append( (build_menuitem(tool_label, act, menu=self._popup_rate), act) ) 1038 1039 self._popup_rate.popup(None, None, None, 0, e.time) # e.button, e.time) # longstanding bug doesn't activate submenu items 1040 return 1041 # not a rate; how about a state? 1042 self.drag_state, rect = self.space.find_state(x, y) 1043 if not (self.drag_state is None): 1044 self.space.file.states.select(self.drag_state, sender=self.space) 1045 self._item_ReGrab.set_sensitive(can_regrab(self.space)) 1046 1047 for it, act in self._toolitems_state: 1048 self._popup_state.remove(it) 1049 self._toolitems_state = [] 1050 tools = Tools.by_cat['state'] 1051 for tool_label in sorted(tools.keys()): 1052 action, tool_class = tools[tool_label] 1053 act = bind(action, self.space, self.drag_state) 1054 self._toolitems_state.append( (build_menuitem(tool_label, act, menu=self._popup_state), act) ) 1055 1056 self._popup_state.popup(None, None, None, 0, e.time) 1057 return 1058 # in the background: 1059 self._popup_bg.popup(None, None, None, 0, e.time)
1060 - def _onMenuTimeout(self, e):
1061 if self._menuWaiting: 1062 self._menuWaiting = IGNORE_MENU_RELEASE 1063 self.space.release_button(e) 1064 self.onNeedPopup(e.x, e.y, e)
1065 - def _onItemNewState(self, item):
1066 w,h = self.space.dim 1067 x = self.x * 100.0 / w 1068 y = self.y * 100.0 / h 1069 self.space.add_state(x, y) 1070 qubx.pyenv.env.OnScriptable('QubX.Models.view.add_state(%s, %s)' % (repr(x), repr(y))) 1071 self.space.tool = QubModelColorTool(self.x, self.y, self, self.space.file.states.size-1)
1072 - def _onItemDelRate(self, item):
1073 self.space.del_rate(self.drag_rate) 1074 qubx.pyenv.env.OnScriptable('QubX.Models.view.del_rate(%i)' % self.drag_rate)
1075 - def _onItemLigandSens(self, item):
1076 if self.__ignore_sens_event: 1077 return 1078 rr = self.space.file.rates 1079 i = self.drag_rate 1080 if rr[i, 'Ligand']: 1081 qubx.pyenv.env.OnScriptable('QubX.Models.file.rates[%i, "Ligand"] = ""' % i) 1082 rr[i, 'Ligand'] = '' 1083 else: 1084 qubx.pyenv.env.OnScriptable('QubX.Models.file.rates[%i, "Ligand"] = "Ligand"' % i) 1085 rr[i, 'Ligand'] = 'Ligand' 1086 rr.select(i, 'Ligand', sender=self) 1087 self.space.file.undoStack.seal_undo('Set Ligand Sensitivity')
1088 - def _onItemVoltageSens(self, item):
1089 if self.__ignore_sens_event: 1090 return 1091 rr = self.space.file.rates 1092 i = self.drag_rate 1093 if rr[i, 'Voltage']: 1094 rr[i, 'k1'] = 0.0 1095 qubx.pyenv.env.OnScriptable('QubX.Models.file.rates[%i, "Voltage"] = ""' % i) 1096 rr[i, 'Voltage'] = '' 1097 else: 1098 qubx.pyenv.env.OnScriptable('QubX.Models.file.rates[%i, "Voltage"] = "Voltage"' % i) 1099 rr[i, 'k1'] = 0.1 1100 rr[i, 'Voltage'] = 'Voltage' 1101 rr.select(i, 'Voltage', sender=self) 1102 self.space.file.undoStack.seal_undo('Set Voltage Sensitivity')
1103 - def _onItemPressureSens(self, item):
1104 if self.__ignore_sens_event: 1105 return 1106 rr = self.space.file.rates 1107 i = self.drag_rate 1108 if rr[i, 'Pressure']: 1109 rr[i, 'k2'] = 0.0 1110 qubx.pyenv.env.OnScriptable('QubX.Models.file.rates[%i, "Pressure"] = ""' % i) 1111 rr[i, 'Pressure'] = '' 1112 else: 1113 qubx.pyenv.env.OnScriptable('QubX.Models.file.rates[%i, "Pressure"] = "Pressure"' % i) 1114 rr[i, 'k2'] = 1.0 1115 rr[i, 'Pressure'] = 'Pressure' 1116 rr.select(i, 'Pressure', sender=self) 1117 self.space.file.undoStack.seal_undo('Set Pressure Sensitivity')
1118 - def _onItemMakeStart(self, item):
1119 self.space.make_start_state(self.drag_state) 1120 qubx.pyenv.env.OnScriptable('QubX.Models.view.make_start_state(%i)' % self.drag_state)
1121 - def _onItemChangeColor(self, item):
1122 self.space.tool = QubModelColorTool(self.x, self.y, self, self.drag_state)
1123 - def _onItemGrab(self, item):
1124 QubX = qubx.pyenv.env.globals['QubX'] 1125 QubX.Data.request_show() 1126 data = QubX.Data.view.hires 1127 data.tool = GrabTool(self.space, self.space.file.states.get(self.drag_state, 'Class'), data.tool)
1128 - def _onItemGrabAll(self, item):
1129 QubX = qubx.pyenv.env.globals['QubX'] 1130 QubX.Data.request_show() 1131 data = QubX.Data.view.hires 1132 data.tool = GrabTool(self.space, -1, data.tool)
1133 - def _onItemReGrab(self, item):
1134 GrabTool(self.space, -1, qubx.global_namespace.QubX.Data.view.hires.tool).regrab()
1135 - def _onItemConnectTo(self, item):
1136 self.space.tool = QubModelConnectTool(self.x, self.y, self, self.drag_state)
1137 - def _onItemDelState(self, item):
1138 self.space.del_state(self.drag_state) 1139 qubx.pyenv.env.OnScriptable('QubX.Models.view.del_state(%i)' % self.drag_state)
1140 - def _onItemFixRate(self, item):
1141 sa, sb = [self.space.file.rates.get(self.drag_rate, x) for x in ('From', 'To')] 1142 for c in self.space.file.constraints_kin.list_involving(sa, sb, 'k0'): 1143 if self.space.file.constraints_kin.get(c, 'Type') == 'FixRate': 1144 return 1145 cns = {'Type': 'FixRate', 'States': [sa, sb]} 1146 self.space.add_constraint_kin(cns) 1147 qubx.pyenv.env.OnScriptable('QubX.Models.view.add_constraint_kin(%s)' % repr(cns))
1148 - def _onItemFixExp(self, item):
1149 sa, sb = [self.space.file.rates.get(self.drag_rate, x) for x in ('From', 'To')] 1150 for c in self.space.file.constraints_kin.list_involving(sa, sb, 'k1'): 1151 if self.space.file.constraints_kin.get(c, 'Type') == 'FixExp': 1152 return 1153 cns = {'Type': 'FixExp', 'States': [sa, sb]} 1154 self.space.add_constraint_kin(cns) 1155 qubx.pyenv.env.OnScriptable('QubX.Models.view.add_constraint_kin(%s)' % repr(cns))
1156 - def _onItemFixPress(self, item):
1157 sa, sb = [self.space.file.rates.get(self.drag_rate, x) for x in ('From', 'To')] 1158 for c in self.space.file.constraints_kin.list_involving(sa, sb, 'k1'): 1159 if self.space.file.constraints_kin.get(c, 'Type') == 'FixPress': 1160 return 1161 cns = {'Type': 'FixPress', 'States': [sa, sb]} 1162 self.space.add_constraint_kin(cns) 1163 qubx.pyenv.env.OnScriptable('QubX.Models.view.add_constraint_kin(%s)' % repr(cns))
1164 - def _onItemScaleRates(self, item):
1165 sa, sb = [self.space.file.rates.get(self.drag_rate, x) for x in ('From', 'To')] 1166 self.space.tool = QubModelScaleTool(self.x, self.y, self, sa, sb, 'k0', 'ScaleRate')
1167 - def _onItemScaleExps(self, item):
1168 sa, sb = [self.space.file.rates.get(self.drag_rate, x) for x in ('From', 'To')] 1169 self.space.tool = QubModelScaleTool(self.x, self.y, self, sa, sb, 'k1', 'ScaleExp')
1170 - def _onItemScalePress(self, item):
1171 sa, sb = [self.space.file.rates.get(self.drag_rate, x) for x in ('From', 'To')] 1172 self.space.tool = QubModelScaleTool(self.x, self.y, self, sa, sb, 'k2', 'ScalePress')
1173 - def _onItemDelConstraint(self, item, c):
1174 self.del_constraint_kin(c) 1175 qubx.pyenv.env.OnScriptable('QubX.Models.file.constraints_kin.remove(%i)' % c)
1176 - def del_constraint_kin(self, index):
1177 self.space.file.constraints_kin.remove(index)
1178 - def _onItemGenConstraint(self, item):
1179 self.space.file.constraints_kin.append({}) 1180 self.space.file.constraints_kin.select(self.space.file.constraints_kin.size-1, sender=self) 1181 qubx.pyenv.env.OnScriptable('QubX.Models.file.constraints_kin.append({})')
1182 - def onKeyPress(self, event):
1183 #if gdk.CONTROL_MASK & event.state: 1184 if event.keyval in (ord('f'), ord('F')): # File 1185 self.space.mnuFile.popup(None, None, None, 0, event.time) 1186 return 1187 1188 menu_map = [('G', self.space.mnuFiles), 1189 ('N', self.space.subNotebook), 1190 ('Z', self.space.lblUndo), 1191 ('R', self.space.lblRedo), 1192 ('I', self.space.chkIeqFv)] 1193 for ltr, subTool in menu_map: 1194 if event.keyval in (ord(ltr), ord(ltr.lower())): 1195 subTool.action(0, 0, Anon(state=event.state, time=event.time, button=0, x=0, y=0)) 1196 return 1197 1198 offset = 0 1199 if event.keyval in (ord('+'), ord('='), keysyms.KP_Add): 1200 offset = 1 1201 elif event.keyval in (ord('-'), ord('_'), keysyms.KP_Subtract): 1202 offset = -1 1203 if offset: 1204 self.space.do_scroll_channelCount(offset, event.state & gdk.CONTROL_MASK, event.state & gdk.SHIFT_MASK) 1205 return
1206
1207 -class QubModelConnectTool(OverlayRgnTool):
1208 """Mouse interaction and overlay while drawing a connection between states."""
1209 - def __init__(self, x, y, prev_tool, state):
1210 OverlayRgnTool.__init__(self, x, y, prev_tool) 1211 self.from_state = state
1212 - def onActivate(self):
1213 self.rgns[:] = self.space._stateRects[:]
1214 - def onOverlay(self, context, w, h):
1215 color = list(NOALPHA(self.space.appearance.color(COLOR_RATE))) 1216 if (not (self.rgn is None)) and (self.rgn != self.from_state) and not self.space.file.rate_connecting.has_key((self.from_state, self.rgn)): 1217 color.append(.8) 1218 else: 1219 color.append(.3) 1220 context.set_source_rgba(*color) 1221 context.move_to(self.start_x, self.start_y) 1222 context.line_to(self.x, self.y) 1223 context.stroke()
1224 - def onRoll(self, x, y, e):
1225 OverlayRgnTool.onRoll(self, x, y, e) 1226 self.space.redraw_canvas(False)
1227 - def onRelease(self, x, y, e):
1228 if (not (self.rgn is None)) and (self.rgn != self.from_state) and not self.space.file.rate_connecting.has_key((self.from_state, self.rgn)): 1229 self.space.add_rate(self.from_state, self.rgn) 1230 qubx.pyenv.env.OnScriptable('QubX.Models.view.add_rate(%i, %i)' % (self.from_state, self.rgn)) 1231 self.space.tool = self.prev_tool
1232
1233 1234 -class QubModelColorTool(OverlayRgnTool):
1235 """Mouse interaction and overlay while picking a state's color."""
1236 - def __init__(self, x, y, prev_tool, state):
1237 OverlayRgnTool.__init__(self, x, y, prev_tool) 1238 self.state = state
1239 - def onOverlay(self, context, w, h):
1240 rgns = [] 1241 codes = [] 1242 def store_rect(xA, yA, xW, yH, c): 1243 xa, xb = map(lambda x: x*dww/dw+x0, [xA, xA+xW]) 1244 ya, yb = map(lambda y: y*dhh/dh+y0, [yA, yA+yH]) 1245 rgns.append(RotRect(xa, ya, xb, yb)) 1246 codes.append(c)
1247 sw2 = 1.5*self.space.appearance.emsize 1248 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 1249 th = fascent + fdescent 1250 instr = ['Pick a color (conductance class):', 'higher numbers are "more open."'] 1251 instr_width = max(context.text_extents(ins)[2] for ins in instr) + th + 10 1252 dw, dh = max(16*sw2, instr_width), 2*th + 7*sw2 1253 dww, dhh = min(dw, w), min(dh, h) 1254 x0, y0 = min(self.start_x, w-dww), min(self.start_y, h-dhh) 1255 context.translate(x0, y0) 1256 context.scale(dww/dw, dhh/dh) 1257 context.set_source_rgba(0,0,0,.75) 1258 context.rectangle(0, 0, dw, dh) 1259 context.fill() 1260 context.set_source_rgb(1, 1, 1) 1261 context.move_to(5, fascent+sw2/2) 1262 context.show_text(instr[0]) 1263 context.set_source_rgb(.8,0,0) 1264 store_rect(dw-th, 0, th, th, -1) 1265 context.rectangle(dw-th, 0, th, th) 1266 context.fill() 1267 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents('x') 1268 context.move_to(dw-th+(th-width)/2-xbearing, (th-height)/2-ybearing) 1269 context.set_source_rgb(0,0,0) 1270 context.show_text('x') 1271 for j in xrange(2): 1272 for i in xrange(5): 1273 c = j*5 + i 1274 sx, sy, sw, sh = (i+1)*sw2 + 2*i*sw2, th + (j+1)*sw2 + 2*j*sw2, 2*sw2, 2*sw2 1275 store_rect(sx, sy, sw, sh, c) 1276 context.rectangle(sx, sy, sw, sh) 1277 alpha = .65 1278 if (not (self.rgn is None)) and (c == self.codes[self.rgn]): 1279 alpha = .9 1280 context.set_source_rgb(1,1,1) 1281 context.stroke_preserve() 1282 context.set_source_rgba(*SETALPHA(self.space.appearance.color(COLOR_CLASS(c)), alpha)) 1283 context.fill() 1284 context.set_source_rgb(1, 1, 1) 1285 lbl = str(c) 1286 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(lbl) 1287 context.move_to(sx + (sw-width)/2 - xbearing, sy + (sh-height)/2 - ybearing) 1288 context.show_text(lbl) 1289 context.set_source_rgb(1, 1, 1) 1290 context.move_to(5, dhh-fdescent-sw2/2) 1291 context.show_text(instr[1]) 1292 store_rect(x0, y0, x0+dww, y0+dhh, -2) # harmless click in bg of windowlet 1293 store_rect(0, 0, w, h, -1) # out-of-bounds -> close 1294 self.rgns = rgns 1295 self.codes = codes
1296 - def onRelease(self, x, y, e):
1297 c = None 1298 if not (self.rgn is None): 1299 c = self.codes[self.rgn] 1300 if c >= 0: 1301 qubx.pyenv.env.OnScriptable('QubX.Models.view.set_state_color(%i, %i)' % (self.state, c)) 1302 self.space.set_state_color(self.state, c) 1303 if c != -2: 1304 self.space.tool = self.prev_tool
1305
1306 1307 -class QubModelScaleTool(OverlayRgnCaptionedTool):
1308 """Mouse interaction and overlay while picking a rate to complete a Scale constraint."""
1309 - def __init__(self, x, y, prev_tool, a, b, part, cns_type):
1310 OverlayRgnCaptionedTool.__init__(self, x, y, prev_tool, 'Click rate constant(s) to fix their proportion. Double-click the last one to finish.') 1311 self.a, self.b, self.part = a, b, part 1312 self.cns_type = cns_type
1313 - def onActivate(self):
1314 OverlayRgnCaptionedTool.onActivate(self) 1315 self.rgns[1:] = [] 1316 self.indices = [[-1, -1]] # (rgn index, rate index) 1317 rr = self.space.file.rates 1318 rects = self.space._rateRectss 1319 for i in xrange(rr.size): 1320 ia, ib = rr.get(i, 'From'), rr.get(i, 'To') 1321 if (self.a != ia) or (self.b != ib): 1322 if rects[i].has_key(self.part): 1323 self.rgns.append(rects[i][self.part]) 1324 self.indices.append([i,i])
1325 - def onRelease(self, x, y, e):
1326 if self.rgn == 0: 1327 self.space.tool = self.prev_tool 1328 elif not (self.rgn is None): 1329 rr = self.space.file.rates 1330 gi, ri = self.indices[self.rgn] 1331 c, d = rr.get(ri, 'From'), rr.get(ri, 'To') 1332 cns = {'Type' : self.cns_type, 'States' : [self.a, self.b, c, d]} 1333 qubx.pyenv.env.OnScriptable('QubX.Models.view.add_constraint_kin(%s)' % repr(cns)) 1334 self.space.add_constraint_kin(cns) 1335 del self.rgns[gi] 1336 del self.indices[gi] 1337 for j in xrange(gi, len(self.indices)): 1338 self.indices[j][0] -=1 1339 self.rgn = None
1340 - def onDblClick(self, x, y, e):
1341 self.space.tool = self.prev_tool
1342
1343 1344 -class QubModelLoopTool(OverlayRgnCaptionedTool):
1345 """Mouse interaction and overlay while picking rates around a loop."""
1346 - def __init__(self, x, y, prev_tool, a, b, cns_type):
1347 OverlayRgnCaptionedTool.__init__(self, x, y, prev_tool, 'Click rate constants to define a loop') 1348 self.a, self.b = a, b 1349 self.cns_type = cns_type
1350 - def onActivate(self):
1351 OverlayRgnCaptionedTool.onActivate(self) 1352 self.loop = [] 1353 self.list_rates([self.a, self.b], [self.a, self.b])
1354 - def list_rates(self, from_states, excl_states):
1355 self.rgns[1:] = [] 1356 self.indices = [-1] 1357 rr = self.space.file.rates 1358 rects = self.space._rateRectss 1359 for i in xrange(rr.size): 1360 ia, ib = rr.get(i, 'From'), rr.get(i, 'To') 1361 if (ia in from_states) and not (ib in excl_states): 1362 self.rgns.append(rects[i]['k0']) 1363 self.indices.append((i, ia, ib)) 1364 self.space.redraw_canvas(False)
1365 - def onRelease(self, x, y, e):
1366 if self.rgn == 0: 1367 self.space.tool = self.prev_tool 1368 return 1369 if not (self.rgn is None): 1370 rr = self.space.file.rates 1371 i, ia, ib = self.indices[self.rgn] 1372 self.list_rates([ib], [ia]) 1373 if not self.loop: 1374 if ia == self.a: 1375 self.loop = [self.b, ia, ib] 1376 else: 1377 self.loop = [self.a, ia, ib] 1378 elif ib == self.loop[0]: 1379 cns = {'Type' : self.cns_type, 'States' : self.loop} 1380 self.space.add_constraint_kin(cns) 1381 qubx.pyenv.env.OnScriptable('QubX.Models.view.add_constraint_kin(%s)' % repr(cns)) 1382 self.space.tool = self.prev_tool 1383 else: 1384 self.loop.append(ib)
1385
1386 1387 1388 -class GrabTool(qubx.dataGTK.QubData_SelTool):
1389 """ 1390 As L{qubx.dataGTK.QubDataView}.tool, lets user highlight data to initialize class Amp/Std/Cond/CondStd. 1391 If model.file.IeqFv: initializes baseline (Amp[0], Std[0]) from class 0, and (Cond[i], CondStd[i]) from class i, 1392 and if there are more measurements than classes (at multiple voltages) it curve-fits all those params together. 1393 1394 """
1395 - def __init__(self, model, cls, prev_tool, **kw):
1396 """ 1397 @param model: L{QubModelView} 1398 @param cls: index in model.file.classes to initialize, or -1 to initialize all 1399 @param prev_tool: L{qubx.toolspace.Tool} to activate on the L{qubx.dataGTK.QubDataView} when this is done; typically the priorly active one 1400 """ 1401 self.__ref = Reffer() 1402 qubx.dataGTK.QubData_SelTool.__init__(self, action=self.__ref(self.__onSel), **kw) 1403 self.model = model 1404 self.cls = cls 1405 self.prev_tool = prev_tool 1406 self.noRect = [-1, -1, 1, 1] # offscreen rect for hidden button(s) 1407 self.skipping = False # Skip button: when cls == -1, skip to next class 1408 self.skipRect = self.noRect # ____ing: mouse down in button 1409 self.done_ing = False # Done button: to skip any/all remaining measurements 1410 self.doneRect = self.noRect 1411 self.canceling = False # Cancel button: shown with measurement for 2sec before storing it 1412 self.cancelRect = self.noRect 1413 self.conf_msg = None # Text shown with Cancel button ("class 1: 0 +/- 1") 1414 self.conf_action = None # If not canceled within 2sec, calls self.conf_action() to store measurement 1415 self.legend = qubx.toolspace.Layer(x=1, y=1, w=-1, h=-1)# (x=-55, y=.5, w=35, h=2) 1416 self.legend.add_sublayer(qubx.toolspace.SubLayer_Label('Now highlight some data (above)', 0, 1, x=-50, y=5, w=30, h=2)) 1417 self.legend.add_sublayer(qubx.toolspace.SubLayer_Label('Done', 0, 1, x=-28, y=8, w=8, h=2, border=1, 1418 action=self.__ref(bind(self.__onDone))))
1419 - def onActivate(self):
1420 """ 1421 Finds classes used in this model, and Voltage (signal or constant) from this data; 1422 sets up for 2 passes (2 measurements per class, at different Voltage) if model.file.IeqFv and Voltage isn't constant. 1423 """ 1424 qubx.dataGTK.QubData_SelTool.onActivate(self) 1425 self.grab_list = self.space.file.lists.by_name('Model-Grab') 1426 self.prev_layerset = self.space.layerset 1427 self.space.layerset = qubx.toolspace.LayerSet() 1428 self.model.add_layer(self.legend) 1429 if self.cls == -1: 1430 ss = self.model.file.states 1431 self.clss = sorted(list(set(ss.get(i, 'Class') for i in xrange(ss.size)))) 1432 self.clss_iter = iter(self.clss) 1433 self.cls_now = self.clss_iter.next() 1434 self.grab_list.clear() 1435 else: 1436 self.cls_now = self.cls 1437 self.meas = {} # measurements: if IeqFv: {cls : {Voltage: (mean, std)}}; not IeqFv: {cls : (mean, std)} 1438 if self.model.file.IeqFv: 1439 self.passno = 0 # 0: first of 2 passes (0 and 1); 1: second pass (1); 2: only 1 pass (const V) 1440 signals = [self.space.signals.get(i, 'Name') for i in xrange(1, self.space.signals.size)] 1441 try: 1442 self.V_index = signals.index('Voltage') 1443 except: 1444 self.V_index = None 1445 self.passno = 2 1446 constants = dict([(self.space.file.constants.get(i, 'Name'), self.space.file.constants.get(i, 'Value')) 1447 for i in xrange(self.space.file.constants.size)]) 1448 if 'Voltage' in constants: 1449 self.V_const = constants['Voltage'] 1450 else: 1451 self.V_const = 0.0 1452 self.space.file.constants.append({'Name':'Voltage', 'Value':self.V_const})
1453 - def __iterate(self):
1454 try: 1455 self.cls_now = self.clss_iter.next() 1456 except StopIteration: 1457 if self.model.file.IeqFv and (self.passno == 0): 1458 self.passno = 1 1459 self.clss_iter = iter(self.clss) 1460 self.cls_now = self.clss_iter.next() 1461 else: 1462 self.__onDone()
1463 - def __onSel(self, left, right):
1464 """Measures; sets 2sec timer to store measurement and go to next step.""" 1465 def DeferredSet(c, I, S, V, f, l, done=False, passno=None, iterate=False): 1466 """Returns a function which stores a measurement and updates state variable(s). 1467 1468 @ivar c: class index 1469 @ivar I: mean current 1470 @ivar S: current standard deviation 1471 @ivar V: if model.file.IeqFv: mean Voltage; not IeqFv: None 1472 @ivar done: True if this is the last measurement 1473 @ivar passno: when self.cls == 0 and IeqFv and this is the first measurement, passno=1 sets up the second pass 1474 @ivar iterate: True when self.cls == -1, to set up the next class or pass 1475 """ 1476 def DoSet(): 1477 if not (V is None): 1478 try: 1479 self.meas[c][V] = (I, S) 1480 except: 1481 self.meas[c] = {V:(I,S)} 1482 else: 1483 self.meas[c] = (I, S) 1484 self.grab_list.insert_selection(f, l, Class=c, Group=c) 1485 if done: 1486 self.__onDone() 1487 if passno: 1488 self.passno = passno 1489 if iterate: 1490 self.__iterate()
1491 return DoSet
1492 f, l, I, S = self.__measure(left, right) 1493 cls_sel = self.cls_now 1494 V = None 1495 done = False 1496 passno = None 1497 iterate = False 1498 if self.model.file.IeqFv: 1499 if not (self.V_index is None): 1500 f, l, V, Vs = self.__measure(left, right, self.V_index) 1501 else: 1502 V = self.V_const 1503 if self.cls > 0: 1504 done = True 1505 elif self.cls == 0: 1506 if self.passno == 0: 1507 passno = 1 1508 else: 1509 done = True 1510 elif self.cls == -1: 1511 iterate = True 1512 else: 1513 if self.cls >= 0: 1514 done = True 1515 else: 1516 iterate = True 1517 self.__show_confirm("Class %i: %.3g +/- %.3g %s" % (cls_sel, I, S, not (V is None) and ("at %.3g mV" % V) or ""), 1518 DeferredSet(cls_sel, I, S, V, f, l, done, passno, iterate))
1519 - def __onDone(self):
1520 classes = self.model.file.classes 1521 def clsVrev(c): 1522 vRev = classes.get(c, 'vRev') 1523 if abs(vRev - UNSET_VALUE) < 1e-6: 1524 vRev = self.model.file.vRev 1525 return vRev
1526 if self.model.file.IeqFv: 1527 if 0 in self.meas: 1528 m0 = self.meas[0] 1529 if len(m0) == 1: # 1 closed measurement, IeqFv; ->Amp[0], Std[0] 1530 I0, S0 = m0.values()[0] 1531 else: # 2 closed measurements, IeqFv; solve for class 0 Amp, Std, Cond, CondStd 1532 V0, V1 = m0.keys() 1533 mean0, std0 = m0[V0] 1534 mean1, std1 = m0[V1] 1535 if std0 < std1: 1536 V0, mean0, std0, V1, mean1, std1 = V1, mean1, std1, V0, mean0, std0 1537 dV0 = 1e-3 * (V0 - clsVrev(0)) 1538 G = (mean0 - mean1) / (V0 - V1) 1539 I0 = mean0 - dV0 * G 1540 Gs = sqrt(std0**2 - std1**2) / (V0 - V1) 1541 S0 = sqrt(std0**2 - dV0 * Gs) 1542 classes.set(0, 'Cond', G) 1543 classes.set(0, 'CondStd', Gs) 1544 classes.set(0, 'Amp', I0) 1545 classes.set(0, 'Std', S0) 1546 else: # 0 closed measurements, IeqFv; keep Amp[0], Std[0] from model 1547 I0, S0 = [classes.get(0, x) for x in ('Amp', 'Std')] 1548 for c in self.meas.iterkeys(): 1549 if c == 0: continue # already handled above 1550 for V, m in self.meas[c].iteritems(): # class i>0 measurement, IeqFv; solve Cond, CondStd given baseline Amp,Std 1551 I, S = m 1552 dV = 1e-3 * (V - clsVrev(c)) 1553 classes.set(c, 'Cond', (I - I0) / dV) 1554 classes.set(c, 'CondStd', sqrt(max(0, S**2 - S0**2)) / dV) 1555 if (len(self.meas) > 1) and any(len(meas) > 1 for meas in self.meas.itervalues()): 1556 II, SS, DV, CC = [], [], [], [] # more measurements than params, IeqFv; given estimates above, 1557 for c,m in self.meas.iteritems(): # curve-fit Amp[0], Std[0], and all measured classes' Cond, CondStd 1558 for v in m.iterkeys(): 1559 i, s = m[v] 1560 II.append(i) 1561 SS.append(s) 1562 DV.append(1e-3*(v - clsVrev(c))) 1563 CC.append(c) 1564 GG = [classes.get(c, 'Cond') for c in xrange(classes.size)] 1565 GS = [classes.get(c, 'CondStd') for c in xrange(classes.size)] 1566 GG, GS, I0, S0 = FitIeqFv(II, SS, DV, CC, GG, GS, I0, S0) 1567 classes.set(0, 'Amp', I0) 1568 classes.set(0, 'Std', max(0.0, S0)) 1569 for c in set(CC): 1570 classes.set(c, 'Cond', GG[c]) 1571 classes.set(c, 'CondStd', max(0.0, GS[c])) 1572 if self.meas: 1573 classes.select(sorted(self.meas.keys())[0], 'Cond', sender=self) 1574 else: # not IeqFv: apply each measurement to its class's Amp, Std 1575 for c, mean_std in self.meas.iteritems(): 1576 mean, std = mean_std 1577 classes.set(c, 'Amp', mean) 1578 classes.set(c, 'Std', std) 1579 if len(self.meas): 1580 classes.select(sorted(self.meas.keys())[0], 'Amp', sender=self) 1581 self.model.file.undoStack.seal_undo('Grab') 1582 try: 1583 self.model.remove_layer(self.legend) 1584 except: 1585 pass 1586 gobject.idle_add(self.space.set_layerset, self.prev_layerset) 1587 gobject.idle_add(self.space.set_tool, self.prev_tool)
1588 - def __measure(self, left, right, signal=None):
1589 """Returns the (mean, std) of signal, in the first onscreen segment, between left and right [seconds from segment start].""" 1590 DataSource = qubx.pyenv.env.globals['QubX'].DataSource 1591 sig = signal 1592 if sig is None: 1593 sig = DataSource.signal 1594 segi = self.space.time.Iseg 1595 seg = self.space.get_segmentation(first_seg=segi, last_seg=segi, left=left, right=right, signal=sig)[0] 1596 return self.__measure_seg(seg)
1597 - def __measure_seg(self, seg):
1598 mean_sum = std_sum = n_sum = 0.0 1599 for chunk in qubx.data_types.generate_chunk_samples(seg.chunks): 1600 if chunk.included: 1601 mean_sum += chunk.n * chunk.samples.mean() 1602 std_sum += chunk.n * chunk.samples.std()**2 1603 n_sum += chunk.n 1604 if n_sum: 1605 return (seg.f, seg.l, mean_sum/n_sum, sqrt(std_sum/n_sum))
1606 - def __show_confirm(self, msg, commit):
1607 """Displays the string msg for 2sec, with a Cancel button, then calls commit() unless Canceled.""" 1608 self.conf_msg = msg 1609 self.conf_action = commit 1610 self.space.redraw_canvas(False) 1611 self.conf_timer = gobject.timeout_add(100, self.__onTimeout) # was 2200, now effectively disabled
1612 - def __onTimeout(self):
1613 self.conf_timer = None 1614 if self.conf_msg: 1615 self.conf_action() 1616 self.conf_msg = self.conf_action = None 1617 self.space.redraw_canvas(False)
1618 - def onOverlay(self, context, w, h):
1619 qubx.dataGTK.QubData_SelTool.onOverlay(self, context, w, h) 1620 em = self.space.appearance.emsize 1621 boxw = 45 * em 1622 boxh = 8 * em 1623 boxx = em # (w - boxw) / 2 1624 boxy = em 1625 context.set_line_width(.5) 1626 context.set_source_rgba(* self.space.appearance.color(qubx.toolspace.LAYER_BG)) 1627 context.rectangle(boxx, boxy, boxw, boxh) 1628 context.fill() 1629 context.set_source_rgba(* self.space.appearance.color(qubx.toolspace.LAYER_FG)) 1630 line = 'Highlight some data for class ' 1631 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(line) 1632 x0 = boxx + (boxw-width-2*em)/2 - xbearing + em 1633 context.move_to(x0, boxy + 2.75*em) 1634 context.show_text(line) 1635 context.save() 1636 context.rectangle(x0+width+em, boxy+1.5*em, 1.5*em, 1.5*em) 1637 context.set_source_rgba(* self.space.appearance.color(COLOR_CLASS(self.cls_now))) 1638 context.fill_preserve() 1639 context.set_source_rgba(1,1,1,1) 1640 context.stroke() 1641 context.restore() 1642 if self.model.file.IeqFv and (not self.conf_msg) and (self.cls_now in self.meas): 1643 line = 'at a different Voltage than %.3g' % self.meas[self.cls_now].keys()[0] 1644 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(line) 1645 x0 = boxx + (boxw-width)/2 - xbearing 1646 context.move_to(x0, boxy + 4.75*em) 1647 context.show_text(line) 1648 if self.conf_msg: 1649 context.move_to(boxx + em, boxy + 6.55*em) 1650 context.show_text(self.conf_msg) 1651 context.save() 1652 self.cancelRect = (boxx+28*em, boxy+5*em, 5*em, 2*em) 1653 context.rectangle(*self.cancelRect) 1654 context.stroke_preserve() 1655 context.set_source_rgba(*(self.canceling and (.3,0,0,1) or (0,0,.3,.8))) 1656 context.fill() 1657 context.restore() 1658 line = 'Cancel' 1659 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(line) 1660 x0 = boxx + 28*em + (5*em - width)/2 - xbearing 1661 context.move_to(x0, boxy + 6.55*em) 1662 context.show_text(line) 1663 else: 1664 self.cancelRect = self.noRect 1665 if self.cls == -1: 1666 context.save() 1667 self.skipRect = (boxx+34*em, boxy+5*em, 4*em, 2*em) 1668 context.rectangle(*self.skipRect) 1669 context.stroke_preserve() 1670 context.set_source_rgba(*(self.skipping and (.3,0,0,1) or (0,0,.3,.8))) 1671 context.fill() 1672 context.restore() 1673 line = 'Skip' 1674 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(line) 1675 x0 = boxx + 34*em + (4*em - width)/2 - xbearing 1676 context.move_to(x0, boxy + 6.55*em) 1677 context.show_text(line) 1678 else: 1679 self.skipRect = self.noRect 1680 context.save() 1681 self.doneRect = (boxx+39*em, boxy+5*em, 4*em, 2*em) 1682 context.rectangle(*self.doneRect) 1683 context.stroke_preserve() 1684 context.set_source_rgba(*(self.done_ing and (.3,0,0,1) or (0,0,.3,.8))) 1685 context.fill() 1686 context.restore() 1687 line = 'Done' 1688 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(line) 1689 x0 = boxx + 39*em + (4*em - width)/2 - xbearing 1690 context.move_to(x0, boxy + 6.55*em) 1691 context.show_text(line)
1692 - def onPress(self, x, y, e):
1693 """Intercepts button clicks; highlights like QubData_SelTool.""" 1694 if pt_in_rect(x, y, self.cancelRect): 1695 self.canceling = True 1696 self.space.redraw_canvas(False) 1697 if self.conf_timer: 1698 gobject.source_remove(self.conf_timer) 1699 self.conf_timer = None 1700 elif pt_in_rect(x, y, self.doneRect): 1701 self.done_ing = True 1702 self.space.redraw_canvas(False) 1703 if self.conf_timer: 1704 gobject.source_remove(self.conf_timer) 1705 self.conf_timer = None 1706 self.__onTimeout() 1707 elif pt_in_rect(x, y, self.skipRect): 1708 self.skipping = True 1709 self.space.redraw_canvas(False) 1710 else: 1711 qubx.dataGTK.QubData_SelTool.onPress(self, x, y, e)
1712 - def onRelease(self, x, y, e):
1713 """Intercepts button clicks; highlights like QubData_SelTool.""" 1714 if self.canceling: 1715 self.canceling = False 1716 if pt_in_rect(x, y, self.cancelRect): 1717 self.conf_msg = self.conf_action = None 1718 else: 1719 self.conf_timer = gobject.timeout_add(1000, self.__onTimeout) 1720 self.space.redraw_canvas(False) 1721 elif self.done_ing: 1722 self.done_ing = False 1723 if pt_in_rect(x, y, self.doneRect): 1724 self.__onDone() 1725 self.space.redraw_canvas(False) 1726 elif self.skipping: 1727 self.skipping = False 1728 if pt_in_rect(x, y, self.skipRect): 1729 try: 1730 self.cls_now = self.clss_iter.next() 1731 except StopIteration: 1732 if self.model.file.IeqFv and (self.passno == 0): 1733 self.passno = 1 1734 self.clss_iter = iter(self.clss) 1735 self.cls_now = self.clss_iter.next() 1736 else: 1737 self.__onDone() 1738 self.space.redraw_canvas(False) 1739 else: 1740 qubx.dataGTK.QubData_SelTool.onRelease(self, x, y, e)
1741 - def onDrag(self, x, y, e):
1742 """Intercepts button clicks; highlights like QubData_SelTool.""" 1743 if not (self.canceling or self.skipping or self.done_ing): 1744 qubx.dataGTK.QubData_SelTool.onDrag(self, x, y, e)
1745
1746 - def regrab(self):
1747 view = qubx.global_namespace.QubX.Data.view 1748 cc = self.model.file.classes 1749 sig = qubx.global_namespace.QubX.DataSource.signal 1750 segi = view.time.Iseg 1751 for sel in view.file.lists.by_name('Model-Grab'): 1752 if sel.Class < len(cc): 1753 seg = view.get_segmentation_indexed(sel.From, sel.To, sig)[0] 1754 f, l, mean, std = self.__measure_seg(seg) 1755 cc[sel.Class, 'Amp'] = mean 1756 cc[sel.Class, 'Std'] = std
1757
1758 -def can_regrab(model):
1759 if model.file.IeqFv: 1760 return False 1761 try: 1762 lists = qubx.global_namespace.QubX.Data.file.lists 1763 return len(lists.lists[lists.index_of('Model-Grab')]) 1764 except: 1765 return False
1766
1767 1768 -def pt_in_rect(x, y, r):
1769 """Returns True if the point (x, y) is within the rect r=(x0, y0, w, h).""" 1770 return (r[0] <= x < (r[0]+r[2])) and (r[1] <= y < (r[1]+r[3]))
1771
1772 -def FitIeqFv(II, SS, DV, CC, GG, GS, I0, S0):
1773 """Finds the baseline current and per-class conductance which best fit current/voltage measurements. 1774 1775 @param II: list of current-mean measurements [pA] 1776 @param SS: list of current-standard-deviation measurements [pA] 1777 @param DV: list of (V - Vrev) measurements [Volts] 1778 @param CC: list of the model class of each measurement 1779 @param GG: list of starting values for conductance, per model class [pS] 1780 @param GS: list of starting values for conductance standard deviation [pS] 1781 @param I0: mean baseline current [pA] 1782 @param S0: standard deviation of baseline current [pA] 1783 @returns: (GG, GS, I0, S0) after nonlinear least squares fitting 1784 """ 1785 ncls = len(CC) 1786 fit = qubx.fit.Simplex_LM_Fitter(max_iter=500) 1787 # fit II and SS together, as a function of DV and CC 1788 Y = numpy.array(list(II)+list(SS), dtype=numpy.float32) 1789 X = numpy.array(list(DV)+list(DV), dtype=numpy.float32) 1790 C = list(CC) + list(CC) 1791 class VCurve(qubx.fit.BaseCurve): 1792 """params are alternating elements of GG and GS, followed by I0 and S0 (GG[0], GS[0], GG[1], GS[1], ..., I0, S0).""" 1793 def __init__(self): 1794 qubx.fit.BaseCurve.__init__(self) 1795 self.params = [str(i) for i in xrange(2*(ncls+1))] 1796 self.lo = [UNSET_VALUE for i in xrange(2*(ncls+1))] 1797 self.hi = [UNSET_VALUE for i in xrange(2*(ncls+1))] 1798 self.can_fit = [(((i/2)>=ncls) or ((i/2) in CC)) for i in xrange(2*(ncls+1))]
1799 def eval(self, param_vals, xx, vvv=[]): 1800 return numpy.array([(param_vals[-2] + xx[i]*param_vals[2*C[i]]) for i in xrange(len(xx)/2)] + 1801 [sqrt(param_vals[-1]**2 + (xx[i]*param_vals[2*C[i]+1])**2) for i in xrange(len(xx)/2, len(xx))]) 1802 param_vals = reduce(lambda x,y: x+list(y), itertools.izip(GG, GS), []) 1803 param_vals += [I0, S0] 1804 fp, ssr, iter, ff = fit(VCurve(), param_vals, X, Y) 1805 return (fp[:-2:2], fp[1:-2:2], fp[-2], fp[-1]) # G[c], GS[c], I0, S0 1806
1807 1808 -class QubModelCopyDialog(CopyDialog):
1809 """Modal window with settings for 'copy model image'."""
1810 - def __init__(self, parent=None):
1811 CopyDialog.__init__(self, 'Copy model image', parent) 1812 line = pack_item(gtk.HBox(True), self.vbox) 1813 pack_label('Zoom:', line, expand=True) 1814 self.txtZoom = pack_item(NumEntry(1.0, format='%.3g', width_chars=11), line, expand=True) 1815 self.chkOverlays = pack_check('overlays', self.vbox, active=True)
1816 - def run(self, view):
1817 response = CopyDialog.run(self, view) 1818 self.copy_zoom = self.txtZoom.value 1819 self.copy_overlays = self.chkOverlays.get_active() 1820 return response
1821 1822 TheCopyDialog = QubModelCopyDialog()
1823 1824 1825 -class IeqFvAboutBox(gtk.Window):
1826 - def __init__(self):
1827 gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) 1828 self.set_title('QUB Express - About I=f(V), conductance, and voltage') 1829 self.connect('delete_event', self.__onDelete) 1830 scr = gtk.ScrolledWindow() 1831 scr.set_size_request(500, 600) 1832 scr.show() 1833 self.txt = gtk.TextView() 1834 self.txt.set_editable(False) 1835 scr.add(self.txt) 1836 self.txt.show() 1837 self.add(scr) 1838 out = TextViewAppender(self.txt) 1839 out.write(""" About I=f(V), conductance, and Voltage 1840 1841 This checkbox chooses how to model current (I) from the ion channel. 1842 1843 In either case, the baseline is defined by the Amp and Std of Class 0 1844 (in the first row of the Classes table). Amp and Std have units of pA -- 1845 the same at all voltages. 1846 1847 baseline = Amp[0] 1848 baseline_std = Std[0] 1849 1850 When "I = f(V)" is un-checked, the unitary (single-channel) current 1851 doesn't vary with voltage. The current in class i is calculated as: 1852 1853 unitary_amp[i] = amp[i] - baseline_amp 1854 unitary_std[i] = sqrt(std[i]**2 - baseline_std**2) 1855 1856 When "I = f(V)" is checked, the unitary current is calculated from the 1857 conductance, in pS: 1858 1859 delta_v = (Voltage - vRev) * 1e-3 # converting from mV to V 1860 I = delta_v * conductance 1861 1862 "Voltage" is the name of a stimulus signal, or in the Constants table. 1863 Its units are mV. "vRev" is the reversal potential, in the Models table, 1864 also in units of mV. The conductance distribution is in the Classes table, 1865 in the columns "Cond" and "CondStd": 1866 1867 unitary_amp[i,v] = delta_v * Cond[i] 1868 unitary_std[i,v] = delta_v * CondStd[i] 1869 1870 You can override vRev, per class, in the Classes table. 1871 1872 """)
1873 - def __onDelete(self, win, evt):
1874 self.hide() 1875 return True # do nothing more
1876 1877 TheIeqFvAboutBox = IeqFvAboutBox()
1878 1879 -class ModelMerge_Dialog(gtk.Dialog):
1880 - def __init__(self):
1881 gtk.Dialog.__init__(self, "QUB Express - Model Merge", qubx.GTK.get_active_window(), gtk.DIALOG_MODAL, 1882 buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)) 1883 self.set_size_request(600, 250) 1884 self.set_default_response(gtk.RESPONSE_ACCEPT) 1885 pack_label('Model Merge builds the cartesian product of two or more models, with constrained rates.', self.vbox) 1886 self.lstModels = pack_item(qubx.GTK.CountList(['Count', 'Model']), self.vbox, expand=True) 1887 for view in qubx.global_namespace.QubX.Models.views: 1888 if view.file.group == 0: 1889 count = 0 1890 elif 0 < view.file.channelCount < 6: 1891 count = view.file.channelCount 1892 else: 1893 count = 1 1894 self.lstModels.append(count, view.file.path)
1895 - def run(self):
1896 if gtk.RESPONSE_ACCEPT == gtk.Dialog.run(self): 1897 return self.lstModels.get_entries() 1898 return []
1899 1900 ModelMerge_index = 0
1901 1902 -def ModelMerge(item=None, count_paths=None, merged_name=None):
1903 global ModelMerge_index 1904 if merged_name is None: 1905 ModelMerge_index += 1 1906 name = 'Merged %i' % ModelMerge_index 1907 else: 1908 name = merged_name 1909 if count_paths is None: 1910 dlg = ModelMerge_Dialog() 1911 pcs = dlg.run() 1912 dlg.destroy() 1913 qubx.pyenv.env.OnScriptable('qubx.modelGTK.ModelMerge(count_paths=%s, merged_name=%s)' % (repr(pcs), repr(name))) 1914 return ModelMerge(count_paths=pcs, merged_name=name) 1915 factors = [] 1916 if len(count_paths) == len(qubx.global_namespace.QubX.Models.views): 1917 # usually congruent to list of open files; this is more tolerant of duplicate file paths e.g. Untitled 1918 for cp, view in izip(count_paths, qubx.global_namespace.QubX.Models.views): 1919 count, path = cp 1920 if count: 1921 factors.extend(count * [view.file.as_tree()]) 1922 else: 1923 # could be in arbitrary order, as long as they're already open 1924 for count, path in count_paths: 1925 if count > 0: 1926 for view in qubx.global_namespace.QubX.Models.views: 1927 if view.file.path == path: 1928 factors.extend(count * [view.file.as_tree()]) 1929 break 1930 else: 1931 print 'Model Merge: source model is not open; skipping... (%s)' % path 1932 if not factors: 1933 return 1934 merged = qubopt.model.MergeModels(factors) 1935 qubx.global_namespace.QubX.Models.new() 1936 qubx.global_namespace.QubX.Models.file.path = name 1937 qubx.global_namespace.QubX.Models.file.from_tree(merged)
1938 1939 Tools.register('other', 'Model Merge...', ModelMerge)
1940 1941 1942 -def CopyModelTree(item):
1943 qubx.pyenv.env.OnScriptable('QubX.Models.view.copy_tree()') 1944 qubx.global_namespace.QubX.Models.view.copy_tree()
1945
1946 -def PasteModelTree(item):
1947 view = qubx.global_namespace.QubX.Models.view 1948 if view.file and qubx.treeGTK.CanPaste(): 1949 qubx.pyenv.env.OnScriptable('QubX.Models.view.paste_tree()') 1950 view.paste_tree()
1951
1952 -def PasteNewModel(item):
1953 Models = qubx.global_namespace.QubX.Models 1954 Models.new() 1955 qubx.pyenv.env.OnScriptable('QubX.Models.view.paste_tree()') 1956 Models.view.paste_tree()
1957 1958 Tools.register('other', 'Copy model', CopyModelTree) 1959 Tools.register('other', 'Paste model', PasteModelTree) 1960 Tools.register('other', 'Paste as new model', PasteNewModel) 1961 1962 1963 @Propertied(Property('color', 1, 'False for grayscale'), 1964 Property('auto_size', 1, 'False to use width, height'), 1965 Property('width', 900, 'Output width if not auto_size'), 1966 Property('height', 600, 'Output height if not auto_size'), 1967 Property('rate_labels', 1, 'True to write numbers along arrows'), 1968 Property('effective_rate_labels', 1, 'True to write effective K above stimulus-dependent rates'), 1969 Property('rate_font', 0, 'True to override system font size'), 1970 Property('rate_font_size', 16, 'Size of rate font, if rate_font is True'), 1971 Property('arrows_proportional', 0, 'True to draw arrows wider for faster rates'), 1972 Property('arrows_proportional_log', 1, 'False to scale arrow width linearly with rate'), 1973 Property('arrows_proportional_min', 0.1, 'Rate corresponding to narrowest arrow width'), 1974 Property('arrows_proportional_max', 10000, 'Rate corresponding to widest arrow width'), 1975 Property('state_labels', 1, 'True to write numbers or labels of states in boxes'), 1976 Property('state_font', 0, 'True to override system font size'), 1977 Property('state_font_size', 16, 'Size of state font, if state_font is True'), 1978 Property('state_boxes', 1, 'True to draw rectangles around states'), 1979 Property('state_fill', 1, 'True to color the background of state rectangles'), 1980 Property('states_proportional', 0, 'True to draw states in proportion to occupancy probability'), 1981 Property('states_proportional_log', 0, 'True: logarithmic, False: linear scale'), 1982 Property('states_proportional_min', 1e-5, 'occupancy corresponding to smallest state size'), 1983 Property('states_proportional_max', 1.0, 'occupancy corresponding to largest state size'), 1984 Property('states_proportional_Peq', 1.0, "True: equilibrium; False: States table's Pr column"), 1985 Property('states_proportional_min_size', 50.0, 'Percent of ordinary state size corresponding to 0.0 occupancy'), 1986 Property('states_proportional_max_size', 150.0, 'Percent of ordinary state size corresponding to 1.0 occupancy'), 1987 Property('states_proportional_font', 1, 'False to vary only the state box size'))
1988 -class ModelNbPicturePrefs(gtk.Dialog):
1989 __explore_featured = ['global_name', 'running', 'run']
1990 - def __init__(self, parent, global_name=""):
1991 gtk.Dialog.__init__(self, 'Model - Notebook Picture Output Options', parent, gtk.DIALOG_MODAL, buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, 1992 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, 1993 gtk.STOCK_APPLY, gtk.RESPONSE_APPLY)) 1994 self.__ref = Reffer() 1995 self.global_name = global_name 1996 self.propertied_connect_settings('ModelNbPicture') 1997 self.set_default_response(gtk.RESPONSE_ACCEPT) 1998 self.running = False 1999 h = pack_item(gtk.HBox(), self.vbox) 2000 chk = pack_check('Color', h, expand=True) 2001 self.propertied_connect_check('color', chk) 2002 h = pack_item(gtk.HBox(), self.vbox) 2003 chk = pack_check('Auto size', h) 2004 self.propertied_connect_check('auto_size', chk) 2005 pack_label('or Width:', h) 2006 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), h) 2007 self.propertied_connect_NumEntry('width', txt) 2008 pack_label('Height:', h) 2009 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), h) 2010 self.propertied_connect_NumEntry('height', txt) 2011 h = pack_item(gtk.HBox(), self.vbox) 2012 chk = pack_check('Rate labels', h, expand=True) 2013 self.propertied_connect_check('rate_labels', chk) 2014 h = pack_item(gtk.HBox(), self.vbox) 2015 chk = pack_check('Effective K rate labels (for stimulus-dependent rates)', h, expand=True) 2016 self.propertied_connect_check('effective_rate_labels', chk) 2017 h = pack_item(gtk.HBox(), self.vbox) 2018 chk = pack_check('Rate font size:', h) 2019 self.propertied_connect_check('rate_font', chk) 2020 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), h) 2021 self.propertied_connect_NumEntry('rate_font_size', txt) 2022 h = pack_item(gtk.HBox(), self.vbox) 2023 chk = pack_check('Arrow width proportional to rate', h) 2024 self.propertied_connect_check('arrows_proportional', chk) 2025 chk = pack_check('log', h) 2026 self.propertied_connect_check('arrows_proportional_log', chk) 2027 pack_label('in range from:', h) 2028 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h) 2029 self.propertied_connect_NumEntry('arrows_proportional_min', txt) 2030 pack_label('to:', h) 2031 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h) 2032 self.propertied_connect_NumEntry('arrows_proportional_max', txt) 2033 h = pack_item(gtk.HBox(), self.vbox) 2034 chk = pack_check('State labels', h, expand=True) 2035 self.propertied_connect_check('state_labels', chk) 2036 h = pack_item(gtk.HBox(), self.vbox) 2037 chk = pack_check('State font size:', h) 2038 self.propertied_connect_check('state_font', chk) 2039 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), h) 2040 self.propertied_connect_NumEntry('state_font_size', txt) 2041 h = pack_item(gtk.HBox(), self.vbox) 2042 chk = pack_check('State area proportional to occupancy probability', h) 2043 self.propertied_connect_check('states_proportional', chk) 2044 chk = pack_check('vary font size', h) 2045 self.propertied_connect_check('states_proportional_font', chk) 2046 chk = pack_check('use Peq', h) 2047 chk.set_tooltip_text("use equilibrium probability (otherwise, use States table's Pr column)") 2048 self.propertied_connect_check('states_proportional_Peq', chk) 2049 h = pack_item(gtk.HBox(), self.vbox) 2050 tip = "as a percentage of ordinary state size" 2051 pack_label('scale % from', h) 2052 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1000.0), '%.1f', width_chars=6), h) 2053 txt.set_tooltip_text(tip) 2054 self.propertied_connect_NumEntry('states_proportional_min_size', txt) 2055 pack_label('to', h) 2056 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1000.0), '%.1f', width_chars=6), h) 2057 txt.set_tooltip_text(tip) 2058 self.propertied_connect_NumEntry('states_proportional_max_size', txt) 2059 chk = pack_check('log in range from', h) 2060 chk.set_tooltip_text("on a logarithmic scale (otherwise, scale them linearly in 0..1)") 2061 self.propertied_connect_check('states_proportional_log', chk) 2062 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1000.0), '%.2g', width_chars=6), h) 2063 self.propertied_connect_NumEntry('states_proportional_min', txt) 2064 pack_label('to', h) 2065 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1000.0), '%.2g', width_chars=6), h) 2066 self.propertied_connect_NumEntry('states_proportional_max', txt) 2067 h = pack_item(gtk.HBox(), self.vbox) 2068 chk = pack_check('State boxes', h, expand=True) 2069 self.propertied_connect_check('state_boxes', chk) 2070 h = pack_item(gtk.HBox(), self.vbox) 2071 chk = pack_check('State background fill', h, expand=True) 2072 self.propertied_connect_check('state_fill', chk) 2073 h = pack_item(gtk.HBox(), self.vbox) 2074 pack_label('(preview output in the Models panel)', h) 2075 pack_item(qubx.settingsGTK.PresetsMenu('ModelNbPicture', qubx.global_namespace.QubX.appname), h, at_end=True)
2076 - def propertied_set(self, value, name):
2077 super(ModelNbPicturePrefs, self).propertied_set(value, name) 2078 if self.running: 2079 qubx.global_namespace.QubX.Models.view.redraw_canvas(True)
2080 - def run(self):
2081 self.running = True 2082 prefs_in = qubx.settings.SettingsMgr['ModelNbPicture'].active.clone() # to undo if they Cancel 2083 try: 2084 response = gtk.RESPONSE_APPLY 2085 while response == gtk.RESPONSE_APPLY: 2086 qubx.global_namespace.QubX.Models.view.redraw_canvas(True) 2087 response = gtk.Dialog.run(self) 2088 if response == gtk.RESPONSE_REJECT: 2089 qubx.settings.SettingsMgr['ModelNbPicture'].setProperties(prefs_in) 2090 break 2091 finally: 2092 self.running = False 2093 qubx.global_namespace.QubX.Models.view.redraw_canvas(True) 2094 self.hide() 2095 return response
2096
2097 2098 -def iterstim(**stim):
2099 names = [] 2100 vals = [] 2101 for k,v in stim.iteritems(): 2102 names.append(k) 2103 try: 2104 v[0] 2105 vals.append(v) 2106 except: 2107 vals.append([v]) 2108 for vv in itertools.product(*vals): 2109 yield dict((names[i], v) for i,v in enumerate(vv))
2110
2111 -def iter_stim_Pe(modelview, **stim):
2112 defaults = modelview.get_stimulus(qubx.global_namespace.QubX.Data.view) 2113 for ss in iterstim(**stim): 2114 values = defaults.copy() 2115 values.update(ss) 2116 Q = qubx.model.ModelToQ(modelview.file, values) 2117 Pe = qubx.model.QtoPe(Q) 2118 yield Pe, values
2119
2120 -def iter_Pe_of_state(modelview, state, **stim):
2121 for Pe, values in iter_stim_Pe(modelview, **stim): 2122 yield Pe[state], values
2123
2124 -def iter_Pe_of_class(modelview, cls, **stim):
2125 states = modelview.file.states 2126 clazz = numpy.array([states[i, 'Class'] for i in xrange(states.size)]) 2127 for Pe, values in iter_stim_Pe(modelview, **stim): 2128 yield numpy.sum(Pe[clazz==cls]), values
2129
2130 -def calc_Pe_of_stim(modelview, stim, idx, out_name="Peq", calc=iter_Pe_of_class):
2131 """Calculates equilibrium probability at various combinations of stimulus. 2132 @param modelview: e.g. QubX.Models.view 2133 @param stim: dict(stim_name -> value or list of values); will evaluate the cartesian product of all values 2134 @param idx: Index of class or state 2135 @param calc: interpret idx as class (default), or as state with calc=iter_Pe_of_state 2136 @return: list of dict, with entries for out_name and each stim_name, corresponding to cartesian product of all requested conditions 2137 """ 2138 def pack(Pe, values): 2139 values[out_name] = Pe 2140 return values
2141 return [pack(*item) for item in calc(modelview, idx, **stim)] 2142
2143 -def find_ec50(modelview, stim_name, idx, calc=iter_Pe_of_class):
2144 def to_minimize(p): 2145 Pe, values = calc(modelview, idx, **{stim_name:p[0]}).next() 2146 return (Pe - 0.5)**2
2147 p = [1.0] 2148 result = scipy.optimize.minimize(to_minimize, p) 2149 if result.success: 2150 return result.x[0] 2151 else: 2152 print result.message 2153 return None 2154