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

Source Code for Module qubx.simulate

   1  """Panel to control and orchestrate simulation and protocols. 
   2   
   3  Copyright 2008-2014 Research Foundation State University of New York  
   4  This file is part of QUB Express.                                           
   5   
   6  QUB Express is free software; you can redistribute it and/or modify           
   7  it under the terms of the GNU General Public License as published by  
   8  the Free Software Foundation, either version 3 of the License, or     
   9  (at your option) any later version.                                   
  10   
  11  QUB Express is distributed in the hope that it will be useful,                
  12  but WITHOUT ANY WARRANTY; without even the implied warranty of        
  13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
  14  GNU General Public License for more details.                          
  15   
  16  You should have received a copy of the GNU General Public License,    
  17  named LICENSE.txt, in the QUB Express program directory.  If not, see         
  18  <http://www.gnu.org/licenses/>.                                       
  19   
  20  """ 
  21   
  22  from itertools import izip 
  23  from qubx.model import * 
  24  from qubx.faces import * 
  25  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem 
  26  from qubx.toolspace import * 
  27  from qubx.simulation_protocol import * 
  28  from qubx.table import * 
  29  from qubx.tableGTK import * 
  30  from qubx.task import * 
  31  import qubx.pyenv 
  32  from numpy import * 
  33  from qubx.maths import * 
  34  import qubx.settings 
  35  from qubx.settings import Property, Propertied 
  36  import qubx.settingsGTK 
  37  import qubx.fast.filter 
  38  import qubx.fast.simulate 
  39   
  40  CONSTBORDER=2 
  41  COLOR_CONSTBG=('qubx.simulate.constbg', (0,0,1,.2)) 
  42  ColorInfo[COLOR_CONSTBG[0]].label = 'Simulation constants background' 
  43  COLOR_CONSTBORDER=('qubx.simulate.constborder', (1,1,1,.6)) 
  44  ColorInfo[COLOR_CONSTBORDER[0]].label = 'Simulation constants border' 
  45   
  46  Tools = ToolRegistry() # 'presets' 
