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

Source Code for Module qubx.fit_space

   1  """Widget to display and control L{qubx.fit_robot}. 
   2   
   3  Copyright 2008-2013 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   
  23  import cStringIO 
  24  import gc 
  25  import gobject 
  26  import gtk 
  27  import numpy 
  28  import optparse 
  29  import os 
  30  import re 
  31  import sys 
  32  import traceback 
  33  import qubx.fit 
  34  import qubx.fit_robots 
  35  import qubx.GTK 
  36  import qubx.notebook 
  37  import qubx.pyenv 
  38  import qubx.pyenvGTK 
  39  import qubx.settings 
  40  import qubx.toolspace 
  41  import qubx.tree 
  42   
  43  from itertools import izip, count, repeat 
  44  from numpy import random 
  45  from math import * 
  46  from gtk import gdk, keysyms 
  47  from qubx.accept import acceptFloatOrUnset, acceptString, acceptIntGreaterThan, acceptFloatGreaterThan, acceptODEorF 
  48  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem 
  49  from qubx.toolspace import ColorInfo 
  50  from qubx.util_types import WeakEvent, Reffer, ScaleToColor, UNSET_VALUE, bind, bind_with_args, scrolled_float, Anon 
  51   
  52  COLOR_FITSPACE_BG = ('fit.bg', (1,1,1,1)) 
  53  ColorInfo[COLOR_FITSPACE_BG[0]].label = 'Chart background' 
  54  COLOR_FITSPACE_GRID = ('fit.grid', (.3,1,.3,.5)) 
  55  ColorInfo[COLOR_FITSPACE_GRID[0]].label = 'Chart grid lines' 
  56  COLOR_FITSPACE_GRIDZERO = ('fit.gridzero', (1,.3,.3,.5)) 
  57  ColorInfo[COLOR_FITSPACE_GRIDZERO[0]].label = 'Chart grid zero line' 
  58  COLOR_FITSPACE_GRIDLBL = ('fit.gridlbl', (0,0,0,1)) 
  59  ColorInfo[COLOR_FITSPACE_GRIDLBL[0]].label = 'Chart grid labels' 
  60  COLOR_FITSPACE_DATA = ('fit.data', (0,0,0,1)) 
  61  ColorInfo[COLOR_FITSPACE_DATA[0]].label = 'Chart data' 
  62  COLOR_FITSPACE_FILL = ('fit.fill', (.5,.5,.5,1)) 
  63  ColorInfo[COLOR_FITSPACE_FILL[0]].label = 'Chart fill (histogram bars)' 
  64  COLOR_FITSPACE_EXTRA = ('fit.extra', (0,0,1,.8)) 
  65  ColorInfo[COLOR_FITSPACE_EXTRA[0]].label = 'Chart data secondary series' 
  66  COLOR_FITSPACE_CURVE = ('fit.curve', (1,0,0,.8)) 
  67  ColorInfo[COLOR_FITSPACE_CURVE[0]].label = 'Chart fit curve' 
  68  COLOR_FITSPACE_LAYER = ('fit.layer', (1,.8,.33,.8)) 
  69  ColorInfo[COLOR_FITSPACE_LAYER[0]].label = 'Chart layer background' 
  70  COLOR_FITSPACE_TEXT = ('fit.text', (0,.4,0,1)) 
  71  ColorInfo[COLOR_FITSPACE_TEXT[0]].label = 'Chart text' 
  72  COLOR_FITSPACE_NUMBER = ('fit.number', (0,0,0,1)) 
  73  ColorInfo[COLOR_FITSPACE_NUMBER[0]].label = 'Chart numbers' 
  74  COLOR_FITSPACE_BUTTON = ('fit.button', (.7,0,0,1)) 
  75  ColorInfo[COLOR_FITSPACE_BUTTON[0]].label = 'Chart buttons' 
  76  COLOR_CORREL_LBL = ('fit.correl.lbl', (1,1,1,1)) 
  77  ColorInfo[COLOR_CORREL_LBL[0]].label = 'Chart correlation numbers' 
  78  COLOR_FITSPACE_BACKBUTTON = ('fit.backbutton', (.2,.3,.2,1)) 
  79  ColorInfo[COLOR_FITSPACE_BACKBUTTON[0]].label = 'Chart "back" from fitting' 
  80   
  81  LINE_EMS = 1.5 
  82   
  83  FITFMT = '%.4g' 
  84   
  85  ZOOM_SCROLL_FACTOR = .12 
  86   
87 -def make_frame(w, h, appearance, controls_showing):
88 """Returns (left, top, right, bottom) pixel coords, telling where to draw the graph given that the whole space has dimensions (w,h).""" 89 em = appearance.emsize 90 line = em * LINE_EMS 91 if controls_showing: 92 aspect = 2.0 93 ah = h - 5*line 94 aw = w - 4*line 95 if aw > ah*aspect: 96 aw = ah*aspect 97 elif ah > aw/aspect: 98 ah = aw/aspect 99 cx = w*.55 # slightly off-center to the right 100 x0 = max(line, min(w-2*line-aw, cx-aw/2)) 101 y0 = h/2 - ah/2 102 else: 103 x0, y0 = 5*em, 3*line 104 aw = w - x0 - 6*em 105 ah = h - 2*y0 106 return x0, y0, x0+aw, y0+ah
107
108 -def make_x_transform(t0, t1, x0, x1):
109 """Returns (x2t, t2x), where t=x2t(x) and x=t2x(t), mapping t in [t0..t1] to x in [x0..x1].""" 110 if t0 == t1: 111 t0, t1 = t0 - .5, t0 + .5 112 xpt = (x1-x0) / (t1-t0) 113 return (lambda x: (x-x0)/xpt + t0, 114 lambda t: xpt*(t-t0) + x0)
115
116 -def make_y_transform(u0, u1, y0, y1):
117 """Returns (y2u, u2y), where u=y2u(y) and y=u2y(u), mapping u in [u0..u1] to y in [y0..y1].""" 118 if u0 == u1: 119 u0, u1 = u0 - .5, u0 + .5 120 ypu = (y1-y0) / (u1-u0) 121 return (lambda y: u1 - (y-y0)/ypu, 122 lambda u: ypu*(u1-u) + y0)
123
124 -def gen_grid(u0, u1, z0, z1, appearance):
125 """Yields successive gridline u-coordinates along one dimension. 126 127 @param u0: lowest value showing 128 @param u1: highest value showing 129 @param z0: lowest x- or y- coord 130 @param z1: highest x- or y- coord 131 @param appearance: L{qubx.toolspace.TS_Appearance} 132 """ 133 target_ppd = 7*appearance.emsize 134 target_u = abs(target_ppd * (u1-u0)/(z1-z0)) 135 scale = pow(10, floor(log10(abs(target_u or 1.0)))) 136 msd = target_u / scale 137 if msd < 1.6: 138 upg = scale 139 elif msd < 3.6: 140 upg = 2*scale 141 elif msd < 8: 142 upg = 5*scale 143 else: 144 upg = 10*scale 145 u = upg * floor(u0/upg) 146 if u < u0: 147 u += upg 148 while u <= u1: 149 yield u 150 u += upg
151
152 -def apply_zoom_axis(x0, x1, p0, p1):
153 w = x1 - x0 154 return (x0 + w*p0, x0 + w*p1)
155
156 -def zoom_axis(factor, p0, p1):
157 w = min(1.0, (p1 - p0)/factor) 158 hw = 0.5 * w 159 c = min(1.0 - hw, max(hw, (p0 + p1) / 2.0)) 160 return (c-hw, c+hw)
161
162 -def zoom_axis_at(factor, p, p0, p1):
163 p_in_frame = (p - p0) / (p1 - p0) 164 w = min(1.0, (p1 - p0)/factor) 165 return (max(0.0, p - p_in_frame*w), min(1.0, p + (1.0 - p_in_frame)*w))
166
167 -def pan_axis(dp, p0, p1):
168 dp = max(-p0, min(1.0 - p1, dp)) 169 return (p0 + dp, p1 + dp)
170
171 -class FitControls(qubx.toolspace.LayerSet):
172 """Toolspace layers to control a L{qubx.fit_robots.FitSession}. 173 174 @ivar robot: L{qubx.fit_robots.FitSession} 175 @ivar OnClickFit: L{WeakEvent}() when Fit button is clicked, to override default behavior (self.robot.fit()) 176 """
177 - def __init__(self, label='Fit', 178 cLayer=COLOR_FITSPACE_LAYER, cText=COLOR_FITSPACE_TEXT, cNumber=COLOR_FITSPACE_NUMBER, cButton=COLOR_FITSPACE_BUTTON, 179 global_name=""):
180 """Creates a new layerset to control a L{qubx.fit_robot.FitRobot}, creating the robot too if None provided. 181 182 @param label: string to identify the robot, if one is created 183 @param cLayer: colorref for floating panels 184 @param cText: colorref for static text in floating panels 185 @param cNumber: colorref for variable/editable text in floating panels 186 @param cButton: colorref for clickable text in floating panels 187 @param global_name: how self might be called globally, for scripting 188 """ 189 qubx.toolspace.LayerSet.__init__(self) 190 self.robot = qubx.fit_robots.FitSession(label) 191 self.my_robot = True 192 if global_name: 193 self.robot.global_name = '%s.robot' % global_name 194 elif not ('global_name' in self.robot.__dict__): 195 self.robot.global_name = None 196 self.sync = self.robot.sync 197 self.label = label 198 self.cLayer = cLayer 199 self.cText = cText 200 self.cNumber = cNumber 201 self.cButton = cButton 202 self.global_name = global_name 203 self.__ref = Reffer() 204 self.defer_scriptable_scroll = qubx.pyenv.DeferredScriptableScroll() 205 self.OnClickFit = WeakEvent() 206 cat = qubx.settings.SettingsMgr['qubx.fit_space.%s' % label] 207 self.profileCat = cat 208 self.profile = cat.active 209 cat.OnSet = self.__ref(self.update_profile) 210 self.presets = qubx.settings.SettingsMgr['qubx.fit_space.curves'] 211 self.robot.OnData += self.__ref(self.__onData) 212 self.robot.OnWeight += self.__ref(self.__onWeight) 213 self.robot.OnExpr += self.__ref(self.__onExpr) 214 self.robot.OnParam += self.__ref(self.__onParam) 215 self.robot.OnMaxIter += self.__ref(self.__onMaxIter) 216 self.robot.OnToler += self.__ref(self.__onToler) 217 self.robot.OnStrategy += self.__ref(self.__onStrategy) 218 self.robot.OnFitter += self.__ref(self.__onFitter) 219 self.robot.OnStats += self.__ref(self.__onStats) 220 self.robot.OnStartFit += self.__ref(self.__onStartFit) 221 self.robot.OnIteration += self.__ref(self.__onIteration) 222 self.robot.OnEndFit += self.__ref(self.__onEndFit) 223 224 self.layExpr = qubx.toolspace.Layer(x=0, y=0, w=-.1, h=LINE_EMS, cBG=cLayer) 225 self.subExprLbl = qubx.toolspace.SubLayer_Label('y = ', -1, 0, x=0, y=0, w=3, h=LINE_EMS, color=cText, 226 action=self.__ref(self.__onClickExpr)) 227 self.layExpr.add_sublayer(self.subExprLbl) 228 self.subExpr = qubx.toolspace.SubLayer_Label('', -1, 0, x=3, y=0, w=-32, h=LINE_EMS, color=cNumber, 229 action=self.__ref(self.__onClickExpr)) 230 self.layExpr.add_sublayer(self.subExpr) 231 self.subWeightLbl = qubx.toolspace.SubLayer_Label('Weights: ', 1, 0, x=-32, y=0, w=6, h=LINE_EMS, color=cText, 232 action=self.__ref(self.__onClickWeight)) 233 self.layExpr.add_sublayer(self.subWeightLbl) 234 self.subWeight = qubx.toolspace.SubLayer_Label('1.0', -1, 0, x=-25, y=0, w=13, h=LINE_EMS, color=cNumber, 235 action=self.__ref(self.__onClickWeight)) 236 self.layExpr.add_sublayer(self.subWeight) 237 self.subPresets = qubx.toolspace.SubLayer_Popup(color=cText, on_popup=self.__ref(self.__onPresetsPopup), caption="Presets...", 238 x=-11, y=0, w=10, h=LINE_EMS) 239 self.subPresets.popup = gtk.Menu() 240 self.layExpr.add_sublayer(self.subPresets) 241 self.layers.append(self.layExpr) 242 243 self.layParams = qubx.toolspace.Layer(x=0, y=LINE_EMS, w=27, h=-1.3*LINE_EMS, cBG=cLayer) 244 self.layers.append(self.layParams) 245 self.sub_check = self.sub_label = self.sub_value = self.sub_pm = self.sub_err = self.sub_more = [] 246 247 self.layStats = qubx.toolspace.Layer(x=-16, y=-7*LINE_EMS, w=15, h=4*LINE_EMS, cBG=cLayer) 248 for i, lbl in enumerate(['SSR: ', 'r2: ', 'P(runs): ', 'Correl: ']): 249 sub = qubx.toolspace.SubLayer_Label(lbl, 1, 0, x=0, y=i*LINE_EMS, w=6, h=LINE_EMS, color=cText) 250 self.layStats.add_sublayer(sub) 251 self.subSSR = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=0, w=8, h=LINE_EMS, color=cNumber) 252 self.layStats.add_sublayer(self.subSSR) 253 self.subR2 = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=LINE_EMS, w=8, h=LINE_EMS, color=cNumber) 254 self.layStats.add_sublayer(self.subR2) 255 self.subRunsProb = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=2*LINE_EMS, w=8, h=LINE_EMS, color=cNumber) 256 self.layStats.add_sublayer(self.subRunsProb) 257 self.subCorrel = SubLayer_Correl(x=9, y=3*LINE_EMS, w=LINE_EMS, h=LINE_EMS, action=self.__ref(self.__onClickCorrel)) 258 self.layStats.add_sublayer(self.subCorrel) 259 self.layers.append(self.layStats) 260 261 self.layBottom = qubx.toolspace.Layer(x=0, y=-LINE_EMS, w=-.1, h=LINE_EMS, cBG=cLayer) 262 self.subN = qubx.toolspace.SubLayer_Label('', -1, 0, x=-40, y=0, w=8, h=LINE_EMS, color=cText) 263 self.layBottom.add_sublayer(self.subN) 264 self.subStrategy = qubx.toolspace.SubLayer_Label('Strategy...', 0, 0, x=-18, y=0, w=9, h=LINE_EMS, color=cButton, 265 action=self.__ref(self.__onClickStrategy)) 266 self.layBottom.add_sublayer(self.subStrategy) 267 self.subFit = qubx.toolspace.SubLayer_Label('Fit', 0, 0, x=-9, y=0, w=9, h=LINE_EMS, color=cButton, 268 action=self.__ref(self.__onClickFit)) 269 self.layBottom.add_sublayer(self.subFit) 270 self.layers.append(self.layBottom) 271 272 self.layFitting = qubx.toolspace.Layer(x=1, y=-7*LINE_EMS, w=-17, h=4*LINE_EMS, cBG=cLayer) 273 self.subFitting = qubx.toolspace.SubLayer_Label('Fitting...', x=1, y=LINE_EMS, w=-1, h=LINE_EMS, color=cButton) 274 self.layFitting.add_sublayer(self.subFitting) 275 self.subProgress = qubx.toolspace.SubLayer_Progress(color=cButton, x=4, y=2.5*LINE_EMS, w=-4, h=LINE_EMS) 276 self.layFitting.add_sublayer(self.subProgress) 277 278 # modal "dialogs": 279 self.layMore = Layer_Param(cLayer, cText, cNumber, cButton, x=28, y=2*LINE_EMS) 280 self.layCorrel = Layer_Correl(cLayer, cText, cNumber, cButton, x=28, y=2*LINE_EMS) 281 282 # strategy window: 283 self.winStrategy = StrategyDialog(label, self.robot) 284 self.winStrategy.OnClickFit += self.__ref(self.__onClickFit) 285 286 self.__xx = self.__yy = self.__vvv = self.__v_names = [] 287 self.__weight = self.__curve = '1.0' 288 self.__curve_obj = qubx.fit.Curve(self.__curve, locals=self.robot.locals.__dict__) 289 self.__params = self.__vals = self.__lo = self.__hi = self.__can_fit = [] 290 self.__max_iter = self.__toler = 1.0 291 self.__correlation = self.__is_pseudo = self.__std_err_est = self.__ssr = self.__r2 = self.__runs_prob = None 292 self.__iteration = 0 293 self.__fitting = self.__modal = False 294 self.__fit_receiver = None 295 296 self.update_profile(cat, self.profile) 297 self.robot.request_data() 298 self.robot.request_curve() 299 self.robot.request_fitparams() 300 self.robot.request_stats()
301 302 param_names = property(lambda self: self.__params) 303 param_vals = property(lambda self: self.__vals) 304 param_lo = property(lambda self: self.__lo) 305 param_hi = property(lambda self: self.__hi) 306 param_can_fit = property(lambda self: self.__can_fit) 307 weight_expr = property(lambda self: self.__weight) 308 curve_expr = property(lambda self: self.__curve) 309 curve = property(lambda self: self.__curve_obj, None, "copy of robot's qubx.fit.Curve, for computing fit curves on gui thread") 310 xx = property(lambda self: self.__xx) 311 yy = property(lambda self: self.__yy) 312 vvv = property(lambda self: self.__vvv) 313 v_names = property(lambda self: self.__v_names) 314 max_iter = property(lambda self: self.__max_iter) 315 toler = property(lambda self: self.__toler) 316 correlation = property(lambda self: self.__correlation) 317 correlation_is_pseudo = property(lambda self: self.__is_pseudo) 318 std_err_est = property(lambda self: self.__std_err_est) 319 ssr = property(lambda self: self.__ssr) 320 r2 = property(lambda self: self.__r2) 321 runs_prob = property(lambda self: self.__runs_prob) 322 fitting = property(lambda self: self.__fitting) 323
324 - def update_profile(self, settings=None, updates=None, curve_only=False):
325 if updates.find('Name').data: 326 try: 327 self.robot.set_curve(qubx.fit.Curves[str(updates['Name'].data)], 328 str(updates.find('Eqn').data) or None) 329 except: 330 if updates.find('Eqn').data: 331 self.robot.set_expr( str(updates['Eqn'].data) ) 332 elif updates.find('Eqn').data: 333 self.robot.set_expr( str(updates['Eqn'].data) ) 334 for param in qubx.tree.children(updates.find('Params')): 335 value, can_fit, lo, hi = param.data[:4] 336 self.robot.set_param(param.name, value, lo, hi, can_fit) 337 if updates.find('Weight').data: 338 self.robot.set_weight( str(updates['Weight'].data) ) 339 if updates.find('Strategy').data: 340 self.robot.set_strategy(str(updates['Strategy'].data)) 341 if not curve_only: 342 if updates.find('MaxIter').data: 343 self.robot.set_max_iter( updates['MaxIter'].data[0] ) 344 if updates.find('Toler').data: 345 self.robot.set_toler( updates['Toler'].data[0] ) 346 if updates.find('Fitter').data: 347 self.robot.set_fitter( qubx.fit.Fitters[str(updates['Fitter'].data)] )
348 - def __onPresetsPopup(self):
349 self.subPresets.popup.foreach(lambda item: self.subPresets.popup.remove(item)) 350 for name in sorted(qubx.fit.Curves.keys()): 351 build_menuitem(name, self.__ref(bind(self.__use_curve_class, name)), menu=self.subPresets.popup) 352 for name in self.presets.listNames(): 353 build_menuitem(name, self.__ref(bind(self.__use_preset, name)), menu=self.subPresets.popup) 354 build_menuitem('Manage...', self.__ref(self.__onClickManagePresets), menu=self.subPresets.popup) 355 build_menuitem('Add to menu...', self.__ref(self.__onClickAddPreset), menu=self.subPresets.popup) 356 return True
357 - def __use_curve_class(self, name):
358 if self.global_name: 359 qubx.pyenv.env.scriptable_if_matching('%s.robot.set_curve(qubx.fit.Curves[%s])' % (self.global_name, repr(name)), 360 [(self.global_name, self)]) 361 self.robot.set_curve(qubx.fit.Curves[name])
362 - def __use_preset(self, name):
363 if self.global_name: 364 qubx.pyenv.env.scriptable_if_matching('%s.update_profile(updates=SettingsMgr[%s].read(%s), curve_only=True)' % 365 (self.global_name, repr(self.presets.name), repr(name)), 366 [(self.global_name, self)]) 367 self.update_profile(self.profileCat, self.presets.read(name), curve_only=True)
368 - def __onClickManagePresets(self, item):
369 dlg = qubx.settingsGTK.PresetsDialog(self.presets.name, None) 370 dlg.run() 371 dlg.destroy()
372 - def __onClickAddPreset(self, item):
373 name = None 374 dlg = qubx.GTK.NumEntryDialog('%s - Add curve to menu'%qubx.pyenv.env.globals['QubX'].appname, 375 None, 'Name of curve:', 'Untitled', acceptString) 376 if gtk.RESPONSE_ACCEPT == dlg.run(): 377 name = dlg.value 378 dlg.destroy() 379 if not name: return 380 if self.global_name: 381 qubx.pyenv.env.scriptable_if_matching('%s.add_preset(%s)' % (self.global_name, repr(name)), 382 [(self.global_name, self)]) 383 self.add_preset(name)
384 - def add_preset(self, name):
385 preset = qubx.tree.Node('Curve') 386 preset.appendClone(self.profile['Name']) 387 preset.appendClone(self.profile['Eqn']) 388 preset.appendClone(self.profile['Weight']) 389 preset.appendClone(self.profile['Params']) 390 preset.appendClone(self.profile['Strategy']) 391 qubx.settings.SettingsMgr.save(preset, self.presets.name, name)
392 - def dispose(self):
393 """If you didn't provide a robot to the constructor, call this when you're done to stop the robot thread.""" 394 if self.my_robot: 395 self.robot.dispose() 396 self.__ref.clear() 397 self.winStrategy.destroy()
398 - def sync(self):
399 """Waits for any previous robot commands to take effect before returning.""" 400 qubx.pyenv.call_async_wait(self.robot.do)
401 - def synch(self):
402 """Waits for any previous robot commands to take effect before returning.""" 403 qubx.pyenv.call_async_wait(self.robot.do)
404
405 - def __onData(self, xx, yy, vvv, v_names):
406 self.__xx = xx 407 self.__yy = yy 408 self.__vvv = vvv 409 self.__v_names = v_names 410 self.__curve_obj.set_vars(v_names) 411 self.subN.label = 'N: %d' % len(xx)
412 - def __onWeight(self, weight_expr):
413 self.__weight = weight_expr 414 self.subWeight.label = weight_expr 415 self.profile['Weight'].data = weight_expr 416 self.robot.request_stats()
417 - def __onExpr(self, curve_name, curve_expr, params, param_vals, lo, hi, can_fit):
418 self.__curve = curve_expr 419 self.subExpr.label = curve_expr 420 self.__curve_obj = qubx.fit.Curves[curve_name](curve_expr, locals=self.robot.locals.__dict__) 421 self.__curve_obj.set_vars(self.__v_names) 422 self.__params = params[:] 423 self.__vals = param_vals[:] 424 self.__lo = lo[:] 425 self.__hi = hi[:] 426 self.__can_fit = can_fit[:] 427 self.layParams.clear_sublayers() 428 self.sub_check = [] 429 self.sub_label = [] 430 self.sub_value = [] 431 self.sub_pm = [] 432 self.sub_err = [] 433 self.sub_more = [] 434 self.__ref_expr = Reffer() # discard stale callbacks 435 for i in xrange(len(params)): 436 sub = qubx.toolspace.SubLayer_Check(color=self.cButton, x=.5, y=i*LINE_EMS, w=1, h=LINE_EMS) 437 sub.set_active(can_fit[i]) 438 sub.OnToggle += self.__ref_expr(bind(self.__onToggle, i)) 439 self.sub_check.append(sub) 440 self.layParams.add_sublayer(sub) 441 sub = qubx.toolspace.SubLayer_Label('%i: %s' % (i+1, params[i]), -1, 0, x=2, y=i*LINE_EMS, w=6, h=LINE_EMS, color=self.cText, 442 action=self.__ref_expr(bind(self.__onToggle, i))) 443 self.sub_label.append(sub) 444 self.layParams.add_sublayer(sub) 445 sub = qubx.toolspace.SubLayer_Label(FITFMT%param_vals[i], 0, 0, x=8, y=i*LINE_EMS, w=7, h=LINE_EMS, color=self.cNumber, 446 action=self.__ref_expr(bind(self.__onClickValue, i)), scroll=self.__ref_expr(bind_with_args(self.__onScrollValue, i))) 447 self.sub_value.append(sub) 448 self.layParams.add_sublayer(sub) 449 sub = qubx.toolspace.SubLayer_Label('+/-', 0, 0, x=15, y=i*LINE_EMS, w=2, h=LINE_EMS, color=self.cText) 450 self.sub_pm.append(sub) 451 self.layParams.add_sublayer(sub) 452 sub = qubx.toolspace.SubLayer_Label('', 0, 0, x=17, y=i*LINE_EMS, w=7, h=LINE_EMS, color=self.cNumber) 453 self.sub_err.append(sub) 454 self.layParams.add_sublayer(sub) 455 sub = qubx.toolspace.SubLayer_Label('...', 0, 0, x=24, y=i*LINE_EMS, w=2, h=LINE_EMS, color=self.cButton, 456 action=self.__ref_expr(bind(self.__onClickMore, i))) 457 self.sub_more.append(sub) 458 self.layParams.add_sublayer(sub) 459 self.layParams.h = len(params) * LINE_EMS 460 self.profile['Name'].data = curve_name 461 self.profile['Eqn'].data = curve_expr 462 self.profile.remove(self.profile['Params']) 463 paramsNode = self.profile['Params'] 464 for i, name in enumerate(params): 465 paramNode = paramsNode.append(name) 466 paramNode.data = [param_vals[i], float(can_fit[i]), lo[i], hi[i]] 467 self.robot.request_stats()
468 - def __onParam(self, index, name, value, lo, hi, can_fit):
469 self.__can_fit[index] = can_fit 470 self.sub_check[index].set_active(can_fit) 471 self.__params[index] = name 472 self.sub_label[index].label = '%i: %s' % (index+1, name) 473 self.__vals[index] = value 474 self.sub_value[index].label = FITFMT%value 475 self.__lo[index] = lo 476 self.__hi[index] = hi 477 for paramNode in qubx.tree.children(self.profile['Params']): 478 if name == paramNode.name: 479 paramNode.data = [value, float(can_fit), lo, hi] 480 break 481 if not self.__fitting: 482 self.robot.request_stats()
483 - def __onMaxIter(self, max_iter):
484 self.__max_iter = max_iter 485 self.profile['MaxIter'].data = max_iter
486 - def __onToler(self, toler):
487 self.__toler = toler 488 self.profile['Toler'].data = toler
489 - def __onStrategy(self, script):
490 self.profile['Strategy'].data = script
491 - def __onFitter(self, fitter_name):
492 self.profile['Fitter'].data = fitter_name
493 - def __onStats(self, correlation, is_pseudo, std_err_est, ssr, r2, runs_prob):
494 self.__correlation = correlation 495 self.__is_pseudo = is_pseudo 496 self.__std_err_est = std_err_est[:] 497 self.__ssr = ssr 498 self.__r2 = r2 499 self.__runs_prob = runs_prob 500 self.subSSR.label = FITFMT%ssr 501 self.subR2.label = FITFMT%r2 502 self.subRunsProb.label = FITFMT%runs_prob 503 self.subCorrel.correlation = correlation 504 self.subCorrel.is_pseudo = is_pseudo 505 for i,err in enumerate(std_err_est): 506 self.sub_err[i].label = FITFMT%err 507 if self.__fit_receiver: 508 #print 'to fit receiver' 509 gobject.idle_add(self.__fit_receiver) 510 self.__fit_receiver = None
511 - def __onStartFit(self):
512 self.__fitting = True 513 self.space.add_layer(self.layFitting) 514 self.subFit.label = 'Stop'
515 - def __onIteration(self, param_vals, iteration):
516 self.subProgress.progress = iteration * 100.0 / self.__max_iter 517 self.__vals = param_vals[:] 518 for v, sub in izip(param_vals, self.sub_value): 519 sub.label = FITFMT%v
520 - def __onEndFit(self):
521 self.__fitting = False 522 self.space.remove_layer(self.layFitting) 523 self.subFit.label = 'Fit' 524 for paramNode in qubx.tree.children(self.profile['Params']): 525 if paramNode.name in self.__params: 526 ix = self.__params.index(paramNode.name) 527 paramNode.data = [self.__vals[ix], float(self.__can_fit[ix]), self.__lo[ix], self.__hi[ix]]
528 - def write_test(self, fObj):
529 def get_val(name): 530 name0 = name+'0' 531 pp = [i for i,p in enumerate(self.__params) if ((p == name) or (p == name0))] 532 if pp: 533 return self.__vals[pp[0]] 534 else: 535 return 1.0
536 if fObj.f: 537 return 'f(%s)' % ', '.join(('%s=%f'%(x, get_val(x)) for x in fObj.args)) 538 else: 539 return 'dy(%s)' % ', '.join(('%s=%f'%(x, get_val(x)) for x in (fObj.args + fObj.ynames)))
540 - def __expr_caption(self):
541 caption = 'Enter an expression in terms of %s:'%', '.join(['x']+self.__v_names) 542 if len(caption) > 80: 543 caption = caption[:caption[:80].rfind(' ')] + ', ...' 544 return caption
545 - def __onClickExpr(self, x, y, e):
546 if self.__fitting or self.__modal: return 547 dlg = qubx.pyenvGTK.HelpTestDialog(acceptODEorF(static=["x"], avail=self.__v_names, custom=True, locals=self.robot.locals.__dict__), 548 caption=self.__expr_caption(), 549 parent=None, 550 label="f(x) = ", 551 help_msg=FIT_SPACE_HELP, 552 bind=lambda val: qubx.pyenv.env.globals.__setitem__(val.f and 'f' or 'dy', val.f and val or val.dy), 553 write_test=self.write_test) 554 if gtk.RESPONSE_ACCEPT == dlg.run(self.__curve): 555 if self.global_name: 556 qubx.pyenv.env.scriptable_if_matching('%s.robot.set_expr(%s)' % (self.global_name, repr(dlg.expr)), 557 [(self.global_name, self)]) 558 self.robot.set_expr(dlg.expr) 559 dlg.destroy()
560 - def __onClickWeight(self, x, y, e):
561 if self.__fitting or self.__modal: return 562 dlg = qubx.GTK.NumEntryDialog('Edit the weight expression', None, 563 self.__expr_caption(), 564 self.__weight, str) 565 if gtk.RESPONSE_ACCEPT == dlg.run(): 566 if self.global_name: 567 qubx.pyenv.env.scriptable_if_matching('%s.robot.set_weight(%s)' % (self.global_name, repr(dlg.value)), 568 [(self.global_name, self)]) 569 self.robot.set_weight(dlg.value) 570 dlg.destroy()
571 - def __onToggle(self, i):
572 self.sub_check[i].set_active(self.__can_fit[i]) 573 if self.__fitting or self.__modal: return 574 if self.global_name: 575 qubx.pyenv.env.scriptable_if_matching('%s.robot.set_param(%s, can_fit=%s)' % 576 (self.global_name, repr(self.__params[i]), repr(not self.__can_fit[i])), 577 [(self.global_name, self)]) 578 self.robot.set_param(self.__params[i], can_fit=not self.__can_fit[i])
579 - def __onClickValue(self, i):
580 if self.__fitting or self.__modal: return 581 dlg = qubx.GTK.NumEntryDialog('Edit parameter value', None, '%s = ' % self.__params[i], 582 self.__vals[i], float) 583 if gtk.RESPONSE_ACCEPT == dlg.run(): 584 if self.global_name: 585 qubx.pyenv.env.scriptable_if_matching('%s.robot.set_param(%s, value=%s)' % 586 (self.global_name, repr(self.__params[i]), repr(dlg.value)), 587 [(self.global_name, self)]) 588 self.robot.set_param(self.__params[i], value=dlg.value) 589 dlg.destroy()
590 - def __onScrollValue(self, i, x, y, e, o):
591 if self.__fitting or self.__modal: return 592 if self.global_name: 593 self.defer_scriptable_scroll(self.__params[i], 594 '%s.robot.set_param(%s, value=scrolled_float(%s.param_vals[%i], %%s))' % 595 (self.global_name, repr(self.__params[i]), self.global_name, i), 596 o, e.state&gdk.CONTROL_MASK, e.state&gdk.SHIFT_MASK, 597 if_matching=[(self.global_name, self)]) 598 self.robot.set_param(self.__params[i], value=scrolled_float(self.__vals[i], o, e.state&gdk.CONTROL_MASK, e.state&gdk.SHIFT_MASK))
599 - def __onClickMore(self, i):
600 if self.__fitting: return 601 if self.__modal: 602 self.layMore.click_ok() # click_cancel() 603 gobject.idle_add(self.__onClickMore, i) 604 else: 605 self.__modal = True 606 self.layMore.run(self.space, self.__params[i], self.__vals[i], self.__lo[i], self.__hi[i], self.__std_err_est[i], self.__can_fit[i], 607 self.__onMoreOK, self.__onModalClose)
608 - def __onMoreOK(self, nm, value, lo, hi, can_fit):
609 if self.global_name: 610 qubx.pyenv.env.scriptable_if_matching('%s.robot.set_param(%s, value=%s, lo=%s, hi=%s, can_fit=%s)' % 611 (self.global_name, repr(nm), 612 repr(value), repr(lo), repr(hi), repr(can_fit)), 613 [(self.global_name, self)]) 614 self.robot.set_param(nm, value, lo, hi, can_fit)
615 - def __onModalClose(self):
616 self.__modal = False
617 - def __onClickCorrel(self, x, y, e):
618 if self.__fitting or self.__modal: return 619 self.__modal = True 620 self.layCorrel.run(self.space, self.subCorrel.correlation, self.subCorrel.is_pseudo, self.__onModalClose)
621 - def __onClickStrategy(self, x, y, e):
622 if self.__modal or self.__fitting: return 623 self.winStrategy.hide() 624 self.winStrategy.show()
625 - def __onClickFit(self, *args):
626 self.click_fit()
627 - def click_fit(self):
628 if self.__modal: return 629 if self.__fitting: 630 self.robot.interrupt() 631 return 632 if self.global_name: 633 qubx.pyenv.env.scriptable_if_matching('%s.fit()' % self.global_name, 634 [(self.global_name, self)]) 635 self.fit(wait=False)
636 - def fit(self, wait=True, receiver=None):
637 if wait: 638 qubx.pyenv.call_async_wait(lambda rcv: self.fit(wait=False, receiver=rcv)) 639 return 640 self.__fit_receiver = receiver 641 if self.OnClickFit: 642 self.OnClickFit() 643 else: 644 self.robot.fit()
645 - def nb_get_param_text(self):
646 if not (self.__std_err_est is None): 647 return "%s parameters:\n\t"%self.label + "\n\t".join(["%s:\t%.6g%s" % (name, val, (not active) and " (constant)" or (" +/- %.6g" % err)) for name, val, active, err in izip(self.__params, self.__vals, self.__can_fit, self.__std_err_est[:len(self.__params)])]) 648 else: 649 return "%s parameters:\n\t"%self.label + "\n\t".join(["%s:\t%.6g%s" % (name, val, (not active) and " (constant)" or "") for name, val, active in izip(self.__params, self.__vals, self.__can_fit)])
650 - def nb_get_shape(self):
651 if not len(self.__xx): 652 return 0, 0 653 return len(self.__xx), 3+len(self.__vvv)
654 - def nb_get_headers(self):
655 return ['X', 'Y', 'Fit'] + self.__v_names
656 - def nb_get_col(self, c):
657 if c == 0: 658 return self.__xx 659 elif c == 1: 660 return self.__yy 661 elif c == 2: 662 return self.__curve_obj.eval(self.__vals, self.__xx, self.__vvv) 663 else: 664 return self.__vvv[c-3]
665 - def nb_get_type(self):
666 return float
667 668
669 -class FitSpace(qubx.toolspace.ToolSpace):
670 """Mirrors and controls a L{qubx.fit_robot.FitRobot}. 671 672 @ivar controls: L{FitControls} 673 @ivar controlsHidden: minimal L{LayerSet} 674 @ivar draw_data: True (default) to draw data and fit curve 675 @ivar data_width: line width of data trace, in ems 676 @ivar data_rad: radius of data points, in ems 677 @ivar extra_width: line width of additional series 678 @ivar extra_rad: radius of additional series' data points 679 @ivar curve_width: line width of curve 680 @ivar caption: text across the top, when controls are hidden 681 @ivar global_name: how self might be called globally, for scripting 682 @ivar t2x: f(x) -> t 683 @ivar x2t: f(t) -> x 684 @ivar d2y: f(data value) -> y 685 @ivar y2d: f(y) -> data value 686 @ivar u2y: list of f(extra series value) -> y 687 @ivar y2u: list of f(y) -> extra series value 688 """ 689 690 __explore_featured = ['controls', 'controlsHidden', 'draw_data', 'data_width', 'data_rad', 'extra_width', 'extra_rad', 691 'curve_width', 'caption', 'global_name', 't2x', 'x2t', 'd2y', 'y2d', 'u2y', 'y2u', 692 'cBG', 'cGrid', 'cData', 'cExtra', 'cCurve', 'cFill', 'cGridZero', 'cGridLbl', 'hist_width', 693 'draw_hist', 'draw_fit_when_hidden', 'xzoom', 'yzoom', 't0', 't1', 'd0', 'd1', 694 'dispose', 'click_show_fit_controls', 'click_hide_fit_controls', 'update_coords_from_mouse', 695 'nb_get_series', 'nb_picture_get_shape', 'nb_picture_draw', 'set_bounds'] 696
697 - def __init__(self, label='Fit', 698 cBG=COLOR_FITSPACE_BG, cGrid=COLOR_FITSPACE_GRID, cData=COLOR_FITSPACE_DATA, cExtra=COLOR_FITSPACE_EXTRA, cCurve=COLOR_FITSPACE_CURVE, 699 cLayer=COLOR_FITSPACE_LAYER, cText=COLOR_FITSPACE_TEXT, cNumber=COLOR_FITSPACE_NUMBER, cButton=COLOR_FITSPACE_BUTTON, 700 cFill=COLOR_FITSPACE_FILL, cGridZero=COLOR_FITSPACE_GRIDZERO, cGridLbl=COLOR_FITSPACE_GRIDLBL, global_name=""):
701 """Creates a new view on a L{qubx.fit_robot.FitRobot}, creating the robot too if None provided. 702 703 @param label: string to identify the robot, if one is created 704 @param cBG: L{qubx.toolspace}-style colorref for the background 705 @param cGrid: colorref for grid lines 706 @param cData: colorref for data series 707 @param cExtra: colorref for additional series 708 @param cCurve: colorref for trend line 709 @param cLayer: colorref for floating panels 710 @param cText: colorref for static text in floating panels 711 @param cNumber: colorref for variable/editable text in floating panels 712 @param cButton: colorref for clickable text in floating panels 713 @param cFill: colorref for histogram bar interior 714 @param cGridZero: colorref for gridlines at zero 715 @param cGridLbl: colorref for grid labels 716 """ 717 qubx.toolspace.ToolSpace.__init__(self, can_focus=True) 718 self.set_size_request(100, 40) 719 self.controls = FitControls(label, cLayer, cText, cNumber, cButton, 720 global_name=(global_name and ("%s.controls" % global_name) or "")) 721 self.label = label 722 self.cBG = cBG 723 self.cGrid = cGrid 724 self.cData = cData 725 self.cExtra = cExtra 726 self.cCurve = cCurve 727 self.cFill = cFill 728 self.cGridZero = cGridZero 729 self.cGridLbl = cGridLbl 730 self.__global_name = global_name 731 self.hist_width = 0.95 732 self.__ref = Reffer() 733 self.OnDraw += self.__ref(self.__onDraw) 734 self.controls.robot.OnData += self.__ref(self.__onData) 735 self.controls.robot.OnExpr += self.__ref(self.__onExpr) 736 self.controls.robot.OnParam += self.__ref(self.__onParam) 737 self.controls.robot.OnIteration += self.__ref(self.__onIteration) 738 self.controls.robot.OnStatus += self.__ref(self.__onStatus) 739 self.controls.robot.OnStats += self.__ref(self.__onStats) 740 741 self.tool = self.cursor = FitSpaceCursor(self) 742 743 self.controlsHidden = qubx.toolspace.LayerSet() 744 self.layCaption = qubx.toolspace.Layer(x=0, y=0, w=-.1, h=LINE_EMS, cBG=cLayer) 745 self.subCaption = qubx.toolspace.SubLayer_Label("", 0, 0, x=0, y=0, w=-11, h=LINE_EMS, color=cText) 746 self.layCaption.add_sublayer(self.subCaption) 747 self.controlsHidden.layers.append(self.layCaption) 748 749 self.layShowFit = qubx.toolspace.Layer(x=1, y=-3-LINE_EMS, w=2, h=2, cBG=qubx.toolspace.COLOR_CLEAR) 750 self.subShowFit = SubLayer_FitIcon(action=self.__ref(self.__onClickShowFit)) 751 self.subShowFit.tooltip = 'Curve fitting...' 752 self.layShowFit.add_sublayer(self.subShowFit) 753 self.controlsHidden.layers.append(self.layShowFit) 754 755 self.layBottomHidden = qubx.toolspace.Layer(x=0, y=-LINE_EMS, w=-.1, h=LINE_EMS, cBG=cLayer) 756 self.subCoordsHidden = qubx.toolspace.SubLayer_Label('', -1, 0, x=0, y=0, w=-40, h=LINE_EMS, color=cText) 757 self.layBottomHidden.add_sublayer(self.subCoordsHidden) 758 self.subEditBounds = qubx.toolspace.SubLayer_Label('Edit Bounds...', 1, 0, x=-10, y=0, w=9, h=LINE_EMS, color=cButton, 759 action=self.__ref(self.__onClickEditBounds)) 760 self.layBottomHidden.add_sublayer(self.subEditBounds) 761 self.controlsHidden.layers.append(self.layBottomHidden) 762 763 self.subCoords = qubx.toolspace.SubLayer_Label('', -1, 0, x=0, y=0, w=-40, h=LINE_EMS, color=cText) 764 self.controls.layBottom.add_sublayer(self.subCoords) 765 766 self.layHideControls = qubx.toolspace.Layer(x=1, y=-3-LINE_EMS, w=2, h=2, cBG=qubx.toolspace.COLOR_CLEAR) 767 self.subHideControls = qubx.toolspace.SubLayer_Arrow(color=COLOR_FITSPACE_BACKBUTTON, angle=pi, w=2, h=2, 768 action=self.__ref(self.__onClickHideControls)) 769 self.subHideControls.tooltip = 'Hide fitting controls' 770 self.layHideControls.add_sublayer(self.subHideControls) 771 self.controls.layers.append(self.layHideControls) 772 773 self.layerset = self.controls 774 775 self.layZoom = qubx.toolspace.Layer(x=-2*LINE_EMS-0.5, y=2, w=2*LINE_EMS, h=LINE_EMS, cBG=qubx.toolspace.COLOR_WHITE) 776 self.add_layer(self.layZoom) 777 self.subZoomOut = qubx.toolspace.SubLayer_SmoothZoom('-', zoom_out=True, x=0, y=0, w=LINE_EMS, h=LINE_EMS) 778 self.subZoomOut.OnZoom += self.__ref(self.__onZoom) 779 self.layZoom.add_sublayer(self.subZoomOut) 780 self.subZoomIn = qubx.toolspace.SubLayer_SmoothZoom('+', x=LINE_EMS, y=0, w=LINE_EMS, h=LINE_EMS) 781 self.subZoomIn.OnZoom += self.__ref(self.__onZoom) 782 self.layZoom.add_sublayer(self.subZoomIn) 783 784 self.__xx = self.__yy = self.__vvv = self.__v_names = [] 785 self.__params = self.__vals = self.__lo = self.__hi = self.__can_fit = [] 786 787 self.__data_width = .3 788 self.__data_rad = .5 789 self.__extra_width = .2 790 self.__extra_rad = 0 791 self.__curve_width = .5 792 self.__draw_data = True 793 self.__draw_hist = False 794 self.__draw_fit_when_hidden = True 795 self.__caption = "" 796 797 self.__x0 = self.__t0 = self.__y0 = self.__d0 = 0 798 self.__x1 = self.__t1 = self.__y1 = self.__d1 = 1 799 self.__u0 = self.__u1 = [] 800 self.__xzoom = (0.0, 1.0) 801 self.__yzoom = (0.0, 1.0) 802 self.__t0z, self.__t1z, self.__d0z, self.__d1z = self.__x0, self.__x1, self.__d0, self.__d1 803 self.t2x = self.x2t = self.d2y = self.y2d = lambda x: x 804 self.u2y = self.y2u = []
805 806 data_width = property(lambda self: self.__data_width, lambda self, x: self.set_data_width(x)) 807 data_rad = property(lambda self: self.__data_rad, lambda self, x: self.set_data_rad(x)) 808 extra_width = property(lambda self: self.__extra_width, lambda self, x: self.set_extra_width(x)) 809 extra_rad = property(lambda self: self.__extra_rad, lambda self, x: self.set_extra_rad(x)) 810 curve_width = property(lambda self: self.__curve_width, lambda self, x: self.set_curve_width(x)) 811 caption = property(lambda self: self.__caption, lambda self, x: self.set_caption(x)) 812 draw_data = property(lambda self: self.__draw_data, lambda self, x: self.set_draw_data(x)) 813 draw_hist = property(lambda self: self.__draw_hist, lambda self, x: self.set_draw_hist(x)) 814 draw_fit_when_hidden = property(lambda self: self.__draw_fit_when_hidden, 815 lambda self, x: self.set_draw_fit_when_hidden(x)) 816 xzoom = property(lambda self: self.__xzoom, lambda self, x: self.set_xzoom(x)) 817 yzoom = property(lambda self: self.__yzoom, lambda self, x: self.set_yzoom(x)) 818 global_name = property(lambda self: self.__global_name, lambda self, x: self.set_global_name(x)) 819 t0 = property(lambda self: self.__t0) 820 t1 = property(lambda self: self.__t1) 821 d0 = property(lambda self: self.__d0) 822 d1 = property(lambda self: self.__d1) 823
824 - def set_data_width(self, x):
825 self.__data_width = x 826 self.redraw_canvas(True)
827 - def set_data_rad(self, x):
828 self.__data_rad = x 829 self.redraw_canvas(True)
830 - def set_extra_width(self, x):
831 self.__extra_width = x 832 self.redraw_canvas(True)
833 - def set_extra_rad(self, x):
834 self.__extra_rad = x 835 self.redraw_canvas(True)
836 - def set_curve_width(self, x):
837 self.__curve_width = x 838 self.redraw_canvas(True)
839 - def set_draw_data(self, x):
840 self.__draw_data = x 841 self.redraw_canvas(True)
842 - def set_draw_hist(self, x):
843 self.__draw_hist = x 844 self.redraw_canvas(True)
845 - def set_draw_fit_when_hidden(self, x):
846 self.__draw_fit_when_hidden = x 847 self.redraw_canvas(True)
848 - def set_xzoom(self, x):
849 self.__xzoom = x 850 self.redraw_canvas(True)
851 - def set_yzoom(self, x):
852 self.__yzoom = x 853 self.redraw_canvas(True)
854 - def set_caption(self, x):
855 self.__caption = x 856 self.subCaption.label = x
857 - def set_global_name(self, x):
858 """Override to update your NbItems global_names; remember to call FitSpace.set_global_name(x).""" 859 self.__global_name = x
860 - def dispose(self):
861 """If you didn't provide a robot to the constructor, call this when you're done to stop the robot thread.""" 862 self.controls.dispose() 863 self.__ref.clear()
864 - def __onClickEditBounds(self, x, y, e):
865 x0, x1 = apply_zoom_axis(self.t0, self.t1, *self.xzoom) 866 y0, y1 = apply_zoom_axis(self.d0, self.d1, *self.yzoom) 867 x0, y0, x1, y1 = PromptChartBounds(x0, y0, x1, y1, self.global_name, self.t0, self.d0, self.t1, self.d1) 868 if not (x0 is None): 869 self.set_bounds(x0, y0, x1, y1)
870 - def set_bounds(self, x0, y0, x1, y1):
871 w = self.t1 - self.t0 872 h = self.d1 - self.d0 873 self.xzoom = ((x0 - self.t0) / w, (x1 - self.t0) / w) 874 self.yzoom = ((y0 - self.d0) / h, (y1 - self.d0) / h)
875 - def __onClickShowFit(self, x, y, e):
877 - def click_show_fit_controls(self):
878 if self.global_name: 879 qubx.pyenv.env.scriptable_if_matching('%s.layerset = %s.controls' % (self.global_name, self.global_name), 880 [(self.global_name, self)]) 881 gobject.idle_add(self.set_layerset, self.controls)
882 - def __onClickHideControls(self, x, y, e):
884 - def click_hide_fit_controls(self):
885 if self.global_name: 886 qubx.pyenv.env.scriptable_if_matching('%s.layerset = %s.controlsHidden' % (self.global_name, self.global_name), 887 [(self.global_name, self)]) 888 gobject.idle_add(self.set_layerset, self.controlsHidden)
889 - def update_coords_from_mouse(self, x, y):
890 """Updates the lower-left display of data-coords under the mouse, given mouse-coords x and y.""" 891 if self.extra_rad or self.extra_width: 892 extras = lambda: ''.join([' %s: %10.6g' % (self.__v_names[i], self.y2u[i](y)) for i in xrange(1,len(self.__v_names))]) 893 else: 894 extras = lambda: '' 895 lbl = 'X: %10.6g Y: %10.6g%s' % (self.x2t(x), self.y2d(y), extras()) 896 self.subCoords.label = lbl 897 self.subCoordsHidden.label = lbl
898 - def __onZoom(self, sublayer, factor):
899 self.xzoom = zoom_axis(factor, *self.xzoom) 900 self.yzoom = zoom_axis(factor, *self.yzoom)
901 - def draw_extra_series(self, context, w, h, arr, d0=None, d1=None):
902 d0a = self.__d0 if (d0 is None) else d0 903 d1a = self.__d1 if (d1 is None) else d1 904 d0z, d1z = apply_zoom_axis(d0a, d1a, *self.__yzoom) 905 y2d, d2y = make_y_transform(d0z, d1z, self.__y0, self.__y1) 906 if self.extra_rad or self.extra_width: 907 context.set_source_rgba(*self.appearance.color(self.cExtra)) 908 context.set_line_width(self.extra_width * self.appearance.emsize * self.appearance.line_width) 909 draw_line(context, self.appearance, self.__xx, arr, self.t2x, d2y, self.extra_rad)
910 - def __onDraw(self, context, w, h):
911 self.__x0, self.__y0, self.__x1, self.__y1 = make_frame(w, h, self.appearance, not (self.layerset == self.controlsHidden)) 912 self.__t0z, self.__t1z = apply_zoom_axis(self.__t0, self.__t1, *self.__xzoom) 913 self.__d0z, self.__d1z = apply_zoom_axis(self.__d0, self.__d1, *self.__yzoom) 914 self.x2t, self.t2x = make_x_transform(self.__t0z, self.__t1z, self.__x0, self.__x1) 915 self.y2d, self.d2y = make_y_transform(self.__d0z, self.__d1z, self.__y0, self.__y1) 916 self.y2u = []; self.u2y = [] 917 for s0, s1 in izip(self.__u0, self.__u1): 918 s0z, s1z = apply_zoom_axis(s0, s1, *self.__yzoom) 919 y2s, s2y = make_y_transform(s0z, s1z, self.__y0, self.__y1) 920 self.y2u.append(y2s) 921 self.u2y.append(s2y) 922 923 context.set_source_rgba( *self.appearance.color(self.cBG) ) 924 context.rectangle(0, 0, w, h) 925 context.fill() 926 927 if not self.__draw_data: 928 return 929 930 ## series first # add extras with draw_extra_series, in OnDraw or OnOverlay 931 #if self.__vvv and (self.extra_rad or self.extra_width): 932 # context.set_source_rgba(*self.appearance.color(self.cExtra)) 933 # context.set_line_width(self.extra_width * self.appearance.emsize * self.appearance.line_width) 934 # for i, vv in izip(count(1), self.__vvv[1:]): 935 # draw_line(context, self.appearance, self.__xx, vv, self.t2x, self.u2y[i], self.extra_rad) 936 context.set_line_width(self.data_width * self.appearance.emsize * self.appearance.line_width) 937 if self.__draw_hist: 938 draw_hist(context, self.appearance, self.__xx, self.__yy, self.t2x, self.d2y, self.cData, self.cFill, self.hist_width) 939 else: 940 context.set_source_rgba(*self.appearance.color(self.cData)) 941 draw_line(context, self.appearance, self.__xx, self.__yy, self.t2x, self.d2y, self.data_rad) 942 943 # fit curve 944 if (self.layerset == self.controls) or self.__draw_fit_when_hidden: 945 if not self.controls.curve.can_resample: 946 xx = self.__xx 947 ff = self.controls.robot.curve.last_fit # can't run 2 ode threads at once 948 else: 949 try: 950 # eval simple curve at each pixel 951 x0 = max(0, int(floor(self.t2x(self.__t0)))) 952 x1 = min(w-1, int(floor(self.t2x(self.__t1)))) 953 xx = numpy.array([self.x2t(x) for x in xrange(x0, x1+1)]) 954 ff = self.controls.curve.eval(self.__vals, xx) 955 except: 956 # not going to interpolate extra series to get fake finer detail on curve 957 xx = self.__xx 958 ff = self.controls.curve.eval(self.__vals, self.__xx, self.__vvv) 959 context.set_source_rgba(*self.appearance.color(self.cCurve)) 960 context.set_line_width(self.curve_width * self.appearance.emsize * self.appearance.line_width) 961 draw_line(context, self.appearance, xx, ff, self.t2x, self.d2y, 0) 962 963 # grid 964 set_grid_color = lambda v: context.set_source_rgba( *self.appearance.color((abs(v)>1e-7) and self.cGrid or self.cGridZero) ) 965 context.set_line_width(self.appearance.line_width) 966 for t in gen_grid(self.x2t(0), self.x2t(w), 0, w, self.appearance): 967 set_grid_color(t) 968 x = self.t2x(t) 969 context.move_to(x, 0) 970 context.line_to(x, h) 971 context.stroke() 972 context.set_source_rgba(*self.appearance.color(self.cGridLbl)) 973 context.save() 974 context.translate(x-self.appearance.emsize/5, h-LINE_EMS*self.appearance.emsize) 975 context.scale(0.7, 0.7) 976 context.rotate(-pi/2) 977 context.move_to(0, 0) 978 context.show_text((abs(t)<=1e-7) and "0" or ('%.2g' % t)) 979 context.restore() 980 for d in gen_grid(self.y2d(h), self.y2d(0), h, 0, self.appearance): 981 set_grid_color(d) 982 y = self.d2y(d) 983 context.move_to(0, y) 984 context.line_to(w, y) 985 context.stroke() 986 context.set_source_rgba(*self.appearance.color(self.cGridLbl)) 987 context.save() 988 context.translate(self.appearance.emsize, y-self.appearance.emsize/5) 989 context.scale(0.7, 0.7) 990 context.move_to(0, 0) 991 context.show_text((abs(d)<=1e-7) and "0" or ('%.2g' % d)) 992 context.restore()
993 994
995 - def __onData(self, xx, yy, vvv, v_names):
996 self.__xx = xx 997 self.__yy = yy 998 self.__vvv = vvv 999 self.__v_names = v_names 1000 if len(xx): 1001 self.__t0 = min(xx) 1002 self.__t1 = max(xx) 1003 self.__d0 = min(yy) 1004 self.__d1 = max(yy) 1005 self.__u0 = [min(vv) for vv in vvv] 1006 self.__u1 = [max(vv) for vv in vvv] 1007 else: 1008 self.__t0 = 0.0 1009 self.__t1 = 1.0 1010 self.__d0 = 0.0 1011 self.__d1 = 1.0 1012 self.__u0 = [0.0] * len(v_names) 1013 self.__u1 = [1.0] * len(v_names) 1014 if self.draw_hist and (len(xx) > 1): 1015 self.__t0 -= (xx[1] - xx[0])/2 1016 self.__t1 += (xx[-1] - xx[-2])/2 1017 self.redraw_canvas(True)
1018 - def __onExpr(self, curve_name, curve_expr, params, param_vals, lo, hi, can_fit):
1019 self.__params = params[:] 1020 self.__vals = param_vals[:] 1021 self.__lo = lo[:] 1022 self.__hi = hi[:] 1023 self.__can_fit = can_fit[:] 1024 self.redraw_canvas(True)
1025 - def __onParam(self, index, name, value, lo, hi, can_fit):
1026 self.__can_fit[index] = can_fit 1027 self.__params[index] = name 1028 self.__vals[index] = value 1029 self.__lo[index] = lo 1030 self.__hi[index] = hi 1031 self.redraw_canvas(True)
1032 - def __onIteration(self, param_vals, iteration):
1033 self.__vals = param_vals[:] 1034 self.redraw_canvas(True)
1035 - def __onStatus(self, robot, msg):
1036 print msg
1037 - def __onStats(self, correlation, is_pseudo, std_err_est, ssr, r2, runs_prob):
1038 self.redraw_canvas(True)
1039 - def nb_get_series(self):
1040 return [qubx.notebook.NbChartSeries(0, 1, ser_type=self.draw_hist and qubx.notebook.HISTOGRAM or qubx.notebook.DOTS), 1041 qubx.notebook.NbChartSeries(0, 2, ser_type=qubx.notebook.LINES, color_rgb=self.appearance.color(self.cCurve)[:3])]
1042 - def nb_picture_get_shape(self):
1043 return self.dim
1044 - def nb_picture_draw(self, context, w, h):
1045 self.draw_to_context(context, w, h)
1046 1047
1048 -def draw_line(context, appearance, tt, uu, t2x, u2y, rad):
1049 if (len(tt) == 0) or (len(uu) == 0): return 1050 rad = rad * appearance.emsize; 1051 x = t2x(tt[0]) 1052 y = u2y(uu[0]) 1053 context.move_to(x, y) 1054 for t,u in izip(tt, uu): 1055 x = t2x(t) 1056 y = u2y(u) 1057 context.line_to(x, y) 1058 context.stroke() 1059 if rad: 1060 context.arc(x, y, rad, 0, 2*pi) 1061 context.fill() 1062 context.move_to(x, y)
1063
1064 -def draw_hist(context, appearance, tt, uu, t2x, u2y, color, fill_color, hist_width=0.8):
1065 N = min(len(tt), len(uu)) 1066 if N <= 1: 1067 return # how wide is a bin? 1068 y0 = u2y(0.0) 1069 for i in xrange(N): 1070 t, u = tt[i], uu[i] 1071 if i == 0: 1072 dt = tt[1] - t 1073 else: 1074 dt = t - tt[i-1] 1075 x_left = t2x(t - 0.5*dt*hist_width) 1076 x_right = t2x(t + 0.5*dt*hist_width) 1077 y1 = u2y(u) 1078 context.set_source_rgba(*appearance.color(fill_color)) 1079 context.rectangle(x_left, y0, x_right-x_left, y1-y0) 1080 context.fill_preserve() 1081 context.set_source_rgba(*appearance.color(color)) 1082 context.stroke()
1083 1084
1085 -class StrategyDialog(gtk.Window):
1086 - def __init__(self, label, robot):
1087 gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) 1088 self.__ref = Reffer() 1089 self.OnClickFit = WeakEvent() # () 1090 self.robot = robot 1091 self.robot.OnFitter += self.__ref(self.__onFitter) 1092 self.robot.OnMaxIter += self.__ref(self.__onMaxIter) 1093 self.robot.OnToler += self.__ref(self.__onToler) 1094 self.robot.OnStrategy += self.__ref(self.__onStrategy) 1095 self.robot.OnStartFit += self.__ref(self.__onStartFit) 1096 self.robot.OnEndFit += self.__ref(self.__onEndFit) 1097 self.set_title('%s - %s Strategy' % (qubx.pyenv.env.globals['QubX'].appname, label)) 1098 self.connect('delete-event', self.__onDelete) 1099 self.set_size_request(400, 300) 1100 self.vbox = gtk.VBox() 1101 self.vbox.show() 1102 self.add(self.vbox) 1103 1104 line = pack_item(gtk.HBox(), self.vbox) 1105 pack_label('Fitter:', line) 1106 self.mnuFitter = pack_item(qubx.GTK.DynamicComboBox(), line, expand=True) 1107 self.mnuFitter.OnPopulate += self.__ref(self.__onPopulateFitter) 1108 self.mnuFitter.OnChanged += self.__ref(self.__onChangeFitter) 1109 1110 line = pack_item(gtk.HBox(), self.vbox) 1111 pack_label('Max iterations:', line) 1112 self.txtMaxIter = pack_item(qubx.GTK.NumEntry(1, acceptIntGreaterThan(0), width_chars=5), line) 1113 self.txtMaxIter.OnChange += self.__ref(self.__onChangeMaxIter) 1114 pack_label('Toler:', line) 1115 self.txtToler = pack_item(qubx.GTK.NumEntry(1e-5, acceptFloatGreaterThan(0.0), width_chars=8), line) 1116 self.txtToler.OnChange += self.__ref(self.__onChangeToler) 1117 line = pack_item(gtk.HBox(), self.vbox) 1118 pack_label('Strategy:', line) 1119 self.btnFit = pack_button('Fit', line, at_end=True, on_click=self.__onClickFit) 1120 self.btnAdd = pack_button('Add a step', line, at_end=True, on_click=self.__onClickAdd) 1121 self.btnReset = pack_button('Reset', line, at_end=True, on_click=self.__onClickReset) 1122 1123 self.vsplit = pack_item(gtk.VPaned(), self.vbox, expand=True) 1124 self.txtStrategy = gtk.TextView() 1125 qubx.GTK.SetFixedWidth(self.txtStrategy) 1126 scroll = pack_scrolled(self.txtStrategy, None, size_request=(100, 70)) 1127 self.vsplit.pack1(scroll, True, True) 1128 self.txtOutput = qubx.pyenvGTK.PythonOut(self.robot.OnOutput) 1129 self.txtOutput.show() 1130 self.vsplit.pack2(self.txtOutput, True, True) 1131 1132 self.robot.request_fitparams() 1133 self.robot.request_strategy() 1134 self.robot.request_fitter_name()
1135
1136 - def screen_to_script(self):
1137 buf = self.txtStrategy.get_buffer() 1138 script = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) 1139 if self.robot.global_name: 1140 qubx.pyenv.env.scriptable_if_matching('%s.set_strategy(%s)' % 1141 (self.robot.global_name, repr(script)), 1142 [(self.robot.global_name, self.robot)]) 1143 self.robot.set_strategy(script)
1144 - def __onDelete(self, w, e):
1145 self.hide() 1146 self.screen_to_script() 1147 return True # no destroy
1148 - def __onFitter(self, fitter_name):
1149 self.mnuFitter.choose(fitter_name)
1150 - def __onMaxIter(self, maxiter):
1151 self.txtMaxIter.value = maxiter
1152 - def __onToler(self, toler):
1153 self.txtToler.value = toler
1154 - def __onStrategy(self, script):
1155 self.txtStrategy.get_buffer().set_text(script)
1156 - def __onStartFit(self):
1157 self.txtStrategy.set_editable(False) 1158 self.btnAdd.set_sensitive(False) 1159 self.btnFit.set_sensitive(False) 1160 self.mnuFitter.set_sensitive(False) 1161 self.txtMaxIter.set_sensitive(False) 1162 self.txtToler.set_sensitive(False)
1163 - def __onEndFit(self):
1164 self.txtStrategy.set_editable(True) 1165 self.btnAdd.set_sensitive(True) 1166 self.btnFit.set_sensitive(True) 1167 self.mnuFitter.set_sensitive(True) 1168 self.txtMaxIter.set_sensitive(True) 1169 self.txtToler.set_sensitive(True)
1170 - def __onPopulateFitter(self, add):
1171 for name in sorted(qubx.fit.Fitters.keys()): 1172 add(name)
1173 - def __onChangeFitter(self, mnu, name):
1174 if self.robot.global_name: 1175 qubx.pyenv.env.scriptable_if_matching('%s.set_fitter(qubx.fit.Fitters[%s])' % 1176 (self.robot.global_name, repr(name)), 1177 [(self.robot.global_name, self.robot)]) 1178 self.robot.set_fitter(qubx.fit.Fitters[name])
1179 - def __onChangeMaxIter(self, txt, val):
1180 if self.robot.global_name: 1181 qubx.pyenv.env.scriptable_if_matching('%s.set_max_iter(%s)' % 1182 (self.robot.global_name, repr(val)), 1183 [(self.robot.global_name, self.robot)]) 1184 self.robot.set_max_iter(val)
1185 - def __onChangeToler(self, txt, val):
1186 if self.robot.global_name: 1187 qubx.pyenv.env.scriptable_if_matching('%s.set_toler(%s)' % 1188 (self.robot.global_name, repr(val)), 1189 [(self.robot.global_name, self.robot)]) 1190 self.robot.set_toler(val)
1191 - def __onClickFit(self, btn):
1192 self.screen_to_script() 1193 self.OnClickFit()
1194 - def __onClickAdd(self, btn):
1195 self.screen_to_script() 1196 if self.robot.global_name: 1197 qubx.pyenv.env.scriptable_if_matching('%s.add_strategy_line()' % self.robot.global_name, 1198 [(self.robot.global_name, self.robot)]) 1199 self.robot.add_strategy_line()
1200 - def __onClickReset(self, btn):
1201 if self.robot.global_name: 1202 qubx.pyenv.env.scriptable_if_matching('%s.set_strategy("")' % self.robot.global_name, 1203 [(self.robot.global_name, self.robot)]) 1204 self.robot.set_strategy("")
1205 1206 1207 TheFitCopyDialog = qubx.GTK.CopyDialog('Copy fit curve image') # L{qubx.GTK.CopyDialog} 1208
1209 -class BaseFitSpaceCursor(qubx.toolspace.Tool):
1210 """Tracks mouse coordinates in a L{FitSpace}."""
1211 - def __init__(self, space):
1213 - def update_coords(self, x, y):
1214 try: 1215 if self.space.draw_data: 1216 self.space.update_coords_from_mouse(x, y) 1217 except: 1218 pass
1219 - def onPress(self, x, y, e):
1220 self.press_t, self.press_d = self.space.x2t(x), self.space.y2d(y) 1221 self.press_x2t, self.press_y2d = self.space.x2t, self.space.y2d 1222 self.press_xzoom, self.press_yzoom = self.space.xzoom, self.space.yzoom
1223 - def onRoll(self, x, y, e):
1224 self.update_coords(x, y)
1225 - def onDrag(self, x, y, e):
1226 self.update_coords(x, y) 1227 t, d = self.press_x2t(x), self.press_y2d(y) 1228 self.space.xzoom = pan_axis((self.press_t - t)/(self.space.t1 - self.space.t0), *self.press_xzoom) 1229 self.space.yzoom = pan_axis((self.press_d - d)/(self.space.d1 - self.space.d0), *self.press_yzoom)
1230 - def onScroll(self, x, y, e, amount):
1231 factor = (1.0 + ZOOM_SCROLL_FACTOR) * abs(amount) 1232 if amount < 0: 1233 factor = 1.0 / factor 1234 self.space.xzoom = zoom_axis_at(factor, (self.space.x2t(x)-self.space.t0)/(self.space.t1-self.space.t0), *self.space.xzoom) 1235 self.space.yzoom = zoom_axis_at(factor, (self.space.y2d(y)-self.space.d0)/(self.space.d1-self.space.d0), *self.space.yzoom)
1236 - def onActivate(self):
1237 self.space.redraw_canvas(True) # full re-layout of curve
1238 - def onKeyPress(self, e):
1239 if e.keyval in (ord('n'), ord('N')): 1240 if 'subNotebook' in self.space.__dict__: 1241 self.space.subNotebook.action(0, 0, Anon(state=e.state, time=e.time, button=0, x=0, y=0)) 1242 return
1243
1244 -class FitSpaceCursor(BaseFitSpaceCursor):
1245 - def __init__(self, space):
1246 BaseFitSpaceCursor.__init__(self, space) 1247 self.cursor = gdk.Cursor(gdk.FLEUR)
1248 - def onKeyPress(self, e):
1249 if self.space.layerset != self.space.controls: 1250 if e.keyval in (ord('f'), ord('F')): 1251 self.space.click_show_fit_controls() 1252 gobject.idle_add(self.space.grab_focus) 1253 return 1254 else: 1255 if e.keyval in (ord('f'), ord('F')): 1256 self.space.click_hide_fit_controls() 1257 gobject.idle_add(self.space.grab_focus) 1258 return 1259 if e.keyval in (keysyms.Return, keysyms.KP_Enter, keysyms.space): 1260 self.space.controls.click_fit() 1261 return 1262 kmap = [('Y', self.space.controls.subExpr), 1263 ('W', self.space.controls.subWeight), 1264 ('P', self.space.controls.subPresets), 1265 ('S', self.space.controls.subStrategy), 1266 ('C', self.space.controls.subCorrel)] 1267 kmap.extend([(str(i+1), sub) for i, sub in enumerate(self.space.controls.sub_more)]) 1268 for ltr, sub in kmap: 1269 if e.keyval in (ord(ltr), ord(ltr.lower())): 1270 sub.action(0, 0, Anon(state=e.state, time=e.time, button=0, x=0, y=0)) 1271 return 1272 # "dialog" also overrides Tool for keypresses: 1273 # enter/escape: ok/cancel 1274 # up/down: hilight (hover?) 1275 # space: "click" 1276 1277 BaseFitSpaceCursor.onKeyPress(self, e)
1278
1279 -class FitSpaceModalCursor(BaseFitSpaceCursor):
1280 - def __init__(self, space, layer, ok, cancel, clickable=[]):
1281 BaseFitSpaceCursor.__init__(self, space) 1282 self.layer = layer 1283 self.ok = ok 1284 self.cancel = cancel 1285 self.clickable = clickable
1286 - def onPress(self, x, y, e):
1287 # click in bg: cancel 1288 self.cancel()
1289 - def onDrag(self, x, y, e):
1290 self.update_coords(x, y)
1291 - def onKeyPress(self, e):
1292 if e.keyval in (keysyms.Return, keysyms.KP_Enter): 1293 self.ok() 1294 return 1295 if e.keyval == keysyms.Escape: 1296 self.cancel() 1297 return 1298 if e.keyval == keysyms.space: 1299 if self.layer.sub_hover: 1300 self.layer.sub_hover.action(0, 0, Anon(state=e.state, time=e.time, x=0, y=0, button=0)) 1301 return 1302 1303 if self.clickable: 1304 if e.keyval in (keysyms.Up, keysyms.Left): 1305 self.prev_clickable() 1306 return 1307 if e.keyval in (keysyms.Down, keysyms.Right): 1308 self.next_clickable() 1309 return 1310 if e.keyval == keysyms.Tab: 1311 if (e.state & gdk.SHIFT_MASK) or (e.state & gdk.CONTROL_MASK): 1312 self.prev_clickable() 1313 else: 1314 self.next_clickable() 1315 return
1316 - def prev_clickable(self):
1317 if self.layer.sub_hover in self.clickable: 1318 i = self.clickable.index(self.layer.sub_hover) 1319 else: 1320 i = None 1321 i = (len(self.clickable)-1) if (i in (None, 0)) else i-1 1322 self.layer.sub_hover = self.clickable[i]
1323 - def next_clickable(self):
1324 if self.layer.sub_hover in self.clickable: 1325 i = self.clickable.index(self.layer.sub_hover) 1326 else: 1327 i = None 1328 i = 0 if (i in (None, len(self.clickable)-1)) else i+1 1329 self.layer.sub_hover = self.clickable[i]
1330 1331
1332 -class SubLayer_Correl(qubx.toolspace.SubLayer):
1333 """Displays a square matrix that has all entries -1 <= x <= 1, as a square subdivided into squares of color; see L{ScaleToColor}. 1334 1335 @ivar correlation: a square matrix with all entries -1 <= x <= 1, such as the cross-correlation of fit parameters. 1336 @ivar is_pseudo: True if correlation could not be calculated strictly (covariance was not positive definite); ignored for now. 1337 """
1338 - def __init__(self, *args, **kw):
1339 qubx.toolspace.SubLayer.__init__(self, *args, **kw) 1340 self.__correlation = None 1341 self.is_pseudo = False
1342 - def set_correlation(self, x):
1343 self.__correlation = x 1344 self.invalidate()
1345 correlation = property(lambda self: self.__correlation, lambda self, x: self.set_correlation(x))
1346 - def draw(self, context, w, h, appearance):
1347 qubx.toolspace.SubLayer.draw(self, context, w, h, appearance) 1348 context.rectangle(0, 0, self.w, self.h) 1349 context.set_source_rgba(0,0,0,1) 1350 context.fill() 1351 if self.__correlation is None: return 1352 N = min(self.__correlation.shape) 1353 if N == 0: return 1354 dx = float(self.w) / N 1355 dy = float(self.h) / N 1356 for i in xrange(N): 1357 for j in xrange(N): 1358 context.set_source_rgb( *ScaleToColor(self.__correlation[i,j]) ) 1359 context.rectangle(i*dx, j*dy, dx, dy) 1360 context.fill()
1361 1362
1363 -class SubLayer_CorrelSquare(qubx.toolspace.SubLayer):
1364 """Displays one square from a matrix that has all entries -1 <= x <= 1, as a color and number; see L{ScaleToColor}."""
1365 - def __init__(self, value, cNumber=COLOR_CORREL_LBL, *args, **kw):
1366 qubx.toolspace.SubLayer.__init__(self, *args, **kw) 1367 self._label = '%.2f' % value 1368 self.color = ScaleToColor(value) 1369 self.cNumber = cNumber
1370 - def draw(self, context, w, h, appearance):
1371 qubx.toolspace.SubLayer.draw(self, context, w, h, appearance) 1372 context.rectangle(0, 0, self.w, self.h) 1373 context.set_source_rgba(*self.color) 1374 context.fill() 1375 1376 # half-size font: 1377 context.save() 1378 context.scale(0.5, 0.5) 1379 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 1380 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(self._label) 1381 vtotal = fascent + fdescent 1382 by = (2*self.h - vtotal)/2 + fascent 1383 bx = (2*self.w - width)/2 - xbearing 1384 context.set_source_rgba(*appearance.color(self.cNumber)) 1385 context.move_to(bx, by) 1386 context.show_text(self._label) 1387 context.restore()
1388 1389
1390 -class Layer_Correl(qubx.toolspace.Layer):
1391 """Displays a large-size, labeled cross-correlation matrix, modally."""
1392 - def __init__(self, cBG, cText, cNumber, cButton, *args, **kw):
1393 if not ('h' in kw): 1394 kw['h'] = 10*LINE_EMS 1395 kw['w'] = 14*LINE_EMS 1396 qubx.toolspace.Layer.__init__(self, cBG=cBG, *args, **kw) 1397 self.__ref = Reffer() 1398 self.subX = qubx.toolspace.SubLayer_Label('X', 0, 1, x=-LINE_EMS, y=0, w=LINE_EMS, h=LINE_EMS, color=cButton, 1399 action=self.__ref(self.__onClickOK)) 1400 self.add_sublayer(self.subX) 1401 self.subTitle = qubx.toolspace.SubLayer_Label('Cross-Correlation', 0, 1, x=1, y=0, w=-1, h=LINE_EMS, color=cText) 1402 self.add_sublayer(self.subTitle) 1403 self.subDisclaimer = qubx.toolspace.SubLayer_Label('', 0, 1, x=1, y=-2.5*LINE_EMS, w=-1, h=LINE_EMS, color=cText) 1404 self.add_sublayer(self.subDisclaimer) 1405 self.subOK = qubx.toolspace.SubLayer_Label('OK', 0, 1, x=-10, y=-1.5*LINE_EMS, w=9, h=1.5*LINE_EMS, color=cButton, 1406 action=self.__ref(self.__onClickOK)) 1407 self.add_sublayer(self.subOK) 1408 self.sub_cells = []
1409 - def __onClickOK(self, *args):
1410 self.space.tool = self.prev_tool 1411 self.space.remove_layer(self) 1412 gobject.idle_add(self.on_close)
1413 - def run(self, space, correlation, is_pseudo, on_close):
1414 if correlation is None: 1415 gobject.idle_add(on_close) 1416 return 1417 self.prev_tool = space.tool 1418 space.tool = FitSpaceModalCursor(space, self, self.__onClickOK, self.__onClickOK) 1419 self.subDisclaimer.label = (is_pseudo and 'covar not positive definite' or '') 1420 for sub in self.sub_cells: 1421 self.remove_sublayer(sub) 1422 self.sub_cells = [] 1423 space.add_layer(self) 1424 self.on_close = on_close 1425 gobject.idle_add(self.__layout, space, correlation) # wait for w,h to get pixel coords
1426 - def __layout(self, space, correlation):
1427 M, N = correlation.shape 1428 em = space.appearance.emsize 1429 px = py = min((self.w - 2*em), (self.h - 4*LINE_EMS*em)) 1430 x0, y0 = (self.w - px) / (2*em), (self.h - 2*LINE_EMS*em - py) / (2*em) 1431 dx, dy = px / (em*M), py / (em*N) 1432 for i in xrange(M): 1433 for j in xrange(N): 1434 sub = SubLayer_CorrelSquare(correlation[i,j], x=x0+i*dx, y=y0+j*dy, w=dx, h=dy) 1435 self.sub_cells.append(sub) 1436 self.add_sublayer(sub)
1437 1438 1439
1440 -class Layer_Param(qubx.toolspace.Layer):
1441 """Presents details about one parameter, as when the user clicks "..." in the upper-left param list; you run() it like a dialog."""
1442 - def __init__(self, cBG, cText, cNumber, cButton, *args, **kw):
1443 if not ('h' in kw): 1444 kw['h'] = 9*LINE_EMS 1445 if not ('w' in kw): 1446 kw['w'] = 14*LINE_EMS 1447 qubx.toolspace.Layer.__init__(self, cBG=cBG, *args, **kw) 1448 self.__ref = Reffer() 1449 for i,lbl in enumerate(['Name:', 'Value:', 'Low:', 'High:', '+/-']): 1450 sub = qubx.toolspace.SubLayer_Label(lbl, 1, 0, x=0, y=i*LINE_EMS, w=6, h=LINE_EMS, color=cText) 1451 self.add_sublayer(sub) 1452 self.subName = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=0, w=7, h=LINE_EMS, color=cNumber) 1453 self.add_sublayer(self.subName) 1454 self.subValue = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=LINE_EMS, w=7, h=LINE_EMS, color=cNumber, 1455 action=self.__ref(self.__onClickValue), scroll=self.__ref(self.__onScrollValue)) 1456 self.add_sublayer(self.subValue) 1457 self.subLo = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=2*LINE_EMS, w=7, h=LINE_EMS, color=cNumber, 1458 action=self.__ref(self.__onClickLow)) 1459 self.add_sublayer(self.subLo) 1460 self.subHi = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=3*LINE_EMS, w=7, h=LINE_EMS, color=cNumber, 1461 action=self.__ref(self.__onClickHigh)) 1462 self.add_sublayer(self.subHi) 1463 self.subErr = qubx.toolspace.SubLayer_Label('', -1, 0, x=7, y=4*LINE_EMS, w=7, h=LINE_EMS, color=cNumber) 1464 self.add_sublayer(self.subErr) 1465 1466 self.subChk = qubx.toolspace.SubLayer_Check(color=cButton, x=2, y=5*LINE_EMS, w=1.5, h=LINE_EMS) 1467 self.subChk.OnToggle += self.__ref(self.__onToggle) 1468 self.add_sublayer(self.subChk) 1469 self.subChkLbl = qubx.toolspace.SubLayer_Label('fit this parameter', -1, 0, x=4, y=5*LINE_EMS, w=-1, h=LINE_EMS, color=cText, 1470 action=self.__ref(self.__onToggle)) 1471 self.add_sublayer(self.subChkLbl) 1472 1473 self.subCancel = qubx.toolspace.SubLayer_Label('Cancel', 0, 1, x=-20, y=7*LINE_EMS, w=9, h=1.5*LINE_EMS, color=cButton, 1474 action=self.__ref(self.__onClickCancel)) 1475 self.add_sublayer(self.subCancel) 1476 self.subOK = qubx.toolspace.SubLayer_Label('OK', 0, 1, x=-10, y=7*LINE_EMS, w=9, h=1.5*LINE_EMS, color=cButton, 1477 action=self.__ref(self.__onClickOK)) 1478 self.add_sublayer(self.subOK) 1479 self.subX = qubx.toolspace.SubLayer_Label(' X ', 0, 1, x=-2, y=0, w=2, h=1.5*LINE_EMS, color=cButton, 1480 action=self.__ref(self.__onClickOK)) 1481 self.add_sublayer(self.subX)
1482
1483 - def __onClickValue(self, x, y, e):
1484 dlg = qubx.GTK.NumEntryDialog('Edit param value', None, '%s = '%self.nm, self.val, acceptFloatOrUnset) 1485 if gtk.RESPONSE_ACCEPT == dlg.run(): 1486 self.val = dlg.value 1487 self.subValue.label = FITFMT%self.val 1488 dlg.destroy()
1489 - def __onScrollValue(self, x, y, e, o):
1490 self.val = scrolled_float(self.val, o, e.state&gdk.CONTROL_MASK, e.state&gdk.SHIFT_MASK) 1491 self.subValue.label = FITFMT%self.val
1492 - def __onClickLow(self, x, y, e):
1493 low = '' if (self.lo == UNSET_VALUE) else self.lo 1494 dlg = qubx.GTK.NumEntryDialog('Edit param lower bound', None, 'Low: ', low, acceptFloatOrUnset) 1495 if gtk.RESPONSE_ACCEPT == dlg.run(): 1496 self.lo = dlg.value 1497 self.subLo.label = (self.lo != UNSET_VALUE) and (FITFMT%self.lo) or '' 1498 dlg.destroy()
1499 - def __onClickHigh(self, x, y, e):
1500 high = '' if (self.hi == UNSET_VALUE) else self.hi 1501 dlg = qubx.GTK.NumEntryDialog('Edit param upper bound', None, 'High: ', high, acceptFloatOrUnset) 1502 if gtk.RESPONSE_ACCEPT == dlg.run(): 1503 self.hi = dlg.value 1504 self.subHi.label = (self.hi != UNSET_VALUE) and (FITFMT%self.hi) or '' 1505 dlg.destroy()
1506 - def __onToggle(self, *args):
1507 self.can_fit = not self.can_fit 1508 self.subChk.set_active(self.can_fit)
1509 - def __onClickCancel(self, x, y, e):
1510 self.click_cancel()
1511 - def click_cancel(self):
1512 self.space.tool = self.prev_tool 1513 self.space.remove_layer(self) 1514 gobject.idle_add(self.on_close)
1515 - def __onClickOK(self, x, y, e):
1516 self.click_ok()
1517 - def click_ok(self):
1518 if self.space: 1519 self.space.tool = self.prev_tool 1520 self.space.remove_layer(self) 1521 gobject.idle_add(self.on_ok, self.nm, self.val, self.lo, self.hi, self.can_fit) 1522 gobject.idle_add(self.on_close)
1523 - def run(self, space, nm, val, lo, hi, err, can_fit, on_ok, on_close):
1524 """Displays this layer on the space; fills the blanks with (nm, val, lo, hi, err, can_fit); returns immediately. 1525 1526 @param on_ok: when the user clicks OK, this layer is hidden, then it calls on_ok(nm, val, lo, hi, can_fit) 1527 @param on_close: when the user clicks Cancel, or after on_ok, it calls on_close() 1528 """ 1529 self.nm = nm 1530 self.val = val 1531 self.lo = lo 1532 self.hi = hi 1533 self.can_fit = can_fit 1534 self.on_ok = on_ok 1535 self.on_close = on_close 1536 1537 self.prev_tool = space.tool 1538 space.tool = FitSpaceModalCursor(space, self, self.click_ok, self.click_cancel, 1539 [self.subValue, self.subLo, self.subHi, self.subChk, self.subCancel, self.subOK]) 1540 1541 self.subName.label = nm 1542 self.subValue.label = FITFMT%val 1543 self.subLo.label = (lo != UNSET_VALUE) and (FITFMT%lo) or '' 1544 self.subHi.label = (hi != UNSET_VALUE) and (FITFMT%hi) or '' 1545 self.subErr.label = FITFMT%err 1546 self.subChk.set_active(can_fit) 1547 1548 space.add_layer(self)
1549 1550 1551
1552 -def draw_fit_icon(cr, w, h):
1553 w = int(w) 1554 h = int(h) 1555 margin = min(w, h)/6 1556 cr.save() 1557 cr.set_line_width(4.0) 1558 cr.set_source_rgba(.5,.5,.5,.5) 1559 cr.rectangle(margin, margin, w-2*margin, h-2*margin) 1560 cr.stroke_preserve() 1561 cr.set_source_rgba(0,0,0,.8) 1562 cr.fill() 1563 margin += 2 1564 cr.translate(margin, margin) 1565 ptrad = max(.25, (min(w,h) - 2*margin)/10.0) 1566 dx = max(1e-6, 1.0 / (w - 2*margin)) 1567 dy = max(1e-6, 1.0 / (h - 2*margin)) 1568 f = lambda x: h*(-.5*(2*x-1)**2 + .5) 1569 cr.set_source_rgba(1, 1, 1, .6) 1570 for i in xrange(w/2): 1571 x = random.uniform() 1572 cr.arc(x/dx, ((h-2*margin) - f(x) + ptrad*random.normal()), ptrad, 0, 2*pi) 1573 cr.fill() 1574 cr.set_source_rgba(1, 0, 0, .5) 1575 cr.set_line_width(2.0*ptrad) 1576 cr.move_to(0, (h-2*margin)-f(0)) 1577 for i in xrange(1, w-2*margin): 1578 cr.line_to(i, (h-2*margin)-f(i*dx)) 1579 cr.stroke() 1580 cr.restore()
1581
1582 -class SubLayer_FitIcon(qubx.toolspace.SubLayer_Icon):
1583 - def draw(self, context, w, h, appearance):
1584 qubx.toolspace.SubLayer_Icon.draw(self, context, w, h, appearance) 1585 draw_fit_icon(context, self.w, self.h)
1586 1587 1588 1589 MAX_SERIES_NAME_LEN = 32 1590
1591 -def SafeName(nm):
1592 """Returns nm, modified to be a legal python identifier.""" 1593 alnum = re.sub(r"[^a-zA-Z0-9_]", '_', nm) 1594 safe = re.sub(r"(^[0-9])", r"_\1", alnum) 1595 if safe == 'x': safe = '_x_' 1596 return safe[:MAX_SERIES_NAME_LEN]
1597
1598 -def read_data(f):
1599 """Returns (series, names) read from file-like object f. 1600 The file should be tab- or comma-separated text. The first line can have column labels. 1601 Returns C{series}, a list of numpy.array(dtype='float32'), one per column, 1602 and C{names}, a list of str identifiers. Blank names are replaced with Y%i. If there's 1603 only one column, an ordinal series 'X' is prepended. 1604 """ 1605 names = [] 1606 series = [] 1607 N = 0 1608 splitter = re.compile(r"(?:, ?)|(?:\t)") 1609 for line in f: 1610 toks = splitter.split(line.strip(' \r\n')) 1611 # strip bogus initial, final empty token 1612 while toks and toks[0] == '': 1613 toks = toks[1:] 1614 while toks and toks[-1] == '': 1615 toks = toks[:-1] 1616 # replace blanks with 0.0 1617 toks = [x or 0.0 for x in toks] 1618 if not toks: continue 1619 for i in xrange(len(series), len(toks)): 1620 series.append([0.0]*N) 1621 names.append('') 1622 try: 1623 nums = map(float, toks) 1624 for i, num in enumerate(nums): 1625 series[i].append(num) 1626 for i in xrange(len(toks), len(series)): 1627 series[i].append(0.0) 1628 N += 1 1629 except ValueError, e: 1630 for i, tok in enumerate(toks): 1631 if (not names[i]) and tok: 1632 names[i] = SafeName(tok) 1633 1634 if len(series) == 1: 1635 series.insert(0, [float(x) for x in xrange(N)]) 1636 names.insert(0, 'X') 1637 elif not names[0]: 1638 names[0] = 'X' 1639 for i in xrange(len(series)): 1640 if not names[i]: 1641 names[i] = 'Y%d' % i 1642 series = [numpy.array(ser, dtype='float32') for ser in series] 1643 return series, names
1644
1645 -def pick_xy(series, names, x_name, y_name):
1646 """Given (series, names) from L{read_data}, and preferred x- and y- series names, returns the most appropriate 2 series. 1647 1648 @return: (x_series, y_series, index of x_series, index of y_series) 1649 """ 1650 if x_name and (x_name in series): 1651 xi = names.index(x_name) 1652 else: 1653 xi = 0 1654 if y_name and (y_name in series): 1655 yi = names.index(y_name) 1656 else: 1657 yi = 1 1658 return series[xi], series[yi], xi, yi
1659
1660 -def main():
1661 usage = "usage: %prog [options] file.txt" 1662 parser = optparse.OptionParser(usage) 1663 parser.add_option('-e', '--expr', dest='expr', default='m*x + b', help='fit curve expression') 1664 parser.add_option('-x', '--x-name', dest='x_name', default='', help='x column header or index') 1665 parser.add_option('-y', '--y-name', dest='y_name', default='', help='y column header or index') 1666 options, args = parser.parse_args() 1667 expr = options.expr 1668 x_name = options.x_name 1669 y_name = options.y_name 1670 1671 if len(args) > 1: 1672 print 'usage: %prog [options] {file.txt or -} (- reads stdin until eof)' 1673 sys.exit(1) 1674 if (len(args) == 0) or (args[0] == '-'): 1675 series, names = read_data(sys.stdin) 1676 else: 1677 series, names = read_data(open(args[0], 'r')) 1678 1679 if (len(series) == 0) or (len(series[0]) == 0): 1680 print 'no data' 1681 sys.exit(1) 1682 xx, yy, xi, yi = pick_xy(series, names, x_name, y_name) 1683 yname = names[yi] 1684 for i in reversed(sorted([xi, yi])): 1685 del series[i] 1686 del names[i] 1687 series = [yy] + series 1688 names = [yname] + names 1689 1690 gobject.threads_init() 1691 win = gtk.Window(gtk.WINDOW_TOPLEVEL) 1692 win.set_title('qubx.fit_space %s' % os.path.split(args[0])[1]) 1693 win.connect('destroy', lambda w: gtk.main_quit()) 1694 1695 fitspace = FitSpace() 1696 win.add(fitspace) 1697 fitspace.show() 1698 win.show() 1699 #gobject.idle_add(win.resize, 320, 240) 1700 1701 qubx.toolspace.Appearance.set_font_size(13) 1702 1703 fitspace.robot.set_data(xx, yy, series, names) 1704 fitspace.robot.set_expr(expr) 1705 1706 try: 1707 gtk.main() 1708 except KeyboardInterrupt: 1709 pass 1710 except: 1711 traceback.print_exc() 1712 fitspace.robot.stop()
1713 1714 1715 FIT_SPACE_HELP = """You can enter either a function, such as 1716 amp * exp(-x / tau) 1717 or a system of differential equations, such as 1718 y1' = a*y2; y2' = b*y1 1719 The first integrated variable (e.g. y1) will be used for fitting. 1720 1721 You can use the minimum/maximum values of any variable, e.g. 1722 hi("y") - lo("y") 1723 x - lo("x") 1724 hi("OtherSeriesByName") 1725 1726 Expressions use Python syntax, except that the single quote (') is reserved for differential equations. 1727 Full documentation is available at www.python.org. 1728 1729 Parameter names must begin with a letter, and contain only letters, numbers, and _ (underscore). They are case sensitive. 1730 1731 Construct piecewise functions using "and" and "or" 1732 ((x<0.0) and (-1e21)) or ((x==0) and -1e20) or log(x) 1733 """ + qubx.pyenv.PYTHON_HELP 1734 1735
1736 -def PromptChartBounds(x0, y0, x1, y1, title=None, t0=None, d0=None, t1=None, d1=None):
1737 dlg = gtk.Dialog('Edit chart bounds%s' % (title and (" (%s)"%title) or ""), qubx.GTK.get_active_window(), gtk.DIALOG_MODAL) 1738 line = pack_item(gtk.HBox(True), dlg.vbox) 1739 pack_label('x0:', line) 1740 txtX0 = pack_item(qubx.GTK.NumEntry(x0, format='%.5g'), line) 1741 line = pack_item(gtk.HBox(True), dlg.vbox) 1742 pack_label('x1:', line) 1743 txtX1 = pack_item(qubx.GTK.NumEntry(x1, format='%.5g'), line) 1744 line = pack_item(gtk.HBox(True), dlg.vbox) 1745 pack_label('y0:', line) 1746 txtY0 = pack_item(qubx.GTK.NumEntry(y0, format='%.5g'), line) 1747 line = pack_item(gtk.HBox(True), dlg.vbox) 1748 pack_label('y1:', line) 1749 txtY1 = pack_item(qubx.GTK.NumEntry(y1, format='%.5g'), line) 1750 def on_click_reset(btn): 1751 if d1 is None: 1752 txtX0.value, txtX1.value = x0, x1 1753 txtY0.value, txtY1.value = y0, y1 1754 else: 1755 txtX0.value, txtX1.value = t0, t1 1756 txtY0.value, txtY1.value = d0, d1
1757 pack_button('Reset', dlg.action_area, on_click_reset) 1758 pack_button('Cancel', dlg.action_area, bind(dlg.response, gtk.RESPONSE_REJECT)) 1759 pack_button('OK', dlg.action_area, bind(dlg.response, gtk.RESPONSE_ACCEPT)) 1760 response = dlg.run() 1761 x0, x1, y0, y1 = txtX0.value, txtX1.value, txtY0.value, txtY1.value 1762 dlg.destroy() 1763 if response == gtk.RESPONSE_ACCEPT: 1764 return x0, y0, x1, y1 1765 else: 1766 return None, None, None, None 1767 1768 if __name__ == '__main__': 1769 main() 1770 1771 1772 # qubx.settings: window size and position of strategy dialog(s)? 1773 # some kind of exit event to trigger strategydialog.screen_to_script? 1774