| Trees | Indices | Help |
|
|---|
|
|
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
81 """Returns the Euclidean distance between (x1, y1) and (x2, y2)."""
82 return sqrt((x2 - x1)**2 + (y2 - y1)**2)
83
87
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
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
209
210 file = property(lambda self: self._file, lambda self, x: self.set_file(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()
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
271 self.redraw_canvas()
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()
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])
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()
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
326 if self.models_table:
327 self.models_table.select(self.models_table.entries.index(self.file), 'Channel Count', self)
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)
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
348 if self.file:
349 self.file.IeqFv = active
350 qubx.pyenv.env.OnScriptable('QubX.Models.file.IeqFv = %s' % repr(active))
352 TheIeqFvAboutBox.present()
357 if self.models_table:
358 self.models_table.select(self.models_table.entries.index(self.file), 'vRev', self)
360 if self.file and (self.file.undoStack == undoStack):
361 self.lblUndo.set_color( undoStack.can_undo and COLOR_BUTTON_TEXT or COLOR_BUTTON_GRAY )
362 self.lblRedo.set_color( undoStack.can_redo and COLOR_BUTTON_TEXT or COLOR_BUTTON_GRAY )
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
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
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)
668 for r in self._stateRects:
669 if r.contains(px, py):
670 return r.state, r
671 return None, None
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
679 pp = qubx.global_namespace.QubX.Models.nbPicturePrefs
680 if pp.auto_size:
681 return self.dim
682 return (pp.width, pp.height)
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
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
702 QubX = qubx.pyenv.env.globals['QubX']
703 return ModelToQ(self.file, self.get_stimulus( QubX.Data.view ))
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
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()]
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()]
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()]
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')
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')
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')
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')
775 self.file.states.set(index, 'Class', color)
776 self.file.undoStack.seal_undo('change state color')
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')
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
789 qubx.pyenv.env.OnScriptable('QubX.Model.file.balance_loops = %s' % repr(chk.get_active()))
790 self.file.balance_loops = chk.get_active()
791
795 """Basic mouse interaction for L{QubModelView}."""
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 = []
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'
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)
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)
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)
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)
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()
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)
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
957 self.space.file.states[index] = {'x': x, 'y': y}
958 self.space.file.undoStack.seal_undo('move state')
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)))
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)
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)
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))))
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)
1061 if self._menuWaiting:
1062 self._menuWaiting = IGNORE_MENU_RELEASE
1063 self.space.release_button(e)
1064 self.onNeedPopup(e.x, e.y, e)
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)
1073 self.space.del_rate(self.drag_rate)
1074 qubx.pyenv.env.OnScriptable('QubX.Models.view.del_rate(%i)' % self.drag_rate)
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')
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')
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')
1119 self.space.make_start_state(self.drag_state)
1120 qubx.pyenv.env.OnScriptable('QubX.Models.view.make_start_state(%i)' % self.drag_state)
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)
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)
1138 self.space.del_state(self.drag_state)
1139 qubx.pyenv.env.OnScriptable('QubX.Models.view.del_state(%i)' % self.drag_state)
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))
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))
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))
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')
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')
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')
1174 self.del_constraint_kin(c)
1175 qubx.pyenv.env.OnScriptable('QubX.Models.file.constraints_kin.remove(%i)' % c)
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({})')
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
1208 """Mouse interaction and overlay while drawing a connection between states."""
1213 self.rgns[:] = self.space._stateRects[:]
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()
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
1235 """Mouse interaction and overlay while picking a state's color."""
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
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
1308 """Mouse interaction and overlay while picking a rate to complete a Scale constraint."""
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
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])
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
1342
1345 """Mouse interaction and overlay while picking rates around a loop."""
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
1351 OverlayRgnCaptionedTool.onActivate(self)
1352 self.loop = []
1353 self.list_rates([self.a, self.b], [self.a, self.b])
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)
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
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 """
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))))
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})
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()
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))
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)
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)
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))
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
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)
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)
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)
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)
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
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
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
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
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
1809 """Modal window with settings for 'copy model image'."""
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)
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()
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 """)
1876
1877 TheIeqFvAboutBox = IeqFvAboutBox()
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)
1896 if gtk.RESPONSE_ACCEPT == gtk.Dialog.run(self):
1897 return self.lstModels.get_entries()
1898 return []
1899
1900 ModelMerge_index = 0
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)
1943 qubx.pyenv.env.OnScriptable('QubX.Models.view.copy_tree()')
1944 qubx.global_namespace.QubX.Models.view.copy_tree()
1945
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
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'))
1989 __explore_featured = ['global_name', 'running', 'run']
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)
2077 super(ModelNbPicturePrefs, self).propertied_set(value, name)
2078 if self.running:
2079 qubx.global_namespace.QubX.Models.view.redraw_canvas(True)
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
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
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
2123
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
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
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
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Dec 15 19:07:38 2017 | http://epydoc.sourceforge.net |