47 48 49 -class SimuFiles(object):
50 """ 51 Provides a rotating collection of "scratch" trees for simulated data. 52 Was going to serve files on disk, but it was crash-prone.""" 53
54 - def __init__(self, path):
55 self.path = path 56 if not os.path.exists(path): 57 os.makedirs(path) 58 self.avail = Queue.Queue() 59 self.ready = Queue.Queue() 60 self.next_i = 0
61 - def get_fresh(self, root_name):
62 try: 63 name = self.avail.get(True, 0.1) 64 except: 65 name = os.path.join(self.path, '%d.qubsim'%self.next_i) 66 self.next_i += 1 67 try: 68 if os.path.exists(name): 69 os.remove(name) 70 tree = qubx.tree.Node(root_name) 71 #tree.saveAs(name) 72 return tree 73 except: 74 return self.get(root_name)
75 - def put_ready(self, tree):
76 #tree.re_map_data() 77 self.ready.put(tree)
78 - def get_ready(self):
79 return self.ready.get()
80 - def put_done(self, tree):
81 self.avail.put(tree.path)
82
83 84 85 -def BuildShape(node):
86 """Returns a shape of the appropriate class from L{qubx.simulation_protocol}, initialized from a L{qubx.tree.Node_numpy}.""" 87 allowed = [ShapeClass.name for ShapeClass in Shapes] 88 return Shapes[allowed.index(str(node.data))](node)
89
90 91 -class SimulationFace(Face):
92 """Panel to control and orchestrate HMM simulation. 93 94 @ivar sampling_khz: sampling rate in kHz 95 @ivar duration: length of each segment, in milliseconds 96 @ivar segment_count: number of segments to simulate 97 @ivar usePeq: (bool) True to start at equilibrium; False to start at model.states:Pr 98 @ivar reset_prob: (bool) False to carry over occupancy probability across segments 99 @ivar shuffle: generate stimulus segments out of order 100 @ivar force_Q: use Q matrix method (instead of A matrix) even if it would be slower 101 @ivar continuous: (bool) True to start a new simulation as soon as the last one ends 102 @ivar paused: (bool) True to prevent re-simulation 103 @ivar robot: L{qubx.task.Robot} - worker thread 104 @ivar protocol: L{SimProtocol} 105 @ivar constants: L{SimConstants} 106 @ivar OnFinish: L{WeakEvent}() 107 """ 108 109 __explore_featured = ['sampling_khz', 'duration', 'segment_count', 'usePeq', 'reset_prob', 'shuffle', 'force_Q', 'continuous', 'paused', 110 'robot', 'protocol', 'constants', 'files', 'noise', 'model', 'update', 'get_stimulus', 'OnFinish'] 111
112 - def __init__(self, name='Simulation', global_name='QubX.Simulation'):
113 Face.__init__(self, name, global_name) 114 self.__ref = Reffer() 115 self.__state = qubx.settings.SettingsMgr['Simulation'].active 116 qubx.settings.SettingsMgr['Simulation'].OnSet = self.__ref(self.__onSettings) 117 self.__sampling = self.__state['Sampling kHz'] 118 if not self.__sampling.data: 119 self.__sampling.data = 10.0 120 self.__segcount = self.__state['RepeatTimes'] 121 if not self.__segcount.data: 122 self.__segcount.data = 1 123 self.__duration = self.__state['Duration'] 124 if not self.__duration.data: 125 self.__duration.data = 1000.0 126 self.__usePeq = self.__state['Use Peq'] 127 if not self.__usePeq.data: 128 self.__usePeq.data = 0 129 self.__reset_prob = self.__state['reset_prob'] 130 if not self.__reset_prob.data: 131 self.__reset_prob.data = 1 132 self.__shuffle = self.__state['Randomize'] 133 if not self.__shuffle.data: 134 self.__shuffle.data = 0 135 self.__force_Q = self.__state['ForceQ'] 136 if not self.__force_Q.data: 137 self.__force_Q.data = 0 138 self.__really_big = self.__state['ReallyBig'] 139 if not self.__really_big.data: 140 self.__really_big.data = 100000000 141 self.__continuous = self.__paused = False 142 self.files = SimuFiles( os.path.join(qubx.pyenv.env.folder, 'Simulations') ) 143 self.QubX = qubx.pyenv.env.globals['QubX'] 144 self.QubX.OnQuit += self.__ref(self.__onQuit) 145 self.robot = Robot('Simulate') 146 self.robot.OnException += self.__ref(self.__onException) 147 self.robot.OnInterrupt += self.__ref(self.__onInterrupt) 148 self.models = self.QubX.Models 149 self.models.OnSwitch += self.__ref(self.__onSwitchModel) 150 self.datas = self.QubX.Data 151 self.datas.OnSwitch += self.__ref(self.__onSwitchData) 152 self.__model = None 153 self.__update_on_show = True 154 self.__serial = 0 155 self.noise = SimNoiseDialog() 156 self.noise.OnChangeEnabled += self.__ref(self.__onChangeNoiseEnabled) 157 self.noise.OnChange += self.__ref(self.__onChangeNoise) 158 line = pack_item(gtk.HBox(), self) 159 pack_label('Sampling (kHz):', line) 160 self.txtSampling = pack_item(NumEntry(self.__sampling.data[0], acceptFloatGreaterThan(0.0), width_chars=5), line) 161 self.txtSampling.OnChange += self.__ref(self.__onChangeSampling) 162 pack_label('Segments:', line) 163 self.txtSegments = pack_item(NumEntry(self.__segcount.data[0], acceptIntGreaterThan(0), width_chars=4), line) 164 self.txtSegments.OnChange += self.__ref(self.__onChangeSegCount) 165 pack_label('Duration (ms):', line) 166 self.txtDuration = pack_item(NumEntry(self.__duration.data[0], acceptFloatGreaterThan(0.0), width_chars=7), line) 167 self.txtDuration.OnChange += self.__ref(self.__onChangeDuration) 168 self.btnAddStimulus = pack_button('Add Stimulus', line, at_end=True, on_click=self.__onClickAddStimulus) 169 self.btnNoise = pack_button('Edit noise...', line, at_end=True, on_click=self.__onClickEditNoise) 170 self.chkNoise = pack_check('Extra noise', line, at_end=True, active=self.noise.enabled, on_toggle=self.__onToggleNoise) 171 self.protocol = SimProtocol() 172 self.constants = pack_item(SimConstants(self.protocol), self) 173 self.constants.OnSet += self.__ref(self.__onEditModel) 174 self.constants.update(self.models.file) 175 pack_item(self.protocol, self, expand=True) 176 self.protocol.protocol = self.__state 177 self.protocol.segment_count = self.segment_count 178 self.protocol.OnEdit += self.__ref(self.__onEditProtocol) 179 line = pack_item(gtk.HBox(), self, at_end=True) 180 self.chkPeq = pack_check('Start at equilibrium', line, active=bool(self.__usePeq.data[0]), on_toggle=self.__onTogglePeq) 181 self.chkResetProb = pack_check('Reset entry prob. each segment', line, active=bool(self.__reset_prob.data[0]), on_toggle=self.__onToggleResetProb) 182 #self.chkShuffle = pack_check('Shuffle segments', line, active=bool(self.__shuffle.data[0]), on_toggle=self.__onToggleShuffle) 183 self.chkForceQ = pack_check('force Q', line, active=bool(self.__force_Q.data[0]), on_toggle=self.__onToggleForceQ) 184 self.chkForceQ.set_tooltip_text('By default, QUB Express may use the A matrix method for some models because it can be faster') 185 self.chkContinuous = pack_check('Non-stop', line, on_toggle=self.__onToggleContinuous) 186 self.chkPause = pack_check('pause', line, on_toggle=self.__onTogglePause) 187 self.preset_additions = [] 188 self.mnuPresets = pack_item(qubx.settingsGTK.PresetsMenu('Simulation', qubx.pyenv.env.globals['QubX'].appname, additions=self.preset_additions), 189 line, at_end=True) 190 gobject.timeout_add(20000, self.refresh_preset_additions) # update menu after plugins have loaded (and also onShow) 191 self.__onEditProtocol() 192 self.OnFinish = WeakEvent()
193 - def set_sampling_khz(self, x):
194 self.txtSampling.value = x 195 self.__sampling.data[0] = x 196 self.update()
197 sampling_khz = property(lambda self: self.__sampling.data[0], lambda self, x: self.set_sampling_khz(x))
198 - def set_duration(self, x):
199 self.txtDuration.value = x 200 self.__duration.data[0] = x 201 self.update()
202 duration = property(lambda self: self.__duration.data[0], lambda self, x: self.set_duration(x))
203 - def set_segment_count(self, x):
204 self.txtSegments.value = x 205 self.__segcount.data[0] = x 206 self.protocol.segment_count = x 207 self.update()
208 segment_count = property(lambda self: self.__segcount.data[0], lambda self, x: self.set_segment_count(x))
209 - def set_usePeq(self, x):
210 self.chkPeq.set_active(x) 211 self.__usePeq.data[0] = int(x) 212 self.update()
213 usePeq = property(lambda self: bool(self.__usePeq.data[0]), lambda self, x: self.set_usePeq(x))
214 - def set_reset_prob(self, x):
215 self.chkResetProb.set_active(x) 216 self.__reset_prob.data[0] = int(x) 217 self.update()
218 reset_prob = property(lambda self: bool(self.__reset_prob.data[0]), lambda self, x: self.set_reset_prob(x))
219 - def set_shuffle(self, x):
220 #self.chkShuffle.set_active(x) 221 self.__shuffle.data[0] = int(x) 222 self.update()
223 shuffle = property(lambda self: bool(self.__shuffle.data[0]), lambda self, x: self.set_shuffle(x))
224 - def set_force_Q(self, x):
225 self.chkForceQ.set_active(x) 226 self.__force_Q.data[0] = int(x) 227 self.update()
228 force_Q = property(lambda self: bool(self.__force_Q.data[0]), lambda self, x: self.set_force_Q(x))
229 - def set_continuous(self, x):
230 self.chkContinuous.set_active(x) 231 self.__continuous = x 232 if x: 233 self.update()
234 continuous = property(lambda self: self.__continuous, lambda self, x: self.set_continuous(x))
235 - def set_paused(self, x):
236 self.chkPause.set_active(x) 237 self.__paused = x 238 if not x: 239 self.update()
240 paused = property(lambda self: self.__paused, lambda self, x: self.set_paused(x))
241 - def onShow(self, showing):
242 if showing: 243 self.QubX.About.append_face(self.protocol.shape_properties) 244 self.QubX.About.show_face('Shape') 245 self.refresh_preset_additions() 246 else: 247 self.QubX.About.remove_face(self.QubX.About.index('Shape'))
248 - def refresh_preset_additions(self):
249 # list shared by Presets menu, can be modified in-place 250 additions = self.preset_additions 251 additions[:] = [] 252 tools = Tools.by_cat['presets'] 253 for label in sorted(tools.keys()): 254 action, tool_class = tools[label] 255 additions.append((label, action))
256 - def __onQuit(self):
257 self.robot.stop() 258 self.data = None 259 self.model = None
260 - def __onException(self, robot, typ, val, trace):
261 traceback.print_exception(typ, val, trace)
262 - def __onInterrupt(self, robot, cancel):
263 cancel() # no Keyboard exception thrown 264 self.robot.fastmodel.obj[0].stop_flag = 1
265 - def __onSettings(self, cat, updates):
266 if updates['Sampling kHz'].data: 267 self.sampling_khz = updates['Sampling kHz'].data[0] 268 if updates['RepeatTimes'].data: 269 self.segment_count = updates['RepeatTimes'].data[0] 270 if updates['Duration'].data: 271 self.duration = updates['Duration'].data[0] 272 if updates['Use Peq'].data: 273 self.usePeq = bool(updates['Use Peq'].data[0]) 274 if updates['reset_prob'].data: 275 self.reset_prob = bool(updates['reset_prob'].data[0]) 276 if updates['Randomize'].data: 277 self.shuffle = bool(updates['Randomize'].data[0]) 278 if updates['ForceQ'].data: 279 self.force_Q = bool(updates['ForceQ'].data[0]) 280 self.protocol.update_properties(updates)
281 - def __onChangeSampling(self, txt, val):
282 qubx.pyenv.env.OnScriptable('QubX.Simulation.sampling_khz = %s' % repr(val)) 283 self.sampling_khz = val
284 - def __onChangeDuration(self, txt, val):
285 qubx.pyenv.env.OnScriptable('QubX.Simulation.duration = %s' % repr(val)) 286 self.duration = val
287 - def __onChangeSegCount(self, txt, val):
288 qubx.pyenv.env.OnScriptable('QubX.Simulation.segment_count = %s' % repr(val)) 289 self.segment_count = val
290 - def __onTogglePeq(self, chk):
291 qubx.pyenv.env.OnScriptable('QubX.Simulation.usePeq = %s' % repr(chk.get_active())) 292 self.usePeq = chk.get_active()
293 - def __onToggleResetProb(self, chk):
294 qubx.pyenv.env.OnScriptable('QubX.Simulation.reset_prob = %s' % repr(chk.get_active())) 295 self.reset_prob = chk.get_active()
296 - def __onToggleShuffle(self, chk):
297 qubx.pyenv.env.OnScriptable('QubX.Simulation.shuffle = %s' % repr(chk.get_active())) 298 self.shuffle = chk.get_active()
299 - def __onToggleForceQ(self, chk):
300 qubx.pyenv.env.OnScriptable('QubX.Simulation.force_Q = %s' % repr(chk.get_active())) 301 self.force_Q = chk.get_active()
302 - def __onToggleContinuous(self, chk):
303 qubx.pyenv.env.OnScriptable('QubX.Simulation.continuous = %s' % repr(chk.get_active())) 304 self.continuous = chk.get_active()
305 - def __onTogglePause(self, chk):
306 qubx.pyenv.env.OnScriptable('QubX.Simulation.paused = %s' % repr(chk.get_active())) 307 self.paused = chk.get_active()
308 - def __onChangeNoiseEnabled(self):
309 self.chkNoise.set_active(self.noise.enabled) 310 self.update()
311 - def __onChangeNoise(self):
312 if self.noise.enabled: 313 self.update()
314 - def __onToggleNoise(self, chk):
315 active = chk.get_active() 316 qubx.pyenv.env.OnScriptable('QubX.Simulation.noise.enabled = %s' % repr(active)) 317 self.noise.enabled = active
318 - def __onClickEditNoise(self, btn):
319 self.noise.run()
320 - def __onClickAddStimulus(self, btn):
321 wanted_names = (set(self.model.get_stimulus().keys()) 322 - set([chan.label for chan in self.protocol.channels])) 323 if wanted_names: 324 name = iter(wanted_names).next() 325 else: 326 name = 'Click me' 327 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.add_channel(%s); QubX.Simulation.robot.sync()' % repr(name)) 328 self.protocol.add_channel(name)
329 - def __onEditProtocol(self):
330 if self.protocol.channel_count: 331 self.txtDuration.set_editable(False) 332 self.txtDuration.value = self.protocol.duration * 1e3 333 else: 334 self.txtDuration.set_editable(True) 335 self.txtDuration.value = self.duration 336 self.update()
337 - def __onSwitchModel(self, models, model):
338 self.model = model
339 - def __onSwitchData(self, datas, data):
340 if (self.datas.index == 0) and self.__update_on_show: 341 self.update()
342 - def set_model(self, x):
343 if x == self.__model: return 344 if self.__model: 345 for event in (self.__model.states.OnInsert, 346 self.__model.states.OnRemoved, 347 self.__model.rates.OnInsert, 348 self.__model.rates.OnRemoved, 349 self.__model.classes.OnSet, 350 self.__model.OnSetChannelCount, 351 self.__model.OnSetIeqFv, 352 self.__model.OnSetVRev): 353 event -= self.__ref(self.__onEditModel) 354 self.__model.rates.OnSet -= self.__ref(self.__onSetRate) 355 self.__model.states.OnSet -= self.__ref(self.__onSetState) 356 self.__model = x 357 if self.__model: 358 for event in (self.__model.states.OnInsert, 359 self.__model.states.OnRemoved, 360 self.__model.rates.OnInsert, 361 self.__model.rates.OnRemoved, 362 self.__model.classes.OnSet, 363 self.__model.OnSetChannelCount, 364 self.__model.OnSetIeqFv, 365 self.__model.OnSetVRev): 366 event += self.__ref(self.__onEditModel) 367 self.__model.states.OnSet += self.__ref(self.__onSetState) 368 self.__model.rates.OnSet += self.__ref(self.__onSetRate) 369 self.__onEditModel()
370 model = property(lambda self: self.__model, lambda self, x: self.set_model(x))
371 - def __onEditModel(self, *args):
372 self.constants.update(self.models.file) 373 if self.datas.index == 0: 374 self.update() 375 else: 376 self.__update_on_show = True
377 - def __onSetRate(self, index, field, val, prev, undoing):
378 if field != 'K': 379 self.__onEditModel()
380 - def __onSetState(self, index, field, val, prev, undoing):
381 if field in ('From', 'To', 'Class', 'Pr'): 382 self.__onEditModel()
383 - def __check_really_big(self, n, receiver):
384 if n >= self.__really_big.data[0]: 385 'really?' 386 nBytes = n*4 387 nK = nBytes / 1024 388 nM = nK / 1024 389 nG = nM / 1024 390 if nG: 391 s = "%.1f GiB" % (nG + (nM-nG*1024)/1024.0) 392 else: # elif nM: 393 s = "%.1f MiB" % (nM + (nK-nM*1024)/1024.0) 394 response = qubx.pyenvGTK.show_message("Requested simulation is really big (%s), continue?" % s, 395 gtk.BUTTONS_OK_CANCEL, "QUB Express - Simulation - Warning", self.parent_window) 396 if response != gtk.RESPONSE_OK: 397 'not' 398 receiver(False) 399 return 400 else: 401 'really.' 402 if self.__really_big.data[0] < 2**30: 403 self.__really_big.data[0] = 2 * self.__really_big.data[0] 404 else: 405 self.__really_big.data[0] = 2**30 406 receiver(True) 407 return
408 - def update(self):
409 """Requests re-simulation.""" 410 if not self.paused: 411 self.__update_on_show = False 412 self.__serial += 1 413 self.robot.do(self.robot_try_update, self.__serial)
414 - def robot_try_update(self, serial):
415 """Filters out extra requests (stale serial numbers) and calls robot_update().""" 416 if serial == self.__serial: # most recent 417 Tasks.add_task(self.robot) 418 try: 419 self.robot_update() 420 finally: 421 Tasks.remove_task(self.robot)
422 - def get_stimulus(self):
423 """ 424 Returns (names[Nsignal], counts[Nsegment], segments[Nsegment]), where segments[i][sig] is either a constant or an array of samples. 425 """ 426 names = ['Current'] + self.constants.names + [chan.label for chan in self.protocol.channels] 427 values = [0.0] + self.constants.values 428 if 0 == len(self.protocol.channels): 429 return names, [int(round(self.duration*self.sampling_khz))] * self.segment_count, [tuple(values)] * self.segment_count 430 counts = [] 431 segments = [] 432 samples = self.protocol.get_samples(1e-3/self.sampling_khz) 433 for seg in qubx.tree.children(samples, 'Segment'): 434 counts.append(seg['SampleCount'].data[0]) 435 segments.append(tuple(values + [signal.storage.data[:,0] for signal in qubx.tree.children(seg['Signals'])])) 436 return names, counts, segments
437 - def robot_update(self):
438 """Simulates on the worker thread and/or pool.""" 439 with self.robot.main_hold: 440 if (not self.model) or (not self.model.states): return 441 simfile = self.datas.views[0].file 442 vars, counts, stim_segs = self.get_stimulus() 443 v_signal = self.model.IeqFv and vars.index('Voltage') or 0 444 if self.shuffle: 445 re_ix = range(len(counts)) 446 random.shuffle(re_ix) 447 counts = [counts[i] for i in re_ix] 448 stim_segs = [stim_segs[i] for i in re_ix] 449 fastmodel = ModelToFast(self.model, vars) 450 self.robot.fastmodel = fastmodel 451 usePeq = self.usePeq 452 sampling_sec = 1e-3 / self.sampling_khz 453 Nsegment = self.segment_count 454 Nchannel = self.model.channelCount 455 vary = [chan.label for chan in self.protocol.channels] 456 reset_prob = self.reset_prob 457 force_Q = self.force_Q 458 noise_enabled, baselineFluct, baselineFluctN, baselineFluctLifetime, baselineFluctMaxAmp, \ 459 baselineDrift, baselineDriftSlope, baselineDriftIntercept, \ 460 baselinePeriodic, baselinePeriodicFreq, baselinePeriodicPhase, baselinePeriodicAmp, \ 461 baselineJumps, baselineJumpsAmpStd, \ 462 baselineJumpsFastW, baselineJumpsFast, baselineJumpsSlow, baselineJumpsLifetime, \ 463 baselineLGM, baselineLGM2State, baselineLGMProcStd, baselineLGMX0, baselineLGMX0p = \ 464 self.noise.enabled, self.noise.baselineFluct, self.noise.baselineFluctN, self.noise.baselineFluctLifetime, self.noise.baselineFluctMaxAmp, \ 465 self.noise.baselineDrift, self.noise.baselineDriftSlope, self.noise.baselineDriftIntercept, \ 466 self.noise.baselinePeriodic, self.noise.baselinePeriodicFreq, self.noise.baselinePeriodicPhase, self.noise.baselinePeriodicAmp, \ 467 self.noise.baselineJumps, self.noise.baselineJumpsAmpStd, \ 468 self.noise.baselineJumpsFastW, self.noise.baselineJumpsFast, self.noise.baselineJumpsSlow, self.noise.baselineJumpsLifetime, \ 469 self.noise.baselineLGM, self.noise.baselineLGM2State, self.noise.baselineLGMProcStd, self.noise.baselineLGMX0, self.noise.baselineLGMX0p 470 n = numpy.sum(counts) 471 if not self.robot.gui_call_recv(self.__check_really_big, n, self.robot)[0]: 472 print 'False!' 473 return 474 tree = self.files.get_fresh('QubData') 475 tree_done = False 476 try: 477 tree["Sampling"].data = sampling_sec 478 tree["SignalCount"].data = 1+len(vary) 479 tree_signals = tree["Signals"] 480 sig = tree_signals["Signal"] 481 sig["Name"].data = 'Current' 482 sig["Units"].data = 'pA' 483 for name in vary: 484 sig = tree_signals.append("Signal") 485 sig["Name"].data = name 486 sig['Units'].data = (name == 'Voltage') and 'mV' or '' 487 tree_segs = tree["Segments"] 488 tree_idl = tree["IdealSignals"] 489 sig_idl = tree_idl["IdealSignal"] 490 sig_idl["SignalIndex"].data = 0 491 sig_idl_segs = sig_idl['Segments'] 492 seg_idl = qubx.tree.NullNode() 493 seg_ix = 0 494 sam_ix = 0 495 samples_out = [] 496 seg = qubx.tree.NullNode() 497 for ct, stim in izip(counts, stim_segs): 498 seg = tree_segs.insert("Segment", seg) 499 samples = seg["Signals"]["Signal"]["Samples"] 500 samples.data.setup(qubx.tree.QTR_TYPE_FLOAT, ct, 1) 501 samples_out.append(samples.storage.data) 502 for c, nm in enumerate(vary): 503 samples = seg["Signals"].append("Signal")["Samples"] 504 samples.data.setup(qubx.tree.QTR_TYPE_FLOAT, ct, 1) 505 samples.storage.data[:,0] = stim[c-len(vary)] 506 seg["SampleCount"].data = ct 507 508 if noise_enabled: 509 qubx.fast.simulate.simulate_baselines(samples_out, sampling_sec, 510 baselineFluct, baselineFluctN, baselineFluctLifetime, baselineFluctMaxAmp, 511 baselineDrift, baselineDriftSlope, baselineDriftIntercept, 512 baselinePeriodic, baselinePeriodicFreq, baselinePeriodicPhase, baselinePeriodicAmp, 513 baselineJumps, baselineJumpsAmpStd, 514 baselineJumpsFastW, baselineJumpsFast, baselineJumpsSlow, baselineJumpsLifetime, 515 baselineLGM, baselineLGM2State, baselineLGMProcStd, baselineLGMX0, baselineLGMX0p) 516 517 start_states = zeros(shape=(Nchannel,), dtype='int32') 518 start_states -= 1 519 if reset_prob: 520 results = qubx.fast.simulate.simulate(fastmodel, start_states, True, usePeq, v_signal, sampling_sec, stim_segs, counts, samples_out, self.__robot_on_status, force_Q) 521 else: 522 results = [] 523 for ss, ct, sam in izip(stim_segs, counts, samples_out): 524 results.append(qubx.fast.simulate.simulate(fastmodel, start_states, True, usePeq, v_signal, sampling_sec, [ss], [ct], [sam], self.__robot_on_status, force_Q)[0]) 525 526 for result, ct in izip(results, counts): 527 states, st_counts, st_amp, st_std = result 528 seg_idl = sig_idl_segs.insert("Segment", seg_idl) 529 seg_idl.data = sam_ix, sam_ix + ct - 1 530 seg_idl['amp'].data.setup(qubx.tree.QTR_TYPE_DOUBLE, fastmodel.Nclass, 1) 531 amp = seg_idl['amp'].storage.data 532 seg_idl['sd'].data.setup(qubx.tree.QTR_TYPE_DOUBLE, fastmodel.Nclass, 1) 533 sd = seg_idl['sd'].storage.data 534 for i in xrange(fastmodel.Nstate): 535 amp[fastmodel.clazz[i]] = st_amp[i] 536 sd[fastmodel.clazz[i]] = st_std[i] 537 seg_idl['DwellCount'].data = len(states) 538 seg_idl['Firsts'].data.setup(qubx.tree.QTR_TYPE_INT, len(states), 1) 539 seg_idl['Lasts'].data.setup(qubx.tree.QTR_TYPE_INT, len(states), 1) 540 seg_idl['Classes'].data.setup(qubx.tree.QTR_TYPE_INT, len(states), 1) 541 qubx.fast.simulate.make_idealized_flc(seg_idl['Firsts'].storage.data, 542 seg_idl['Lasts'].storage.data, 543 seg_idl['Classes'].storage.data, 544 fastmodel.clazz, states, st_counts, sam_ix) 545 sam_ix += ct 546 seg_ix += 1 547 tree_const = tree["Constants"] 548 for nm,val in self.constants.valudict.iteritems(): 549 tree_const.insert(nm).data = val 550 tree_done = True 551 finally: 552 pass 553 #tree.re_map_data() 554 #tree.save() 555 if tree_done: 556 self.files.put_ready(tree) 557 gobject.idle_add(self.__swap)
558 - def __swap(self):
559 """Displays the newly simulated data, on the gui thread.""" 560 simfile = self.datas.views[0].file 561 old_tree = simfile.tree 562 simfile.tree = self.files.get_ready() 563 self.OnFinish() 564 if old_tree: 565 self.files.put_done(old_tree) 566 if (self.datas.index == 0) and self.continuous: 567 self.update()
568 - def __robot_on_status(self, frac):
569 try: 570 self.robot.progress = 100.0 * frac 571 return 0 572 except: 573 return -1
574 575 576 @Propertied(Property('enabled', 0, ""), 577 Property('baselineFluct', 0, ""), 578 Property('baselineFluctN', 1000, ""), # 1..100000 579 Property('baselineFluctLifetime', 1.0, ""), # pos. 580 Property('baselineFluctMaxAmp', 1.0, ""), # pos. 581 Property('baselineDrift', 0, ""), 582 Property('baselineDriftSlope', 0.0, ""), # anything 583 Property('baselineDriftIntercept', 0.0, ""), # anything 584 Property('baselinePeriodic', 0, ""), 585 Property('baselinePeriodicFreq', 60.0, ""), # + 586 Property('baselinePeriodicPhase', 0.0, ""), # 0..360 587 Property('baselinePeriodicAmp', 1.0, ""), # + 588 Property('baselineJumps', 0, ""), 589 Property('baselineJumpsAmpStd', 5.0, ""), # 0+ 590 Property('baselineJumpsFastW', 0.5, ""), # 0..1 591 Property('baselineJumpsFast', 1e-3, ""), # + 592 Property('baselineJumpsSlow', 1.0, ""), # + 593 Property('baselineJumpsLifetime', 1.0, ""), # + 594 Property('baselineLGM', 0, ""), 595 Property('baselineLGM2State', 1, ""), 596 Property('baselineLGMProcStd', 1e-3, ""), # + 597 Property('baselineLGMX0', 0.0, ""), # anything 598 Property('baselineLGMX0p', 0.0, "")) # anything
599 -class SimNoiseDialog(gtk.Dialog):
600 __explore_featured = ['global_name', 'OnChange', 'OnChangeEnabled', 'run']
601 - def __init__(self, global_name="QubX.Simulation.noise"):
602 gtk.Dialog.__init__(self, 'QUB Express - Simulation - Extra Noise', qubx.global_namespace.QubX, gtk.DIALOG_MODAL, 603 buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) 604 self.__ref = Reffer() 605 self.global_name = global_name 606 self.propertied_connect_settings('SimulationNoise') 607 self.set_default_response(gtk.RESPONSE_ACCEPT) 608 609 self.OnChange = WeakEvent() 610 self.OnChangeEnabled = WeakEvent() 611 612 columns = pack_item(gtk.HBox(True), self.vbox, expand=True) 613 column = pack_item(gtk.VBox(), columns, expand=True) 614 h = pack_item(gtk.HBox(), column) 615 chk = pack_check('Random fluctuations:', h) 616 self.propertied_connect_check('baselineFluct', chk) 617 h = pack_item(gtk.HBox(), column) 618 pack_space(h, x=20) 619 pack_label('Lifetime [sec]:', h) 620 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h, at_end=True) 621 self.propertied_connect_NumEntry('baselineFluctLifetime', txt) 622 h = pack_item(gtk.HBox(), column) 623 pack_space(h, x=20) 624 pack_label('Max amp:', h) 625 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h, at_end=True) 626 self.propertied_connect_NumEntry('baselineFluctMaxAmp', txt) 627 h = pack_item(gtk.HBox(), column) 628 pack_space(h, x=20) 629 pack_label('N:', h) 630 txt = pack_item(qubx.GTK.NumEntry(1, acceptIntBetween(1, 100000), '%s', width_chars=6), h, at_end=True) 631 self.propertied_connect_NumEntry('baselineFluctN', txt) 632 633 h = pack_item(gtk.HBox(), column) 634 chk = pack_check('Deterministic periodic component:', h) 635 self.propertied_connect_check('baselinePeriodic', chk) 636 h = pack_item(gtk.HBox(), column) 637 pack_space(h, x=20) 638 pack_label('Amplitude:', h) 639 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h, at_end=True) 640 self.propertied_connect_NumEntry('baselinePeriodicAmp', txt) 641 h = pack_item(gtk.HBox(), column) 642 pack_space(h, x=20) 643 pack_label('Frequency [Hz]:', h) 644 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.1f', width_chars=6), h, at_end=True) 645 self.propertied_connect_NumEntry('baselinePeriodicFreq', txt) 646 h = pack_item(gtk.HBox(), column) 647 pack_space(h, x=20) 648 pack_label('Phase [deg]:', h) 649 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 360.0), '%.1f', width_chars=6), h, at_end=True) 650 self.propertied_connect_NumEntry('baselinePeriodicPhase', txt) 651 652 h = pack_item(gtk.HBox(), column) 653 chk = pack_check('Linear Gaussian model:', h) 654 self.propertied_connect_check('baselineLGM', chk) 655 h = pack_item(gtk.HBox(), column) 656 pack_space(h, x=20) 657 pack_label('Process std:', h) 658 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h, at_end=True) 659 self.propertied_connect_NumEntry('baselineLGMProcStd', txt) 660 h = pack_item(gtk.HBox(), column) 661 pack_space(h, x=20) 662 pack_label('x0:', h) 663 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloat, '%.3g', width_chars=6), h, at_end=True) 664 self.propertied_connect_NumEntry('baselineLGMX0', txt) 665 h = pack_item(gtk.HBox(), column) 666 pack_space(h, x=20) 667 pack_label("x0':", h) 668 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloat, '%.3g', width_chars=6), h, at_end=True) 669 self.propertied_connect_NumEntry('baselineLGMX0p', txt) 670 h = pack_item(gtk.HBox(), column) 671 pack_space(h, x=20) 672 chk = pack_check('2 state model', h) 673 self.propertied_connect_check('baselineLGM2State', chk) 674 675 column = pack_item(gtk.VBox(), columns, expand=True) 676 h = pack_item(gtk.HBox(), column) 677 chk = pack_check('Random jumps and exp recovery:', h) 678 self.propertied_connect_check('baselineJumps', chk) 679 h = pack_item(gtk.HBox(), column) 680 pack_space(h, x=20) 681 pack_label('Jump amp std:', h) 682 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThanOrEqualTo(0.0), '%.3g', width_chars=6), h, at_end=True) 683 self.propertied_connect_NumEntry('baselineJumpsAmpStd', txt) 684 h = pack_item(gtk.HBox(), column) 685 pack_space(h, x=20) 686 pack_label('Lifetime [sec]:', h) 687 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h, at_end=True) 688 self.propertied_connect_NumEntry('baselineJumpsLifetime', txt) 689 h = pack_item(gtk.HBox(), column) 690 pack_space(h, x=20) 691 pack_label('Fast w [0..1]:', h) 692 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatBetween(0.0, 1.0), '%.3f', width_chars=6), h, at_end=True) 693 self.propertied_connect_NumEntry('baselineJumpsFastW', txt) 694 h = pack_item(gtk.HBox(), column) 695 pack_space(h, x=20) 696 pack_label('Fast rec [sec]:', h) 697 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h, at_end=True) 698 self.propertied_connect_NumEntry('baselineJumpsFast', txt) 699 h = pack_item(gtk.HBox(), column) 700 pack_space(h, x=20) 701 pack_label('Slow rec [sec]:', h) 702 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h, at_end=True) 703 self.propertied_connect_NumEntry('baselineJumpsSlow', txt) 704 705 h = pack_item(gtk.HBox(), column) 706 chk = pack_check('Deterministic linear drift:', h) 707 self.propertied_connect_check('baselineDrift', chk) 708 h = pack_item(gtk.HBox(), column) 709 pack_space(h, x=20) 710 pack_label('Slope:', h) 711 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloat, '%.3g', width_chars=6), h, at_end=True) 712 self.propertied_connect_NumEntry('baselineDriftSlope', txt) 713 h = pack_item(gtk.HBox(), column) 714 pack_space(h, x=20) 715 pack_label('Intercept:', h) 716 txt = pack_item(qubx.GTK.NumEntry(1.0, acceptFloat, '%.3g', width_chars=6), h, at_end=True) 717 self.propertied_connect_NumEntry('baselineDriftIntercept', txt) 718 719 pack_space(column, y=60) 720 h = pack_item(gtk.HBox(), column) 721 pack_item(qubx.settingsGTK.PresetsMenu('SimulationNoise', qubx.global_namespace.QubX.appname), h, at_end=True)
722
723 - def propertied_set(self, value, name):
724 super(SimNoiseDialog, self).propertied_set(value, name) 725 if name == 'enabled': 726 self.OnChangeEnabled() 727 else: 728 self.OnChange()
729
730 - def run(self):
731 self.set_transient_for(qubx.GTK.get_active_window()) 732 result = gtk.Dialog.run(self) 733 self.hide() 734 return result
735
736 737 738 -class SimProtocol(gtk.VBox):
739 """Displays, edits and generates stimulus protocols. 740 741 @ivar segment_count: number of segments 742 @ivar channel_count: number of varying signals 743 @ivar protocol: L{qubx.tree.Node_numpy}, basically U{QPF<http://www.qub.buffalo.edu/qubdoc/files/qpf.html>} 744 """ 745 746 __explore_featured = ['segment_count', 'channel_count', 'protocol', 'channels', 'duration', 'zoom', 'OnEdit', 'shape_properties', 747 'add_channel', 'remove_channel', 'set_proto', 'update_properties', 'get_sample_counts', 'get_samples', 748 'reset', 'data_signal'] 749
750 - def __init__(self):
751 gtk.VBox.__init__(self) 752 self.__ref = Reffer() 753 self.channels = [] 754 self.duration = 1.0 755 self.__proto = None 756 self.__channel_count = 0 757 758 self.chanbox = gtk.VBox() 759 self.scroll = pack_scrolled(self.chanbox, self, expand=True, with_vp=True) 760 self.zoom = pack_item(ZoomBar(), self, show=False) 761 self.zoom.adjustment.connect('changed', self.__onZoom) 762 self.zoom.adjustment.connect('value_changed', self.__onZoom) 763 self.OnEdit = WeakEvent() # () 764 self.shape_properties = SimProtoProperties() 765 self.shape_properties.show() 766 self.shape_properties.OnEdit += self.__ref(self.__onEdit) 767 self.protocol = None 768 self.__segment_count = 0 769 self.__data_signals = {}
770 - def set_segment_count(self, x):
771 self.__segment_count = x 772 for chan in self.channels: 773 chan.segment_count = x 774 self.__onEdit()
775 segment_count = property(lambda self: self.__segment_count, lambda self, x: self.set_segment_count(x))
776 - def set_channel_count(self, Nch, new_node=True):
777 while len(self.channels) < Nch: 778 chan = SimProtoChannel(self.zoom) 779 chan.segment_count = self.segment_count 780 chan.OnEdit += self.__ref(self.__onEdit) 781 chan.OnSelect += self.__ref(self.__onSelect) 782 chan.OnRemove += self.__ref(self.__onRemove) 783 self.channels.append(chan) 784 if new_node: chan.channel = self.protocol['Stages']['Stage'].append('Channel') 785 chan.show() 786 self.chanbox.pack_start(chan, False, True) 787 while len(self.channels) > Nch: 788 self.remove_channel(len(self.channels)-1, silent=True) 789 self.__channel_count = Nch 790 self.protocol['ChannelCount'].data = Nch 791 if Nch: 792 self.zoom.show() 793 else: 794 self.zoom.hide() 795 self.OnEdit()
796 channel_count = property(lambda self: self.__channel_count, lambda self, x: self.set_channel_count(x))
797 - def add_channel(self, name):
798 """Adds a varying signal by name.""" 799 self.channel_count += 1 800 self.channels[-1].label = name
801 - def remove_channel(self, i, silent=False):
802 """Removes the i'th varying signal.""" 803 chan = self.channels[i] 804 if chan.active_i >= 0: 805 self.__onSelect(chan, None) 806 del self.channels[i] 807 self.chanbox.remove(chan) 808 self.protocol['Stages']['Stage'].remove(chan.channel) 809 chan.OnEdit -= self.__ref(self.__onEdit) 810 chan.OnSelect -= self.__ref(self.__onSelect) 811 chan.OnRemove -= self.__ref(self.__onRemove) 812 self.__channel_count -= 1 813 self.protocol['ChannelCount'].data = self.__channel_count 814 if self.__channel_count: 815 self.zoom.show() 816 else: 817 self.zoom.hide() 818 if not silent: 819 self.OnEdit()
820 - def set_proto(self, x):
821 self.__proto = x 822 if x: 823 if x.find('ChannelCount').data: 824 self.set_channel_count(x['ChannelCount'].data[0], new_node=False) 825 for i, node in enumerate(qubx.tree.children(x['Stages']['Stage'], 'Channel')): 826 if i < self.channel_count: 827 self.channels[i].channel = node 828 self.__onEdit()
829 protocol = property(lambda self: self.__proto, lambda self, x: self.set_proto(x))
830 - def update_properties(self, updates):
831 """Updates the protocol to match the L{qubx.tree.Node_numpy} updates.""" 832 if updates.find('ChannelCount').data: 833 self.channel_count = updates['ChannelCount'].data[0] 834 for c, ch in enumerate(qubx.tree.children(updates['Stages']['Stage'], 'Channel')): 835 if ch.find('Label'): 836 self.channels[c].label = str(ch['Label'].data) 837 while self.channels[c].shapes: 838 self.channels[c].remove_shape(0) 839 for s, sh in enumerate(qubx.tree.children(ch, 'Shape')): 840 self.channels[c].append_shape(BuildShape(sh.clone())) 841 self.OnEdit()
842 - def __onZoom(self, *args):
843 for chan in self.channels: 844 chan.redraw_canvas()
845 - def __onEdit(self, channel=None):
846 if not channel: 847 for chan in self.channels: 848 chan.update_duration() 849 duration = (self.channels and max(chan.duration for chan in self.channels)) or self.duration 850 for chan in self.channels: 851 chan.group_duration = duration 852 self.duration = duration 853 # update computed signals in data display 854 for view in qubx.global_namespace.QubX.Data.views: 855 signals = view.file.signals 856 for i in xrange(signals.size): 857 if 'protocol' in signals[i, 'Computed as']: 858 view.file.OnChangeSamples(view.file, i) 859 self.OnEdit()
860 - def __onSelect(self, channel, shape):
861 chan_ix = self.channels.index(channel) 862 shape_ix = channel.shapes.index(shape) if (not (shape is None)) else -1 863 if shape_ix >= 0: 864 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.channels[%i].select_shape(%i)' % (chan_ix, shape_ix)) 865 for chan in self.channels: 866 if chan != channel: 867 chan.active_i = -1 868 self.shape_properties.shape = shape.properties if shape else None 869 About = qubx.pyenv.env.globals['QubX'].About 870 if shape: 871 try: 872 About.show_face('Shape') 873 except: 874 pass
875 - def __onRemove(self, channel):
876 dlg = gtk.MessageDialog(None, flags=gtk.DIALOG_MODAL, buttons=gtk.BUTTONS_OK_CANCEL, 877 message_format = 'Remove the entire stimulus signal "%s"?'%channel.label) 878 if dlg.run() == gtk.RESPONSE_OK: 879 chan_ix = self.channels.index(channel) 880 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.remove_channel(%i)' % chan_ix) 881 self.remove_channel(chan_ix) 882 dlg.destroy()
883 - def get_sample_counts(self, sampling):
884 return [(self.channels and max(chan.get_duration(i) for chan in self.channels)) or self.duration for i in xrange(self.segment_count)]
885 - def get_samples(self, sampling):
886 """Returns the entire stimulus protocol as sampled data in qub scratch format.""" 887 root = qubx.tree.Node() 888 root['ChannelCount'].data = self.channel_count 889 seg = qubx.tree.NullNode() 890 for i in xrange(self.segment_count): 891 seg = root.insert('Segment', seg) 892 duration = (self.channels and max(chan.get_duration(i) for chan in self.channels)) or self.duration 893 count = int(round(duration / sampling)) 894 seg['SampleCount'].data = count 895 for channel in self.channels: 896 chan = seg['Signals'].append(channel.label) 897 chan.data.setup(qubx.tree.QTR_TYPE_FLOAT, count, 1) 898 channel.generate_into(chan, i, 0, count-1, sampling) 899 return root
900 - def reset(self):
901 self.channel_count = 0
902 - def data_signal(self, name):
903 try: 904 return self.__data_signals[name] 905 except KeyError: 906 ds = self.__data_signals[name] = SimProtoDataSignal(name) 907 return ds
908
909 -class SimProtoDataSignal(object):
910 - def __init__(self, name):
911 self.name = name
912 - def get_samples(self, sampling, f, l, buf):
913 proto = qubx.global_namespace.QubX.Simulation.protocol 914 c = 0 915 for i, chan in enumerate(proto.channels): 916 if chan.label == self.name: 917 c = i 918 break 919 else: 920 buf[:] = 0.0 921 return 922 chan = proto.channels[c] 923 node = qubx.tree.Node() 924 node.data.setup(qubx.tree.QTR_TYPE_FLOAT, max(1, int(round(proto.duration/sampling))), 1) 925 tm_pre = sampling * f 926 samples_done = 0 927 samples_remain = l - f + 1 928 for rep in xrange(proto.segment_count): 929 dur = chan.get_duration(rep) 930 if dur <= tm_pre: 931 tm_pre -= dur 932 continue 933 start = int(round(tm_pre/sampling)) 934 n = min(samples_remain, int(round((dur - tm_pre)/sampling))) 935 chan.generate_into(node, rep, start, start+n-1, sampling) 936 buf[samples_done:samples_done+n] = node.storage.data[:n].flat 937 samples_done += n 938 samples_remain -= n 939 if not samples_remain: 940 return 941 if samples_remain: 942 buf[samples_done:samples_done+samples_remain] = 0.0
943 944 945 COLOR_SIG_NAME = ('simulate.signal.name', (1, 1, 1, 1)) 946 ColorInfo[COLOR_SIG_NAME[0]].label = 'Simulation signal names' 947 COLOR_SIG_VAL = ('simulate.signal.value', LAYER_FG[1]) 948 ColorInfo[COLOR_SIG_VAL[0]].label = 'Simulation signal values' 949 COLOR_SIG_VARY = ('simulate.signal.vary', (1, .3, .3, 1)) 950 ColorInfo[COLOR_SIG_VARY[0]].label = 'Simulation signal vary' 951 COLOR_SIG_REM = ('simulate.signal.remove', (1, .2, .2, 1)) 952 ColorInfo[COLOR_SIG_REM[0]].label = 'Simulation signal remove' 953 COLOR_SHAPE_POPUP = ('simulate.shape.popup', (0, .8, 0, .9)) 954 ColorInfo[COLOR_SHAPE_POPUP[0]].label = 'Simulation signal menu'
955 956 -class SimProtoChannel(ToolSpace):
957 """One signal in a L{SimProtocol}. 958 959 @ivar zoom: the shared L{ZoomBar} among all signals 960 @ivar channel: L{qubx.tree.Node_numpy} (sub-tree) describing this signal 961 @ivar segment_count: total number of segments 962 @ivar active_i: index of highlighted shape 963 @ivar group_duration: all signals display as being this long, together 964 @ivar OnEdit: L{WeakEvent}C{(SimProtoChannel)} when anything changes 965 @ivar OnSelect: L{WeakEvent}C{(SimProtoChannel, Shape)} when a shape is highlighted 966 @ivar OnRemove: L{WeakEvent}C{(SimProtoChannel)} when a shape is deleted 967 """ 968 969 __explore_featured = ['zoom', 'channel', 'segment_count', 'active_i', 'group_duration', 'OnEdit', 'OnSelect', 'OnRemove', 970 'duration', 'shapes', 'shape_bounds', 'label', 'filter_kHz', 971 'shape_index', 'remove_shape', 'select_shape', 'insert_shape', 'insert_shape_dict', 'append_shape', 'move_shape', 972 'copy_shape_to_clipboard', 'paste_shape_from_clipboard', 'get_duration', 'update_duration', 'count', 973 'generate_into'] 974
975 - def __init__(self, zoom):
976 self.rep_samples = Product(self.__get_rep_samples) 977 ToolSpace.__init__(self) 978 self.__ref = Reffer() 979 self.zoom = zoom 980 self.__ref = Reffer() 981 self.appearance.OnSetFontSize += self.__ref(self.__onSetFontSize) 982 self.set_size_request(100, 6*self.appearance.emsize) 983 self.__channel = None 984 self.__segment_count = 0 985 self.__active_i = -1 986 self.duration = 0.0 987 self.__group_duration = 1.0 988 self.shapes = [] 989 self.shape_bounds = [] 990 self.OnDraw += self.__ref(self.__onDraw) 991 self.tool = SimProtoPicker() 992 self.OnEdit = WeakEvent() # (SimProtoChannel) 993 self.OnSelect = WeakEvent() # (SimProtoChannel, Shape) 994 self.OnRemove = WeakEvent() # (SimProtoChannel) 995 # layers: [self.label : ] [Hold] [Step] [Ramp] ... [X] || click to edit label / append shape 996 self.layName = Layer(0, 0, 24, 2) 997 self.subName = SubLayer_Label('', 0, 0, x=1, y=0, w=16, h=2, color=COLOR_SIG_NAME, 998 action=self.__ref(self.__onClickName), 999 tooltip="click to rename") 1000 self.layName.add_sublayer(self.subName) 1001 self.subFilter = SubLayer_Label('F:none', 0, 0, x=18, y=0, w=6, h=2, color=COLOR_SIG_NAME, 1002 action=self.__ref(self.__onClickFilter), 1003 tooltip="Low-pass filter; click to edit") 1004 self.layName.add_sublayer(self.subFilter) 1005 self.add_layer(self.layName) 1006 x = 25 1007 def SubLayer_Shape(ShapeClass): 1008 return SubLayer_Label(ShapeClass.name, 0, 0, x=1, y=0, w=6, h=2, 1009 action=self.__ref(lambda x,y,e: self.__onClickShape(ShapeClass)))
1010 for shape in Shapes: 1011 layer = Layer(x, 0, 8, 2) 1012 layer.add_sublayer(SubLayer_Shape(shape)) 1013 self.add_layer(layer) 1014 x += 9 1015 self.layRem = Layer(-2, 0, 2, 2) 1016 self.subRem = SubLayer_Label('X', 0, 0, x=0, y=0, w=2, h=2, hover_color=COLOR_SIG_REM, 1017 action=self.__ref(lambda x,y,e: gobject.idle_add(self.OnRemove, self))) 1018 self.layRem.add_sublayer(self.subRem) 1019 self.add_layer(self.layRem) 1020 self.layMenu = Layer(0, 2, 2, 2) 1021 self.subMenu = SubLayer_Popup(x=0, y=0, w=2, h=2, color=COLOR_SHAPE_POPUP, popup=self.tool.popup, on_popup=self.__ref(self.tool.on_popup)) 1022 self.layMenu.add_sublayer(self.subMenu)
1023 - def redraw_canvas(self, invalid=True, immediately=False, invalid_layers=False):
1024 if invalid: 1025 self.rep_samples.invalidate() 1026 ToolSpace.redraw_canvas(self, invalid=invalid, immediately=immediately, invalid_layers=invalid_layers)
1027 - def set_label(self, x):
1028 if self.__channel: 1029 self.__channel['Label'].data = x 1030 self.subName.label = x
1031 label = property(lambda self: self.subName.label, lambda self, x: self.set_label(x))
1032 - def set_filter_kHz(self, x):
1033 if self.__channel: 1034 self.__channel['filter_kHz'].data = x 1035 self.__update_filter_label(x)
1036 filter_kHz = property(lambda self: self.__channel['filter_kHz'].data[0], lambda self, x: self.set_filter_kHz(x))
1037 - def set_group_duration(self, x):
1038 self.__group_duration = x 1039 self.redraw_canvas()
1040 group_duration = property(lambda self: self.__group_duration, lambda self, x: self.set_group_duration(x))
1041 - def set_channel(self, x):
1042 if self.__active_i >= 0: 1043 self.select_shape(-1) 1044 self.__channel = x 1045 self.shapes[:] = [] 1046 if self.__channel: 1047 for node in qubx.tree.children(self.__channel, 'Shape'): 1048 try: 1049 self.shapes.append( BuildShape(node) ) 1050 except: 1051 traceback.print_exc() 1052 self.subName.label = str(self.channel['Label'].data) 1053 if not x['filter_kHz'].data: 1054 x['filter_kHz'].data = filter_kHz = 0.0 1055 else: 1056 filter_kHz = x['filter_kHz'].data[0] 1057 self.__update_filter_label(filter_kHz) 1058 self.update_duration()
1059 channel = property(lambda self: self.__channel, lambda self, x: self.set_channel(x))
1060 - def set_segment_count(self, x):
1061 self.__segment_count = x 1062 self.update_duration() 1063 self.redraw_canvas()
1064 segment_count = property(lambda self: self.__segment_count, lambda self, x: self.set_segment_count(x))
1065 - def set_active_i(self, x):
1066 self.__active_i = x 1067 self.redraw_canvas()
1068 active_i = property(lambda self: self.__active_i, lambda self, x: self.set_active_i(x))
1069 - def shape_index(self, x):
1070 """Returns the index of the shape at time x; -1: before; len(shapes): after""" 1071 if x < 0: return -1 1072 for i, bounds in enumerate(self.shape_bounds): 1073 f, l = bounds 1074 if f <= x <= l: 1075 return i 1076 return len(self.shape_bounds)
1077 - def remove_shape(self, i):
1078 """Deletes the i'th shape.""" 1079 QubX = qubx.pyenv.env.globals['QubX'] 1080 chan_ix = QubX.Simulation.protocol.channels.index(self) 1081 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.channels[%i].remove_shape(%i)' % (chan_ix, i)) 1082 reselect = self.__active_i == i 1083 self.channel.remove(self.shapes[i].properties) 1084 del self.shapes[i] 1085 self.update_duration() 1086 if reselect: 1087 gobject.idle_add(self.select_shape, max(0, i-1)) 1088 self.OnEdit(self)
1089 - def select_shape(self, i):
1090 """Highlights the i'th shape.""" 1091 self.__active_i = min(i, len(self.shapes)-1) 1092 if 0 <= self.__active_i < len(self.shapes): 1093 self.OnSelect(self, self.shapes[self.__active_i]) 1094 if not (self.layMenu in self._layers): 1095 self.add_layer(self.layMenu) 1096 else: 1097 self.OnSelect(self, None) 1098 if self.layMenu in self._layers: 1099 self.remove_layer(self.layMenu) 1100 self.redraw_canvas()
1101 - def insert_shape(self, i, shape):
1102 """Inserts a shape at index i.""" 1103 QubX = qubx.pyenv.env.globals['QubX'] 1104 chan_ix = QubX.Simulation.protocol.channels.index(self) 1105 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.channels[%i].insert_shape_dict(%i, %s)' % (chan_ix, i, qubx.tree.tree_to_dict(shape.properties))) 1106 self.shapes.insert(i, shape) 1107 self.__insert_shape_at(i, shape.properties) 1108 self.update_duration() 1109 self.OnEdit(self) 1110 gobject.idle_add(self.select_shape, len(self.shapes)-1)
1111 - def insert_shape_dict(self, i, d):
1112 self.insert_shape(i, BuildShape(qubx.tree.dict_to_tree(d)))
1113 - def append_shape(self, shape):
1114 """Appends a shape at the end.""" 1115 self.insert_shape(len(self.shapes), shape)
1116 - def __insert_shape_at(self, i, node):
1117 prev = qubx.tree.NullNode() 1118 child = self.channel.find('Shape') 1119 for k in xrange(i): 1120 prev = child 1121 child = child.next('Shape') 1122 self.channel.insert(node, prev)
1123 - def move_shape(self, i, j):
1124 """Moves the shape at index i to index j.""" 1125 if i == j: return 1126 QubX = qubx.pyenv.env.globals['QubX'] 1127 chan_ix = QubX.Simulation.protocol.channels.index(self) 1128 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.channels[%i].move_shape(%i, %i)' % (chan_ix, i, j)) 1129 self.channel.remove(self.shapes[i].properties) 1130 self.__insert_shape_at(j, self.shapes[i].properties) 1131 self.shapes[i], self.shapes[j] = self.shapes[j], self.shapes[i] 1132 if i == self.__active_i: 1133 self.__active_i = j 1134 elif j == self.__active_i: 1135 self.__active_i = i 1136 self.redraw_canvas()
1137 - def copy_shape_to_clipboard(self, index):
1138 qubx.treeGTK.Copy(self.shapes[index].properties)
1139 - def paste_shape_from_clipboard(self, index):
1140 try: 1141 node = qubx.treeGTK.Paste() 1142 self.insert_shape(index, BuildShape(node)) 1143 except: 1144 traceback.print_exc()
1145 - def get_duration(self, rep):
1146 """Returns the total duration of this signal, for segment number C{rep}.""" 1147 dur = 0.0 1148 for shape in self.shapes: 1149 dur += shape.duration(rep) 1150 return dur
1151 - def update_duration(self):
1152 """Recalculates self.duration, using the last segment as rep.""" 1153 self.duration = 0.0 1154 for shape in self.shapes: 1155 self.duration += shape.duration(self.__segment_count-1)
1156 - def count(self, sampling):
1157 """Returns the number of samples in this signal, current segment.""" 1158 return int(round(self.duration / sampling))
1159 - def generate_into(self, node, rep, first, last, sampling, shape_bounds=None):
1160 """Fills node.data with sampled stimulus.""" 1161 t_start = first * sampling 1162 t = 0.0 1163 i = 0 1164 start = 0 1165 while (t < t_start) and (i < len(self.shapes)): 1166 shape = self.shapes[i] 1167 rem_samples = int(round((t_start - t) / sampling)) 1168 if not rem_samples: break 1169 n = shape.count(t, sampling, rep) 1170 if rem_samples < n: 1171 t += rem_samples * sampling 1172 start = rem_samples 1173 else: 1174 if not (shape_bounds is None): shape_bounds.append( (-1, -1) ) 1175 t += shape.duration(rep) 1176 i += 1 1177 start = 0 1178 p = 0 1179 arr_size = last-first+1 1180 while (i < len(self.shapes)) and (p < arr_size): 1181 shape = self.shapes[i] 1182 shape.setup() 1183 n = shape.count(t, sampling, rep) - start 1184 if n > 0: 1185 n = min(arr_size-p, n) 1186 if not n: break 1187 shape.generateInto(start, start+n-1, t, sampling, rep, node, p) 1188 if not (shape_bounds is None): shape_bounds.append( (p, p+n-1) ) 1189 p += n 1190 t += shape.duration(rep) 1191 i += 1 1192 start = 0 1193 if not (shape_bounds is None): 1194 while i < len(self.shapes): 1195 shape_bounds.append( (arr_size+1, arr_size+1) ) 1196 i += 1 1197 if p < arr_size: 1198 hold = 0.0 1199 if p > 0: hold = node.data[p-1] 1200 node.storage.data[p:arr_size,0] = hold 1201 filter_kHz = self.__channel['filter_kHz'].data[0] 1202 if filter_kHz: 1203 node.storage.data[:,:] = qubx.fast.filter.filter(node.storage.data, sampling, 1e3*filter_kHz) 1204 return p
1205 - def __onSetFontSize(self, font_size):
1206 self.set_size_request(100, 6*self.appearance.emsize)
1207 - def __onClickName(self, x, y, e):
1208 dlg = NumEntryDialog('%s - Enter a variable name'%qubx.pyenv.env.globals['QubX'].appname, 1209 None, 'Model variable e.g. Voltage:', 1210 str(self.channel['Label'].data), acceptString) 1211 if gtk.RESPONSE_ACCEPT == dlg.run(): 1212 QubX = qubx.pyenv.env.globals['QubX'] 1213 chan_ix = QubX.Simulation.protocol.channels.index(self) 1214 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.channels[%i].label = %s' % (chan_ix, repr(dlg.value))) 1215 self.label = dlg.value 1216 self.OnEdit(self) 1217 dlg.destroy()
1218 - def __onClickFilter(self, x, y, e):
1219 dlg = NumEntryDialog('%s - Filter one stimulus signal'%qubx.pyenv.env.globals['QubX'].appname, 1220 None, 'Filter cutoff (kHz) or blank for no filter:', 1221 formatFloatOrUnset('%.4g')(self.channel['filter_kHz'].data[0] or UNSET_VALUE), 1222 acceptXorUnset(acceptFloatGreaterThanOrEqualTo(0.0))) 1223 response = dlg.run() 1224 if gtk.RESPONSE_ACCEPT == response: 1225 QubX = qubx.pyenv.env.globals['QubX'] 1226 chan_ix = QubX.Simulation.protocol.channels.index(self) 1227 filter_kHz = 0.0 if (dlg.value == UNSET_VALUE) else dlg.value 1228 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.channels[%i].filter_kHz = %s' % (chan_ix, filter_kHz)) 1229 self.filter_kHz = filter_kHz 1230 self.OnEdit(self) 1231 dlg.destroy()
1232 - def __update_filter_label(self, filter_kHz):
1233 self.subFilter.label = ('F:%.2g' % filter_kHz) if filter_kHz else 'F:none'
1234 - def __onClickShape(self, MakeShape):
1235 self.append_shape(MakeShape())
1236 - def __get_rep_samples(self, w, zoom, start_frac):
1237 sampling = (self.__group_duration * 1.0 / w) / zoom 1238 t_start = self.__group_duration * start_frac 1239 i_start = int(round(t_start / sampling)) 1240 rep_samples = qubx.tree.Node() 1241 one_rep = qubx.tree.NullNode() 1242 hi = -1e10 1243 lo = 1e10 1244 for rep in xrange(self.segment_count): 1245 one_rep = rep_samples.insert("rep", one_rep) 1246 one_rep.data.setup(qubx.tree.QTR_TYPE_FLOAT, w, 1) 1247 self.shape_bounds = [] 1248 valid_count = self.generate_into(one_rep, rep, i_start, i_start+w-1, sampling, self.shape_bounds) 1249 one_rep['valid_count'].data = valid_count 1250 if w: 1251 arrd = one_rep.storage.data[:,0] 1252 lo = min(lo, min(arrd)) 1253 hi = max(hi, max(arrd)) 1254 if hi <= lo: 1255 lo, hi = lo - 1, lo + 1 1256 rep_samples['lo'].data = lo 1257 rep_samples['hi'].data = hi 1258 1259 self.__last_rep_samples = rep_samples 1260 return rep_samples
1261 - def __onDraw(self, context, w, h):
1262 if not w: return 1263 rep_samples = self.rep_samples.get_val(w, self.zoom.zoom / 100.0, self.zoom.adjustment.value / 100.0) 1264 em = self.appearance.emsize 1265 context.set_source_rgb(0,0,0) 1266 context.paint() 1267 context.translate(0, 2*em) 1268 h = float(max(1, h-2*em)) 1269 context.set_line_width(3.0) 1270 lo = rep_samples['lo'].data[0] 1271 hi = rep_samples['hi'].data[0] 1272 last_rep = None 1273 for rep, arr in enumerate(qubx.tree.children(rep_samples, 'rep')): 1274 last_rep = arr 1275 if self.segment_count > 1: 1276 r,g,b = HSVtoRGB(rep*.67/self.segment_count, 1.0, 1.0) 1277 else: 1278 r,g,b = (1,1,1) 1279 context.set_source_rgba(r, g, b, .5+(rep*.5/self.segment_count)) 1280 arrd = array(arr.storage.data[:,0], copy=True) 1281 arrd -= hi 1282 arrd *= - h / (hi - lo) 1283 context.move_to(0, arrd[0]) 1284 for i in xrange(1, w): 1285 context.line_to(i, arrd[i]) 1286 context.stroke() 1287 # final rep bounds 1288 context.set_source_rgb(1,1,1) 1289 context.set_line_width(1.0) 1290 for s in xrange(len(self.shape_bounds)): 1291 lo, hi = self.shape_bounds[s] 1292 if (lo <= w) and (hi >= 0): 1293 context.rectangle(lo+.5, 0, hi-lo+1, h) 1294 if s == self.__active_i: 1295 context.set_source_rgba(0,.6,1,.3) 1296 context.fill_preserve() 1297 context.set_source_rgb(1,1,1) 1298 context.stroke() 1299 if last_rep: 1300 valid_count = last_rep['valid_count'].data[0] 1301 if valid_count < w: 1302 context.rectangle(valid_count+1, 0, w-valid_count, h) 1303 context.set_source_rgba(.3, .3, .3, .3) 1304 context.fill() 1305 if 0 <= self.__active_i < len(self.shape_bounds): 1306 self.layMenu.x = max(0, min(self._dim[0]-6*em, self.shape_bounds[self.__active_i][1] - 2*em)) * self._layer_scale[0] / em
1307
1308 -def InsertShape(picker, index, ShapeClass):
1309 def insert(*args): 1310 picker.space.insert_shape(index(), ShapeClass())
1311 return insert 1312
1313 -class SimProtoPicker(Tool):
1314 """Mouse interactions for L{SimProtoChannel}."""
1315 - def __init__(self):
1316 Tool.__init__(self) 1317 self.__ref = Reffer() 1318 self.mnuInsert = gtk.Menu() 1319 for shape in Shapes: 1320 build_menuitem(shape.name, InsertShape(self, self.index_for_insert, shape), menu=self.mnuInsert) 1321 self.popup = gtk.Menu() 1322 self.itemCut = build_menuitem('Cut shape', self.__ref(self.__onCut), menu=self.popup) 1323 self.itemCopy = build_menuitem('Copy shape', self.__ref(self.__onCopy), menu=self.popup) 1324 self.itemPaste = build_menuitem('Paste shape', self.__ref(self.__onPaste), menu=self.popup) 1325 self.itemInsert = build_menuitem('Insert', submenu=self.mnuInsert, menu=self.popup) 1326 self.itemAddToData = build_menuitem('Add signal to data file', self.__ref(self.__onAddToData), menu=self.popup) 1327 self.itemRemove = build_menuitem('Remove shape', self.__ref(self.__onRemove), menu=self.popup) 1328 self.itemRemoveChannel = build_menuitem('Remove stimulus', self.__ref(self.__onRemoveChannel), menu=self.popup)
1329 - def onOverlay(self, context, w, h):
1330 pass
1331 - def onPress(self, x, y, e):
1332 self.__i = self.space.shape_index(x) 1333 self.space.select_shape(self.__i)
1334 - def onRelease(self, x, y, e):
1335 pass
1336 - def onDrag(self, x, y, e):
1337 if 0 <= self.__i < len(self.space.shapes): 1338 j = self.space.shape_index(x) 1339 if (0 <= j < len(self.space.shapes)) and (j != self.__i): 1340 self.space.move_shape(self.__i, j) 1341 self.__i = j
1342 - def onRoll(self, x, y, e):
1343 pass
1344 - def onNeedPopup(self, x, y, e):
1345 self.onPress(x, y, e) 1346 self.onRelease(x, y, e) 1347 self.on_popup() 1348 self.__x = x 1349 self.popup.popup(None, None, None, 0, e.time)
1350 - def on_popup(self):
1351 on_shape = (0 <= self.space.active_i < len(self.space.shapes)) 1352 self.itemCut.set_sensitive( on_shape ) 1353 self.itemCopy.set_sensitive( on_shape ) 1354 self.itemPaste.set_sensitive( qubx.treeGTK.CanPaste() ) 1355 self.itemRemove.set_sensitive( on_shape ) 1356 if on_shape: 1357 self.__i = self.space.active_i 1358 self.__x = self.space.shape_bounds[self.space.active_i][0] 1359 return True
1360 - def index_for_insert(self):
1361 if self.__i < 0: 1362 return 0 1363 elif self.__i >= len(self.space.shapes): 1364 return len(self.space.shapes) 1365 else: 1366 lo, hi = self.space.shape_bounds[self.__i] 1367 if (self.__x - lo) <= (hi - self.__x): 1368 return self.__i 1369 else: 1370 return self.__i + 1
1371 - def __onCut(self, item):
1372 QubX = qubx.pyenv.env.globals['QubX'] 1373 chan_ix = QubX.Simulation.protocol.channels.index(self.space) 1374 self.__onCopy(self.itemCopy) 1375 self.__onRemove(self.itemRemove)
1376 - def __onCopy(self, item):
1377 QubX = qubx.pyenv.env.globals['QubX'] 1378 chan_ix = QubX.Simulation.protocol.channels.index(self.space) 1379 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.channels[%i].copy_shape_to_clipboard(%i)' % (chan_ix, self.__i)) 1380 self.space.copy_shape_to_clipboard(self.__i)
1381 - def __onPaste(self, item):
1382 QubX = qubx.pyenv.env.globals['QubX'] 1383 chan_ix = QubX.Simulation.protocol.channels.index(self.space) 1384 i = self.index_for_insert() 1385 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.channels[%i].paste_shape_from_clipboard(%i)' % (chan_ix, i)) 1386 self.space.paste_shape_from_clipboard(i)
1387 - def __onAddToData(self, item):
1388 QubX = qubx.pyenv.env.globals['QubX'] 1389 qubx.pyenv.env.OnScriptable("""QubX.Data.file.signals.append({'Name' : %s, 'Computed as' : 'QubX.Simulation.protocol.data_signal(%s)'})""" % (repr(self.space.label), repr(self.space.label))) 1390 QubX.Data.file.signals.append({'Name' : self.space.label, 1391 'Computed as' : 'QubX.Simulation.protocol.data_signal("%s")' % self.space.label})
1392 - def __onRemove(self, item):
1393 self.space.remove_shape(self.__i)
1394 - def __onRemoveChannel(self, item):
1395 gobject.idle_add(self.space.OnRemove, self.space)
1396
1397 1398 -def acceptSimProtoParam(node):
1399 # returns a number or list of numbers 1400 ty = type(node.data[0]) 1401 def accept(s): 1402 v = eval(s, qubx.pyenv.env.globals) 1403 try: 1404 return [ty(x) for x in v] 1405 except: 1406 return ty(v)
1407 return accept 1408
1409 1410 -class SimProtoProperties(Face):
1411 """Panel with protocol shape properties, for QubX.About. 1412 1413 @ivar lbl: gtk.Label with the shape's name 1414 @ivar tableview: (when active) a L{qubx.table.TableView} 1415 @ivar table: (when active) temporary table of Shape's params 1416 @ivar shape: L{qubx.simulation_protocol.Shape} 1417 """ 1418 1419 __explore_featured = ['scroll', 'tableview', 'shape', 'OnEdit', 'set_property'] 1420
1421 - def __init__(self, name='Shape', global_name='QubX.Simulation.protocol.shape_properties'):
1422 Face.__init__(self, 'Shape') 1423 self.__ref = Reffer() 1424 self.lbl = pack_label('', self) 1425 self.scroll = gtk.ScrolledWindow() 1426 self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 1427 self.scroll.show() 1428 self.pack_start(self.scroll, True, True) 1429 self.tableview = None 1430 self.__shape = None 1431 self.OnEdit = WeakEvent() # ()
1432 - def set_shape(self, x):
1433 if self.tableview: 1434 self.table.OnSelect -= self.__ref(self.__onSelect) 1435 self.table.OnSet -= self.__ref(self.__onSet) 1436 self.scroll.remove(self.tableview) 1437 self.tableview = None 1438 self.__shape = x 1439 if self.__shape: 1440 self.lbl.set_text(str(self.__shape.data)) 1441 self.__accept_rows = [] 1442 self.__nodes = [] 1443 self.__names = [] 1444 self.table = SimpleTable('Shape') 1445 self.table.add_field('Field', '', acceptNothing, str, '') 1446 self.table.add_field('Value', 1.0, self.__accept, str, '') 1447 for child in qubx.tree.children(self.__shape): 1448 if child.data: 1449 self.__nodes.append(child) 1450 self.__names.append(child.name) 1451 if child.data.type == qubx.tree.QTR_TYPE_STRING: 1452 self.table.append({'Field': child.name, 1453 'Value': str(child.data)}) 1454 self.__accept_rows.append(str) 1455 else: 1456 cell = child.data[0] if (len(child.data) == 1) else child.data[:] 1457 self.table.append({'Field': child.name, 1458 'Value': cell}) 1459 self.__accept_rows.append(acceptSimProtoParam(child)) 1460 self.table.OnSelect += self.__ref(self.__onSelect) 1461 self.table.OnSet += self.__ref(self.__onSet) 1462 self.table.fields.remove('Index') 1463 self.table.fields.remove('Group') 1464 self.tableview = TableView(self.table) 1465 self.tableview.show() 1466 self.scroll.add(self.tableview) 1467 else: 1468 self.lbl.set_text('')
1469 shape = property(lambda self: self.__shape, lambda self, x: self.set_shape(x))
1470 - def __onSelect(self, row, field_name, sender):
1471 if row >= 0: 1472 self.__accept_row = self.__accept_rows[row]
1473 - def __accept(self, x):
1474 return self.__accept_row(x)
1475 - def __onSet(self, row, field_name, val, prev, undoing):
1476 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.shape_properties.set_property(%s, %s)' % (repr(self.__names[row]), repr(val))) 1477 self.__nodes[row].data = val 1478 self.OnEdit()
1479 - def set_property(self, field_name, val):
1480 self.__nodes[self.__names.index(field_name)].data = val 1481 self.OnEdit()
1482
1483 -class SimConstants(ToolSpace):
1484 """Panel for display and editing simdata.constants; makes sure model vars are listed unless they vary in protocol.""" 1485 1486 __explore_featured = ['OnSet', 'protocol', 'names', 'layers', 'labels', 'subs', 'model', 'valudict', 'values', 'update', 1487 'append_const', 'remove_const', 'set_const'] 1488
1489 - def __init__(self, protocol):
1490 ToolSpace.__init__(self) 1491 self.__ref = Reffer() 1492 self.OnSet = WeakEvent() # (name, value) 1493 self.appearance.OnSetFontSize += self.__ref(self.__onSetFontSize) 1494 self.set_size_request(100, 4*self.appearance.emsize) 1495 self.protocol = protocol 1496 self.OnDraw += self.__ref(self.__onDraw) 1497 self.names = [] 1498 self.layers = [] 1499 self.labels = [] 1500 self.subs = [] 1501 self.model = None 1502 self.valudict = {} 1503 self.protocol.OnEdit += self.__ref(self.__onEditProtocol) 1504 self.update(self.model) 1505 layer = Layer(0, .5, 9, 3) 1506 sub = SubLayer_Label('Constants:', 0, 1, x=0, y=0, w=9, h=3) 1507 layer.add_sublayer(sub) 1508 self.add_layer(layer) 1509 self.defer_scriptable_scroll = qubx.pyenv.DeferredScriptableScroll()
1510 - def get_values(self):
1511 return [self.valudict[name] for name in self.names]
1512 values = property(get_values)
1513 - def update(self, model):
1514 """Makes sure only non-varying model variables are listed, and nothing else.""" 1515 self.model = model 1516 if self.model: 1517 var_defs = self.model.get_stimulus() 1518 var_names = set([x for x in var_defs.keys()]) 1519 else: 1520 var_defs = {} 1521 var_names = set() 1522 const_names = set(self.names) 1523 chan_names = set([chan.label for chan in self.protocol.channels]) 1524 # each in var_names must be in either const_names or chan_names 1525 # in both: del const 1526 # in neither: add const 1527 for name in var_names: 1528 if name in chan_names: 1529 if name in const_names: 1530 self.remove_const(name) 1531 elif not (name in const_names): 1532 self.append_const(name, var_defs[name]) 1533 for name in (const_names - var_names): 1534 self.remove_const(name)
1535 - def append_const(self, nm, value):
1536 i = len(self.names) 1537 self.names.append(nm) 1538 self.valudict[nm] = value 1539 layer = Layer(10+i*20, .5, 19, 3, cBG=COLOR_CONSTBG, border=CONSTBORDER, cBorder=COLOR_CONSTBORDER) 1540 sub = SubLayer_Label(nm, 0, 1, x=0, y=0, w=8, h=3, color=COLOR_SIG_NAME) 1541 self.labels.insert(i, sub) 1542 layer.add_sublayer(sub) 1543 sub = SubLayer_Label('%.3g' % value, 0, 1, x=8, y=0, w=6, h=3, 1544 action=self.__ref(lambda x,y,e: self.__onClickConstant(nm)), 1545 scroll=self.__ref(lambda x,y,e,o: self.__onScrollConstant(nm, e, o))) 1546 self.subs.insert(i, sub) 1547 layer.add_sublayer(sub) 1548 sub = SubLayer_Label('[vary]', 1, 1, x=14, y=0, w=4, h=3, color=COLOR_SIG_VARY, 1549 action=self.__ref(lambda x,y,e: self.__onVaryConstant(nm))) 1550 layer.add_sublayer(sub) 1551 self.layers.insert(i, layer) 1552 self.add_layer(layer)
1553 #for j, layer in enumerate(self.layers[i+1:]): 1554 # layer.x = 10+j*20
1555 - def remove_const(self, name):
1556 i = self.names.index(name) 1557 del self.valudict[name] 1558 self.remove_layer(self.layers[i]) 1559 del self.names[i] 1560 del self.labels[i] 1561 del self.subs[i] 1562 del self.layers[i] 1563 for j, layer in enumerate(self.layers[i:]): 1564 layer.x = 10+j*20
1565 - def set_const(self, name, value):
1566 self.valudict[name] = value 1567 self.subs[self.names.index(name)].label = '%.3g' % value 1568 self.OnSet(name, value)
1569 - def __onSetFontSize(self, font_size):
1570 self.set_size_request(100, 4*self.appearance.emsize)
1571 - def __onClickConstant(self, nm):
1572 i = self.names.index(nm) 1573 dlg = qubx.GTK.NumEntryDialog('%s - Enter a number'%qubx.pyenv.env.globals['QubX'].appname, 1574 None, '%s:'%self.names[i], 1575 '%.3g'%self.valudict[self.names[i]], acceptFloat) 1576 if gtk.RESPONSE_ACCEPT == dlg.run(): 1577 qubx.pyenv.env.OnScriptable('QubX.Simulation.constants.set_const(%s, %s)' % (repr(self.names[i]), repr(dlg.value))) 1578 self.set_const(self.names[i], dlg.value) 1579 dlg.destroy()
1580 - def __onScrollConstant(self, nm, event, offset):
1581 i = self.names.index(nm) 1582 self.defer_scriptable_scroll(nm, 1583 'QubX.Simulation.constants.set_const(%s, scrolled_float(QubX.Simulation.constants.valudict[%s], %%s))' % 1584 (repr(nm), repr(nm)), 1585 offset, event.state & gdk.CONTROL_MASK, event.state & gdk.SHIFT_MASK) 1586 self.set_const(nm, scrolled_float(self.valudict[nm], offset, 1587 event.state & gdk.CONTROL_MASK, event.state & gdk.SHIFT_MASK))
1588 - def __onVaryConstant(self, nm):
1589 i = self.names.index(nm) 1590 qubx.pyenv.env.OnScriptable('QubX.Simulation.protocol.add_channel(%s); QubX.Simulation.robot.sync()' % repr(nm)) 1591 self.protocol.add_channel(nm) 1592 self.update(self.model)
1593 - def __onEditProtocol(self):
1594 self.update(self.model)
1595 - def __onDraw(self, context, w, h):
1596 context.set_source_rgb(0,0,0) 1597 context.paint()
1598 1599 1600 @Propertied(Property('sweep_duration', 10000.0, 'milliseconds in a segment (sweep)'), 1601 Property('sweep_repeat', 1, 'number of times to repeat each set of levels'), 1602 Property('sampling_kHz', 10.0, 'sampling rate'), 1603 Property('name_1', 'Ligand', 'name of first stimulus variable'), 1604 Property('start_rep_1', 1, 'first stimulus stays at resting level until this rep'), 1605 Property('resting_1', 0.0, 'first stimulus resting level'), 1606 Property('prepulse_duration_1', 100.0, 'milliseconds before first stimulus rises'), 1607 Property('pulse_duration_1', 10.0, 'milliseconds at level'), 1608 Property('pulse_levels_1', [1.0], 'list of first stimulus pulse amplitudes'), 1609 Property('pulse_filter_1', False, 'True to apply low-pass Gaussian filter'), 1610 Property('pulse_filter_kHz_1', 1.0, 'low-pass filter freq'), 1611 Property('stim_2', False, 'True to simulate with two stimulus variables'), 1612 Property('name_2', 'Ligand', 'name of 2nd stimulus variable'), 1613 Property('start_rep_2', 1, '2nd stimulus stays at resting level until this rep'), 1614 Property('resting_2', 0.0, '2nd stimulus resting level'), 1615 Property('prepulse_duration_2', 100.0, 'milliseconds before 2nd stimulus rises'), 1616 Property('pulse_duration_2', 10.0, 'milliseconds at level'), 1617 Property('pulse_levels_2', [1.0], 'list of 2nd stimulus pulse amplitudes'), 1618 Property('pulse_filter_2', False, 'True to apply low-pass Gaussian filter'), 1619 Property('pulse_filter_kHz_2', 1.0, 'low-pass filter freq'), 1620 Property('channel_count', 1000, 'number of simulated channels'))
1621 -class DoseResp_Ladder_Wizard(gtk.Dialog):
1622 - def __init__(self):
1623 gtk.Dialog.__init__(self, "Stimulus ladder wizard - QUB Express", qubx.global_namespace.QubX.Simulation.parent_window, gtk.DIALOG_MODAL) 1624 self.propertied_connect_settings('Dose-Response') 1625 self.__ref = Reffer() 1626 1627 h = pack_item(gtk.HBox(), self.vbox) 1628 pack_label('Sweep duration (ms):', h) 1629 txt = pack_item(qubx.GTK.NumEntry(self.sweep_duration, acceptFloatGreaterThan(0.0), "%.4g", width_chars=6), h) 1630 self.propertied_connect_NumEntry('sweep_duration', txt) 1631 pack_space(h, expand=True) 1632 pack_label('Repeat each sweep:', h) 1633 txt = pack_item(qubx.GTK.NumEntry(self.sweep_repeat, acceptIntGreaterThan(0), width_chars=3), h) 1634 self.propertied_connect_NumEntry('sweep_repeat', txt) 1635 pack_space(h, expand=True) 1636 pack_label('Sampling (kHz):', h) 1637 txt = pack_item(qubx.GTK.NumEntry(self.sampling_kHz, acceptFloatGreaterThan(0.0), width_chars=6), h) 1638 self.propertied_connect_NumEntry('sampling_kHz', txt) 1639 pack_space(h, expand=True) 1640 1641 pack_space(self.vbox, y=6) 1642 h = pack_item(gtk.HBox(), self.vbox) 1643 pack_label('Variable 1:', h) 1644 txt = pack_item(qubx.GTK.SuggestiveComboBox(self.name_1, str, str), h) 1645 txt.OnPopup += self.__ref(self.__onPopupName) 1646 self.propertied_connect_NumEntry('name_1', txt) 1647 pack_space(h, expand=True) 1648 pack_label('starts on repeat number:', h) 1649 txt = pack_item(qubx.GTK.NumEntry(self.start_rep_1, acceptIntGreaterThan(0), width_chars=3), h) 1650 self.propertied_connect_NumEntry('start_rep_1', txt) 1651 pack_space(h, expand=True) 1652 1653 h = pack_item(gtk.HBox(), self.vbox) 1654 pack_label('Resting level:', h) 1655 txt = pack_item(qubx.GTK.NumEntry(self.resting_1, acceptFloat, "%.4g", width_chars=6), h) 1656 self.propertied_connect_NumEntry('resting_1', txt) 1657 pack_space(h, expand=True) 1658 pack_label('Pre-pulse duration (ms):', h) 1659 txt = pack_item(qubx.GTK.NumEntry(self.prepulse_duration_1, acceptFloatGreaterThanOrEqualTo(0.0), "%.4g", width_chars=6), h) 1660 self.propertied_connect_NumEntry('prepulse_duration_1', txt) 1661 pack_space(h, expand=True) 1662 pack_label('Pulse duration (ms):', h) 1663 txt = pack_item(qubx.GTK.NumEntry(self.pulse_duration_1, acceptFloatGreaterThan(0.0), "%.4g", width_chars=6), h) 1664 self.propertied_connect_NumEntry('pulse_duration_1', txt) 1665 pack_space(h, expand=True) 1666 1667 h = pack_item(gtk.HBox(), self.vbox) 1668 pack_label('Pulse levels:', h) 1669 txt = pack_item(qubx.GTK.NumEntry(self.pulse_levels_1, acceptFloatList(), formatList("%.4g")), h, expand=True) 1670 self.propertied_connect_NumEntry('pulse_levels_1', txt) 1671 chk = pack_check('Filter:', h) 1672 self.propertied_connect_check('pulse_filter_1', chk) 1673 txt = pack_item(qubx.GTK.NumEntry(self.pulse_filter_kHz_1, acceptFloatGreaterThan(0.0), '%.2g', width_chars=5), h) 1674 self.propertied_connect_NumEntry('pulse_filter_kHz_2', txt) 1675 pack_label('[kHz]', h) 1676 1677 pack_space(self.vbox, y=6) 1678 h = pack_item(gtk.HBox(), self.vbox) 1679 chk = pack_check('Variable 2:', h) 1680 self.propertied_connect_check('stim_2', chk) 1681 self.pan2name = pack_item(gtk.HBox(), h) 1682 txt = pack_item(qubx.GTK.SuggestiveComboBox(self.name_2, str, str), self.pan2name) 1683 txt.OnPopup += self.__ref(self.__onPopupName) 1684 self.propertied_connect_NumEntry('name_2', txt) 1685 pack_space(self.pan2name, expand=True) 1686 pack_label('starts on repeat number:', self.pan2name) 1687 txt = pack_item(qubx.GTK.NumEntry(self.start_rep_2, acceptIntGreaterThan(0), width_chars=3), self.pan2name) 1688 self.propertied_connect_NumEntry('start_rep_2', txt) 1689 pack_space(self.pan2name, expand=True) 1690 self.pan2name.set_sensitive(self.stim_2) 1691 1692 self.pan2 = pack_item(gtk.VBox(), self.vbox, show=self.stim_2) 1693 h = pack_item(gtk.HBox(), self.pan2) 1694 pack_label('Resting level:', h) 1695 txt = pack_item(qubx.GTK.NumEntry(self.resting_2, acceptFloat, "%.4g", width_chars=6), h) 1696 self.propertied_connect_NumEntry('resting_2', txt) 1697 pack_space(h, expand=True) 1698 pack_label('Pre-pulse duration (ms):', h) 1699 txt = pack_item(qubx.GTK.NumEntry(self.prepulse_duration_2, acceptFloatGreaterThanOrEqualTo(0.0), "%.4g", width_chars=6), h) 1700 self.propertied_connect_NumEntry('prepulse_duration_2', txt) 1701 pack_space(h, expand=True) 1702 pack_label('Pulse duration (ms):', h) 1703 txt = pack_item(qubx.GTK.NumEntry(self.pulse_duration_2, acceptFloatGreaterThan(0.0), "%.4g", width_chars=6), h) 1704 self.propertied_connect_NumEntry('pulse_duration_2', txt) 1705 pack_space(h, expand=True) 1706 1707 h = pack_item(gtk.HBox(), self.pan2) 1708 pack_label('Pulse levels:', h) 1709 txt = pack_item(qubx.GTK.NumEntry(self.pulse_levels_2, acceptFloatList(), formatList("%.4g")), h, expand=True) 1710 self.propertied_connect_NumEntry('pulse_levels_2', txt) 1711 chk = pack_check('Filter:', h) 1712 self.propertied_connect_check('pulse_filter_2', chk) 1713 txt = pack_item(qubx.GTK.NumEntry(self.pulse_filter_kHz_2, acceptFloatGreaterThan(0.0), '%.2g', width_chars=5), h) 1714 self.propertied_connect_NumEntry('pulse_filter_kHz_2', txt) 1715 pack_label('[kHz]', h) 1716 1717 pack_space(self.vbox, y=6) 1718 h = pack_item(gtk.HBox(), self.vbox) 1719 pack_label('Channel count:', h) 1720 txt = pack_item(qubx.GTK.NumEntry(self.channel_count, acceptIntGreaterThan(0), width_chars=6), h) 1721 self.propertied_connect_NumEntry('channel_count', txt) 1722 1723 h = pack_item(gtk.HBox(), self.vbox, at_end=True) 1724 pack_item(qubx.settingsGTK.PresetsMenu('Dose-Response', parent=self), h) 1725 pack_button('OK', h, bind(self.response, gtk.RESPONSE_ACCEPT), at_end=True) 1726 pack_space(h, x=6, at_end=True) 1727 pack_button('Cancel', h, bind(self.response, gtk.RESPONSE_REJECT), at_end=True)
1728 - def propertied_set(self, value, name):
1729 super(DoseResp_Ladder_Wizard, self).propertied_set(value, name) 1730 if name == 'stim_2': 1731 if value: 1732 self.pan2.show() 1733 else: 1734 self.pan2.hide() 1735 self.pan2name.set_sensitive(value)
1736 - def __onPopupName(self, combo, suggest):
1737 for name in sorted(qubx.global_namespace.QubX.Models.file.get_stimulus().keys()): 1738 suggest(name)
1739 - def run(self):
1740 response = gtk.Dialog.run(self) 1741 self.hide() 1742 if response == gtk.RESPONSE_ACCEPT: 1743 sim = qubx.global_namespace.QubX.Simulation 1744 sim.paused = True 1745 sim.sampling_khz = self.sampling_kHz 1746 have_2 = self.stim_2 and self.name_2 and (self.name_2 != self.name_1) 1747 sweep_count = max(len(self.pulse_levels_1), len(self.pulse_levels_2)) if have_2 else len(self.pulse_levels_1) 1748 sim.segment_count = self.sweep_repeat * sweep_count 1749 def build_stim(name, start_rep, resting, predur, dur, levels, filter, filter_kHz): 1750 postdur = self.sweep_duration - (predur + dur) 1751 sim.protocol.add_channel(name) 1752 rep_levels = [] 1753 ll = resting 1754 for s in xrange(sweep_count): 1755 if s < len(levels): 1756 ll = levels[s] 1757 for r in xrange(self.sweep_repeat): 1758 if (r+1) < start_rep: 1759 rep_levels.append(resting) 1760 else: 1761 rep_levels.append(ll) 1762 chan = sim.protocol.channels[len(sim.protocol.channels)-1] 1763 chan.insert_shape_dict(0, {'postAmp factor': 1.0, 'postAmp': resting, '__data': 'Step', 'Amp factor': 1.0, 'preAmp': resting, '__root_name': 'Shape', 'Duration factor': 1.0, 'preDuration': 1e-3*predur, 'Amp incr': 0.0, 'preDuration incr': 0.0, 'preAmp incr': 0.0, 'preDuration factor': 1.0, 'postAmp incr': 0.0, 'postDuration incr': 0.0, 'Duration': 1e-3*dur, 'Amp': rep_levels, 'Duration incr': 0.0, 'postDuration factor': 1.0, 'postDuration': 1e-3*postdur, 'preAmp factor': 1.0}) 1764 chan.filter_kHz = filter_kHz if filter else 0.0
1765 1766 sim.protocol.reset() 1767 if self.name_1: 1768 build_stim(self.name_1, self.start_rep_1, self.resting_1, self.prepulse_duration_1, self.pulse_duration_1, self.pulse_levels_1, self.pulse_filter_1, self.pulse_filter_kHz_1) 1769 if have_2: 1770 build_stim(self.name_2, self.start_rep_2, self.resting_2, self.prepulse_duration_2, self.pulse_duration_2, self.pulse_levels_2, self.pulse_filter_2, self.pulse_filter_kHz_2) 1771 qubx.global_namespace.QubX.Models.file.channelCount = self.channel_count 1772 sim.paused = False 1773 1774 return response
1775
1776 -def doseresp_ladder_wizard():
1777 dlg = DoseResp_Ladder_Wizard() 1778 dlg.run() 1779 dlg.destroy()
1780 1781 Tools.register('presets', 'Stimulus ladder wizard...', doseresp_ladder_wizard) 1782 1783 1784 1785 @Propertied(Property('varname', 'Ligand', 'name of stimulus variable'), 1786 Property('level_rest', 0.0, 'resting level'), 1787 Property('level_cond', 1.0, 'conditioning level'), 1788 Property('level_test', 1.0, 'test level'), 1789 Property('dur_cond', 100.0, 'conditioning pulse duration'), 1790 Property('dur_test', 100.0, 'test pulse duration'), 1791 Property('dur_recovery', [1.0, 10.0, 100.0], 'list of recovery durations'), 1792 Property('channel_count', 1000, 'number of simulated channels'), 1793 Property('ratio_cond', [1.0], 'list of k0_cond/k0_rest per rate'), 1794 Property('ratio_test', [1.0], 'list of k0_test/k0_rest per rate'))
1795 -class PairedPulse_Wizard(gtk.Dialog):
1796 - def __init__(self):
1797 gtk.Dialog.__init__(self, "Paired pulse wizard - QUB Express", qubx.global_namespace.QubX.Simulation.parent_window, gtk.DIALOG_MODAL) 1798 self.propertied_connect_settings('PairedPulse') 1799 self.__ref = Reffer() 1800 self.__installed = False 1801 1802 self.figure = pack_item(ToolSpace(), self.vbox) 1803 self.figure.set_size_request(-1, 100) 1804 self.figure.OnDraw += self.__ref(self.__onDrawFigure) 1805 1806 h = pack_item(gtk.HBox(True), self.vbox) 1807 pack_label('Variable name:', h) 1808 txt = pack_item(qubx.GTK.SuggestiveComboBox(self.varname, str, str), h) 1809 txt.OnPopup += self.__ref(self.__onPopupVarname) 1810 self.propertied_connect_NumEntry('varname', txt) 1811 1812 h = pack_item(gtk.HBox(True), self.vbox) 1813 pack_label('Resting level:', h) 1814 txt = pack_item(qubx.GTK.NumEntry(self.level_rest, acceptFloat, "%.4g", width_chars=6), h) 1815 self.propertied_connect_NumEntry('level_rest', txt) 1816 txt.OnChange += self.__ref(self.__onParamChange) 1817 h = pack_item(gtk.HBox(True), self.vbox) 1818 pack_label('Conditioning level:', h) 1819 txt = pack_item(qubx.GTK.NumEntry(self.level_cond, acceptFloat, "%.4g", width_chars=6), h) 1820 self.propertied_connect_NumEntry('level_cond', txt) 1821 txt.OnChange += self.__ref(self.__onParamChange) 1822 txt.modify_base(gtk.STATE_NORMAL, gdk.color_parse("#EEEEFF")) 1823 h = pack_item(gtk.HBox(True), self.vbox) 1824 pack_label('Test level:', h) 1825 txt = pack_item(qubx.GTK.NumEntry(self.level_test, acceptFloat, "%.4g", width_chars=6), h) 1826 self.propertied_connect_NumEntry('level_test', txt) 1827 txt.OnChange += self.__ref(self.__onParamChange) 1828 txt.modify_base(gtk.STATE_NORMAL, gdk.color_parse("#FFFFDD")) 1829 h = pack_item(gtk.HBox(True), self.vbox) 1830 pack_label('Conditioning duration [ms]:', h) 1831 txt = pack_item(qubx.GTK.NumEntry(self.dur_cond, acceptFloat, "%.4g", width_chars=6), h) 1832 self.propertied_connect_NumEntry('dur_cond', txt) 1833 txt.OnChange += self.__ref(self.__onParamChange) 1834 txt.modify_base(gtk.STATE_NORMAL, gdk.color_parse("#EEEEFF")) 1835 h = pack_item(gtk.HBox(True), self.vbox) 1836 pack_label('Test pulse duration [ms]:', h) 1837 txt = pack_item(qubx.GTK.NumEntry(self.dur_test, acceptFloat, "%.4g", width_chars=6), h) 1838 self.propertied_connect_NumEntry('dur_test', txt) 1839 txt.OnChange += self.__ref(self.__onParamChange) 1840 txt.modify_base(gtk.STATE_NORMAL, gdk.color_parse("#FFFFDD")) 1841 1842 h = pack_item(gtk.HBox(), self.vbox) 1843 pack_label('Recovery durations [ms]:', h) 1844 txt = pack_item(qubx.GTK.NumEntry(self.dur_recovery, acceptFloatList(acceptFloatGreaterThan(0.0)), formatList("%.4g")), h, expand=True) 1845 self.propertied_connect_NumEntry('dur_recovery', txt) 1846 txt.OnChange += self.__ref(self.__onParamChange) 1847 txt.modify_base(gtk.STATE_NORMAL, gdk.color_parse("#FFDDDD")) 1848 1849 h = pack_item(gtk.HBox(True), self.vbox) 1850 pack_label('Channel count:', h) 1851 txt = pack_item(qubx.GTK.NumEntry(self.channel_count, acceptIntGreaterThan(0), width_chars=6), h) 1852 self.propertied_connect_NumEntry('channel_count', txt) 1853 1854 self.panK = pack_item(gtk.VBox(), self.vbox) 1855 1856 h = pack_item(gtk.HBox(), self.vbox, at_end=True) 1857 pack_button('OK', h, bind(self.response, gtk.RESPONSE_ACCEPT), at_end=True) 1858 pack_space(h, x=6, at_end=True) 1859 pack_button('Cancel', h, bind(self.response, gtk.RESPONSE_REJECT), at_end=True)
1860 - def __onPopupVarname(self, combo, suggest):
1861 for name in sorted(qubx.global_namespace.QubX.Models.file.get_stimulus().keys()): 1862 suggest(name)
1863 - def __onParamChange(self, *args):
1864 self.figure.redraw_canvas(True)
1865 - def __onDrawFigure(self, context, w, h):
1866 context.set_source_rgb(1,1,1) 1867 context.paint() 1868 dur_recovery = self.dur_recovery 1869 if not dur_recovery: 1870 return 1871 dt = 1.0/qubx.global_namespace.QubX.Simulation.sampling_khz 1872 dur_cond, dur_test, level_rest, level_cond, level_test = self.dur_cond, self.dur_test, self.level_rest, self.level_cond, self.level_test 1873 dur = dt + dur_cond + max(dur_recovery) + dur_test + dur_test 1874 ppx = w/dur 1875 yMin = min(level_rest, level_cond, level_test) 1876 yMax = max(level_rest, level_cond, level_test) 1877 rng = yMax - yMin 1878 margin = .1*rng 1879 yMin, yMax, rng = yMin-margin, yMax+margin, rng+2*margin 1880 ppy = h/rng 1881 x2p = lambda t: ppx*t 1882 y2p = lambda y: ppy*(yMax-y) 1883 y_rest, y_cond, y_test = y2p(level_rest), y2p(level_cond), y2p(level_test) 1884 context.set_source_rgb(.9,.9,1) 1885 context.rectangle(x2p(dt), 0, x2p(dur_cond), h) 1886 context.fill() 1887 context.set_source_rgb(1,1,.8) 1888 context.rectangle(x2p(dt+dur_cond+dur_recovery[0]), 0, x2p(dur_test), h) 1889 context.fill() 1890 def draw_trace(i): 1891 context.set_source_rgb(*(i and (.5,.5,.5) or (0,0,0))) 1892 context.move_to(0, y_rest) 1893 context.line_to(x2p(dt), y_rest) 1894 context.line_to(x2p(dt), y_cond) 1895 t = dt+dur_cond 1896 context.line_to(x2p(t), y_cond) 1897 context.line_to(x2p(t), y_rest) 1898 context.stroke() 1899 context.set_source_rgb(*(i and (1,.8,.8) or (1,.5,.5))) 1900 context.move_to(x2p(t), y_rest) 1901 t += dur_recovery[i] 1902 context.line_to(x2p(t), y_rest) 1903 context.stroke() 1904 context.set_source_rgb(*(i and (.5,.5,.5) or (0,0,0))) 1905 context.move_to(x2p(t), y_rest) 1906 context.line_to(x2p(t), y_test) 1907 t += dur_test 1908 context.line_to(x2p(t), y_test) 1909 context.line_to(x2p(t), y_rest) 1910 t += dur_test 1911 context.line_to(x2p(t), y_rest) 1912 context.stroke()
1913 for i in reversed(xrange(len(dur_recovery))): 1914 draw_trace(i)
1915 - def run(self):
1916 self.read_model() 1917 response = gtk.Dialog.run(self) 1918 self.hide() 1919 if response == gtk.RESPONSE_ACCEPT: 1920 sim = qubx.global_namespace.QubX.Simulation 1921 sim.paused = True 1922 self.write_model() 1923 self.build_protocol() 1924 self.install_finisher() 1925 sim.paused = False 1926 return response
1927 - def read_model(self):
1928 model = qubx.global_namespace.QubX.Models.file 1929 self.channel_count = model.channelCount 1930 rates = model.rates 1931 self.panK.foreach(lambda item: self.panK.remove(item)) 1932 h = pack_item(gtk.HBox(True), self.panK) 1933 pack_item(gtk.EventBox(), h) 1934 pack_label('Resting', h) 1935 pack_label('Conditioning', h) 1936 pack_label('Test', h) 1937 ratio_cond = self.ratio_cond[:] 1938 ratio_test = self.ratio_test[:] 1939 self.txtRest = [] 1940 self.txtCond = [] 1941 self.txtTest = [] 1942 while len(ratio_cond) < rates.size: 1943 ratio_cond.append(1.0) 1944 ratio_test.append(1.0) 1945 for rate in rates: 1946 k0 = rate.k0 1947 h = pack_item(gtk.HBox(True), self.panK) 1948 pack_label('k %i->%i' % (rate.From, rate.To), h) 1949 self.txtRest.append(pack_item(qubx.GTK.NumEntry(k0, acceptFloatGreaterThan(0.0), width_chars=8), h)) 1950 txt = pack_item(qubx.GTK.NumEntry(k0*ratio_cond[rate.Index], acceptFloatGreaterThan(0.0), width_chars=8), h) 1951 txt.modify_base(gtk.STATE_NORMAL, gdk.color_parse("#EEEEFF")) 1952 self.txtCond.append(txt) 1953 txt = pack_item(qubx.GTK.NumEntry(k0*ratio_test[rate.Index], acceptFloatGreaterThan(0.0), width_chars=8), h) 1954 txt.modify_base(gtk.STATE_NORMAL, gdk.color_parse("#FFFFDD")) 1955 self.txtTest.append(txt) 1956 model_stim = model.get_stimulus() 1957 if model_stim and not (self.varname in model_stim): 1958 self.varname = model_stim.iterkeys().next()
1959 - def write_model(self):
1960 model = qubx.global_namespace.QubX.Models.file 1961 model.channelCount = self.channel_count 1962 rates = model.rates 1963 ratio_cond = [] 1964 ratio_test = [] 1965 for i in xrange(rates.size): 1966 k0 = self.txtRest[i].value 1967 rates.set(i, 'k0', k0) 1968 k1r = self.txtCond[i].value / k0 1969 rates.set(i, 'k1', log(k1r)) 1970 ratio_cond.append(k1r) 1971 rates.set(i, 'Voltage', 'InCond') 1972 k2r = self.txtTest[i].value / k0 1973 rates.set(i, 'k2', log(k2r)) 1974 ratio_test.append(k2r) 1975 rates.set(i, 'Pressure', 'InTest') 1976 self.ratio_cond = ratio_cond 1977 self.ratio_test = ratio_test
1978 - def build_protocol(self):
1979 sim = qubx.global_namespace.QubX.Simulation 1980 sweep_count = max(1, len(self.dur_recovery)) 1981 sim.segment_count = sweep_count 1982 sim.protocol.reset() 1983 sim.protocol.add_channel(self.varname) 1984 chan = sim.protocol.channels[-1] 1985 chan.insert_shape_dict(0, { 1986 '__root_name': 'Shape', '__data': 'Step', 1987 'preAmp': self.level_rest, 'preAmp incr': 0.0, 'preAmp factor': 1.0, 1988 'preDuration': 1e-3/sim.sampling_khz, 'preDuration incr': 0.0, 'preDuration factor': 1.0, 1989 'Amp': self.level_cond, 'preAmp incr': 0.0, 'preAmp factor': 1.0, 1990 'Duration': 1e-3*self.dur_cond, 'preDuration incr': 0.0, 'preDuration factor': 1.0, 1991 'postAmp': self.level_rest, 'postAmp incr': 0.0, 'postAmp factor': 1.0, 1992 'postDuration': 0.0, 'postDuration incr': 0.0, 'postDuration factor': 1.0 1993 }) 1994 preDur = [1e-3*recovery for recovery in self.dur_recovery] 1995 chan.insert_shape_dict(1, { 1996 '__root_name': 'Shape', '__data': 'Step', 1997 'preAmp': self.level_rest, 'preAmp incr': 0.0, 'preAmp factor': 1.0, 1998 'preDuration': preDur, 'preDuration incr': 0.0, 'preDuration factor': 1.0, 1999 'Amp': self.level_test, 'preAmp incr': 0.0, 'preAmp factor': 1.0, 2000 'Duration': 1e-3*self.dur_test, 'preDuration incr': 0.0, 'preDuration factor': 1.0, 2001 'postAmp': self.level_rest, 'postAmp incr': 0.0, 'postAmp factor': 1.0, 2002 'postDuration': 1e-3*self.dur_test, 'postDuration incr': 0.0, 'postDuration factor': 1.0 2003 }) 2004 sim.protocol.add_channel('InCond') 2005 chan = sim.protocol.channels[-1] 2006 chan.insert_shape_dict(0, { 2007 '__root_name': 'Shape', '__data': 'Step', 2008 'preAmp': 0.0, 'preAmp incr': 0.0, 'preAmp factor': 1.0, 2009 'preDuration': 1e-3/sim.sampling_khz, 'preDuration incr': 0.0, 'preDuration factor': 1.0, 2010 'Amp': 1.0, 'preAmp incr': 0.0, 'preAmp factor': 1.0, 2011 'Duration': 1e-3*self.dur_cond, 'preDuration incr': 0.0, 'preDuration factor': 1.0, 2012 'postAmp': 0.0, 'postAmp incr': 0.0, 'postAmp factor': 1.0, 2013 'postDuration': 1e-3/sim.sampling_khz, 'postDuration incr': 0.0, 'postDuration factor': 1.0 2014 }) 2015 sim.protocol.add_channel('InTest') 2016 chan = sim.protocol.channels[-1] 2017 preDur = [1e-3*(1/sim.sampling_khz + self.dur_cond + recovery) for recovery in self.dur_recovery] 2018 chan.insert_shape_dict(0, { 2019 '__root_name': 'Shape', '__data': 'Step', 2020 'preAmp': 0.0, 'preAmp incr': 0.0, 'preAmp factor': 1.0, 2021 'preDuration': preDur, 'preDuration incr': 0.0, 'preDuration factor': 1.0, 2022 'Amp': 1.0, 'preAmp incr': 0.0, 'preAmp factor': 1.0, 2023 'Duration': 1e-3*self.dur_test, 'preDuration incr': 0.0, 'preDuration factor': 1.0, 2024 'postAmp': 0.0, 'postAmp incr': 0.0, 'postAmp factor': 1.0, 2025 'postDuration': 1e-3/sim.sampling_khz, 'postDuration incr': 0.0, 'postDuration factor': 1.0 2026 })
2027 - def install_finisher(self):
2028 if not self.__installed: 2029 self.__installed = True 2030 qubx.global_namespace.QubX.Simulation.OnFinish += self.__ref(self.__onFinish)
2031 - def is_relevant(self):
2032 sim = qubx.global_namespace.QubX.Simulation 2033 if sim.protocol.channel_count != 3: 2034 return False 2035 if sim.protocol.channels[0].label != self.varname: 2036 return False 2037 if sim.protocol.channels[1].label != 'InCond': 2038 return False 2039 if sim.protocol.channels[2].label != 'InTest': 2040 return False 2041 if sim.protocol.segment_count != len(self.dur_recovery): 2042 return False 2043 rates = qubx.global_namespace.QubX.Models.file.rates 2044 for i in xrange(rates.size): 2045 if rates[i,'Pressure'] != 'InTest': 2046 return False 2047 return True
2048 - def __onFinish(self):
2049 sim = qubx.global_namespace.QubX.Simulation 2050 if not self.is_relevant(): 2051 sim.OnFinish -= self.__ref(self.__onFinish) 2052 self.__installed = False 2053 return 2054 dataview = qubx.global_namespace.QubX.Data.view 2055 data = dataview.file 2056 lst = data.lists.show_list('Segments') 2057 tRC = self.dur_recovery 2058 tCond = self.dur_cond 2059 nPre = [1 + int(round((tCond + recovery)*1e-3/data.sampling)) for recovery in tRC] 2060 nPost = [1 + int(round((tCond + recovery)*1e-3/data.sampling)) for recovery in tRC] 2061 for i in xrange(lst.size): 2062 lst[i, 'Trc'] = tRC[i] 2063 model = qubx.global_namespace.QubX.Models.file 2064 if model.channelCount == 1: 2065 # clear all but the first closed and open events post-recovery, so DurHist shows first latency/first opening 2066 for i,fln in enumerate(data.segmentation.segments): 2067 f,l,n = fln 2068 data.ideal[0].idl.set_dwell(f, f+nPre[i]-1, -1) 2069 ff, ll, cc = data.ideal[0].idl.get_dwells(f, l, True) 2070 if len(cc): 2071 iDel = 2 if (cc[0] == 0) else 1 2072 if iDel < len(cc): 2073 data.ideal[0].idl.set_dwell(ff[iDel], l, -1) 2074 qubx.global_namespace.QubX.Figures.DurHist.request_show() 2075 else: 2076 # measure Tpeak, Ipeak post-recovery 2077 for i,fln in enumerate(data.segmentation.segments): 2078 f,l,n = fln 2079 samples = dataview.get_segmentation_indexed(f+nPre[i], l)[0].get_samples().samples 2080 iPeak = numpy.argmax(samples) 2081 lst[i,"Ipeak"] = samples[iPeak] 2082 lst[i,"Tpeak"] = 1e3 * data.sampling * (nPre[i] + iPeak) 2083 qubx.global_namespace.QubX.Charts.add_two_plot('List', 'Trc', 'Ipeak', False, False) 2084 qubx.global_namespace.QubX.Charts.request_show()
2085 2086 2087 gPairedPulseWizard = None
2088 -def paired_pulse_wizard():
2089 global gPairedPulseWizard 2090 if gPairedPulseWizard is None: 2091 gPairedPulseWizard = PairedPulse_Wizard() 2092 gPairedPulseWizard.run()
2093 2094 Tools.register('presets', 'Paired pulse wizard...', paired_pulse_wizard) 2095