Coverage for larch/wxxas/prepeak_panel.py: 0%

1053 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-10-16 21:04 +0000

1import time 

2import sys 

3from pathlib import Path 

4import numpy as np 

5np.seterr(all='ignore') 

6 

7from functools import partial 

8import json 

9 

10import wx 

11import wx.lib.scrolledpanel as scrolled 

12 

13import wx.dataview as dv 

14 

15from lmfit import Parameter 

16import lmfit.models as lm_models 

17 

18from larch import Group, site_config 

19from larch.utils import uname, gformat, mkdir, fix_varname 

20from larch.math import index_of 

21from larch.io.export_modelresult import export_modelresult 

22from larch.io import save_groups, read_groups 

23 

24from larch.wxlib import (ReportFrame, BitmapButton, FloatCtrl, FloatSpin, 

25 SetTip, GridPanel, get_icon, SimpleText, pack, 

26 Button, HLine, Choice, Check, MenuItem, COLORS, 

27 set_color, CEN, RIGHT, LEFT, FRAMESTYLE, Font, 

28 FONTSIZE, FONTSIZE_FW, FileSave, FileOpen, 

29 flatnotebook, Popup, EditableListBox, ExceptionPopup) 

30 

31from larch.wxlib.parameter import ParameterWidgets 

32from larch.wxlib.plotter import last_cursor_pos 

33from .taskpanel import TaskPanel 

34from .config import PrePeak_ArrayChoices, PlotWindowChoices 

35 

36DVSTYLE = dv.DV_SINGLE|dv.DV_VERT_RULES|dv.DV_ROW_LINES 

37 

38ModelChoices = {'other': ('<General Models>', 'Constant', 'Linear', 

39 'Quadratic', 'Exponential', 'PowerLaw', 

40 'Linear Step', 'Arctan Step', 

41 'ErrorFunction Step', 'Logisic Step', 'Rectangle'), 

42 'peaks': ('<Peak Models>', 'Gaussian', 'Lorentzian', 

43 'Voigt', 'PseudoVoigt', 'DampedHarmonicOscillator', 

44 'Pearson7', 'StudentsT', 'SkewedGaussian', 

45 'Moffat', 'BreitWigner', 'Doniach', 'Lognormal'), 

46 } 

47 

48 

49# map of lmfit function name to Model Class 

50ModelFuncs = {'constant': 'ConstantModel', 

51 'linear': 'LinearModel', 

52 'quadratic': 'QuadraticModel', 

53 'polynomial': 'PolynomialModel', 

54 'gaussian': 'GaussianModel', 

55 'lorentzian': 'LorentzianModel', 

56 'voigt': 'VoigtModel', 

57 'pvoigt': 'PseudoVoigtModel', 

58 'moffat': 'MoffatModel', 

59 'pearson7': 'Pearson7Model', 

60 'students_t': 'StudentsTModel', 

61 'breit_wigner': 'BreitWignerModel', 

62 'lognormal': 'LognormalModel', 

63 'damped_oscillator': 'DampedOscillatorModel', 

64 'dho': 'DampedHarmonicOscillatorModel', 

65 'expgaussian': 'ExponentialGaussianModel', 

66 'skewed_gaussian': 'SkewedGaussianModel', 

67 'doniach': 'DoniachModel', 

68 'powerlaw': 'PowerLawModel', 

69 'exponential': 'ExponentialModel', 

70 'step': 'StepModel', 

71 'rectangle': 'RectangleModel'} 

72 

73ModelAbbrevs = {'Constant': 'const', 

74 'Linear': 'line', 

75 'Quadratic': 'quad', 

76 'Exponential': 'exp', 

77 'PowerLaw': 'pow', 

78 'Linear Step': 'line_step', 

79 'Arctan Step': 'atan_step', 

80 'ErrorFunction Step': 'erf_step', 

81 'Logistic Step': 'logi_step', 

82 'Rectangle': 'rect', 

83 'Gaussian': 'gauss', 

84 'Lorentzian': 'loren', 

85 'Voigt': 'voigt', 

86 'PseudoVoigt': 'pvoigt', 

87 'DampedHarmonicOscillator': 'dho', 

88 'Pearson7': 'pear7', 

89 'StudentsT': 'studt', 

90 'SkewedGaussian': 'sgauss', 

91 'Moffat': 'moffat', 

92 'BreitWigner': 'breit', 

93 'Doniach': 'doniach', 

94 'Lognormal': 'lognorm'} 

95 

96BaselineFuncs = ['No Baseline', 

97 'Constant+Lorentzian', 

98 'Linear+Lorentzian', 

99 'Constant+Gaussian', 

100 'Linear+Gaussian', 

101 'Constant+Voigt', 

102 'Linear+Voigt', 

103 'Quadratic', 'Linear'] 

104 

105 

106PLOT_BASELINE = 'Data+Baseline' 

107PLOT_FIT = 'Data+Fit' 

108PLOT_INIT = 'Data+Init Fit' 

109PLOT_RESID = 'Data+Residual' 

110PlotChoices = [PLOT_BASELINE, PLOT_FIT, PLOT_RESID] 

111 

112FitMethods = ("Levenberg-Marquardt", "Nelder-Mead", "Powell") 

113ModelWcards = "Fit Models(*.modl)|*.modl|All files (*.*)|*.*" 

114DataWcards = "Data Files(*.dat)|*.dat|All files (*.*)|*.*" 

115 

116 

117MIN_CORREL = 0.10 

118 

119COMMANDS = {} 

120COMMANDS['prepfit'] = """# prepare fit 

121{group}.prepeaks.user_options = {user_opts:s} 

122{group}.prepeaks.init_fit = peakmodel.eval(peakpars, x={group}.prepeaks.energy) 

123{group}.prepeaks.init_ycomps = peakmodel.eval_components(params=peakpars, x={group}.prepeaks.energy) 

124if not hasattr({group}.prepeaks, 'fit_history'): {group}.prepeaks.fit_history = [] 

125""" 

126 

127COMMANDS['prepeaks_setup'] = """# setup prepeaks 

128if not hasattr({group}, 'energy'): {group:s}.energy = 1.0*{group:s}.xplot 

129{group:s}.xplot = 1.0*{group:s}.energy 

130{group:s}.yplot = 1.0*{group:s}.{array_name:s} 

131prepeaks_setup(energy={group:s}, arrayname='{array_name:s}', elo={elo:.3f}, ehi={ehi:.3f}, 

132 emin={emin:.3f}, emax={emax:.3f}) 

133""" 

134 

135COMMANDS['set_yerr_const'] = "{group}.prepeaks.norm_std = {group}.yerr*ones(len({group}.prepeaks.norm))" 

136COMMANDS['set_yerr_array'] = """ 

137{group}.prepeaks.norm_std = 1.0*{group}.yerr[{imin:d}:{imax:d}] 

138yerr_min = 1.e-9*{group}.prepeaks.yplot.mean() 

139{group}.prepeaks.norm_std[where({group}.yerr < yerr_min)] = yerr_min 

140""" 

141 

142COMMANDS['dofit'] = """# do fit 

143peakresult = prepeaks_fit({group}, peakmodel, peakpars) 

144peakresult.user_options = {user_opts:s} 

145""" 

146 

147 

148def get_model_abbrev(modelname): 

149 if modelname in ModelAbbrevs: 

150 return ModelAbbrevs[modelname] 

151 return fix_varname(modelname).lower() 

152 

153def get_xlims(x, xmin, xmax): 

154 xeps = min(np.diff(x))/ 5. 

155 i1 = index_of(x, xmin + xeps) 

156 i2 = index_of(x, xmax + xeps) + 1 

157 return i1, i2 

158 

159class PrePeakFitResultFrame(wx.Frame): 

160 config_sect = 'prepeak' 

161 def __init__(self, parent=None, peakframe=None, datagroup=None, **kws): 

162 wx.Frame.__init__(self, None, -1, title='Pre-edge Peak Fit Results', 

163 style=FRAMESTYLE, size=(950, 700), **kws) 

164 self.peakframe = peakframe 

165 

166 if datagroup is not None: 

167 self.datagroup = datagroup 

168 # prepeaks = getattr(datagroup, 'prepeaks', None) 

169 # self.peakfit_history = getattr(prepeaks, 'fit_history', []) 

170 self.parent = parent 

171 self.datasets = {} 

172 self.form = {} 

173 self.larch_eval = self.peakframe.larch_eval 

174 self.nfit = 0 

175 self.createMenus() 

176 self.build() 

177 

178 if datagroup is None: 

179 symtab = self.peakframe.larch.symtable 

180 xasgroups = getattr(symtab, '_xasgroups', None) 

181 if xasgroups is not None: 

182 for dname, dgroup in xasgroups.items(): 

183 dgroup = getattr(symtab, dgroup, None) 

184 ppeak = getattr(dgroup, 'prepeaks', None) 

185 hist = getattr(ppeak, 'fit_history', None) 

186 if hist is not None: 

187 self.add_results(dgroup, show=True) 

188 

189 

190 def createMenus(self): 

191 self.menubar = wx.MenuBar() 

192 fmenu = wx.Menu() 

193 m = {} 

194 MenuItem(self, fmenu, "Save Model for Current Group", 

195 "Save Model and Result to be loaded later", 

196 self.onSaveFitResult) 

197 

198 MenuItem(self, fmenu, "Save Fit and Components for Current Fit", 

199 "Save Arrays and Results to Text File", 

200 self.onExportFitResult) 

201 

202 fmenu.AppendSeparator() 

203 MenuItem(self, fmenu, "Save Parameters and Statistics for All Fitted Groups", 

204 "Save CSV File of Parameters and Statistics for All Fitted Groups", 

205 self.onSaveAllStats) 

206 self.menubar.Append(fmenu, "&File") 

207 self.SetMenuBar(self.menubar) 

208 

209 def build(self): 

210 sizer = wx.GridBagSizer(3, 3) 

211 sizer.SetVGap(3) 

212 sizer.SetHGap(3) 

213 

214 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) 

215 splitter.SetMinimumPaneSize(200) 

216 

217 self.filelist = EditableListBox(splitter, self.ShowDataSet, 

218 size=(250, -1)) 

219 set_color(self.filelist, 'list_fg', bg='list_bg') 

220 

221 panel = scrolled.ScrolledPanel(splitter) 

222 

223 panel.SetMinSize((775, 575)) 

224 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.NORMAL) 

225 

226 # title row 

227 self.wids = wids = {} 

228 title = SimpleText(panel, 'Fit Results', font=Font(FONTSIZE+2), 

229 colour=COLORS['title'], style=LEFT) 

230 

231 wids['data_title'] = SimpleText(panel, '< > ', font=Font(FONTSIZE+2), 

232 minsize=(350, -1), 

233 colour=COLORS['title'], style=LEFT) 

234 

235 opts = dict(default=False, size=(200, -1), action=self.onPlot) 

236 ppanel = wx.Panel(panel) 

237 wids['plot_bline'] = Check(ppanel, label='Plot baseline-subtracted?', **opts) 

238 wids['plot_resid'] = Check(ppanel, label='Plot with residual?', **opts) 

239 wids['plot_win'] = Choice(ppanel, size=(60, -1), choices=PlotWindowChoices, 

240 action=self.onPlot) 

241 wids['plot_win'].SetStringSelection('1') 

242 

243 psizer = wx.BoxSizer(wx.HORIZONTAL) 

244 psizer.Add( wids['plot_bline'], 0, 5) 

245 psizer.Add( wids['plot_resid'], 0, 5) 

246 psizer.Add(SimpleText(ppanel, 'Plot Window:'), 0, 5) 

247 psizer.Add( wids['plot_win'], 0, 5) 

248 

249 pack(ppanel, psizer) 

250 

251 wids['load_model'] = Button(panel, 'Load this Model for Fitting', 

252 size=(250, -1), action=self.onLoadModel) 

253 

254 wids['plot_choice'] = Button(panel, 'Plot This Fit', 

255 size=(125, -1), action=self.onPlot) 

256 

257 wids['fit_label'] = wx.TextCtrl(panel, -1, ' ', size=(175, -1)) 

258 wids['set_label'] = Button(panel, 'Update Label', size=(150, -1), 

259 action=self.onUpdateLabel) 

260 wids['del_fit'] = Button(panel, 'Remove from Fit History', size=(200, -1), 

261 action=self.onRemoveFromHistory) 

262 

263 

264 

265 irow = 0 

266 sizer.Add(title, (irow, 0), (1, 1), LEFT) 

267 sizer.Add(wids['data_title'], (irow, 1), (1, 3), LEFT) 

268 

269 irow += 1 

270 wids['model_desc'] = SimpleText(panel, '<Model>', font=Font(FONTSIZE+1), 

271 size=(750, 50), style=LEFT) 

272 sizer.Add(wids['model_desc'], (irow, 0), (1, 6), LEFT) 

273 

274 irow += 1 

275 sizer.Add(wids['load_model'],(irow, 0), (1, 2), LEFT) 

276 

277 irow += 1 

278 sizer.Add(wids['plot_choice'],(irow, 0), (1, 1), LEFT) 

279 sizer.Add(ppanel, (irow, 1), (1, 4), LEFT) 

280 

281 

282 irow += 1 

283 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT) 

284 

285 irow += 1 

286 sizer.Add(SimpleText(panel, 'Fit Label:', style=LEFT), (irow, 0), (1, 1), LEFT) 

287 sizer.Add(wids['fit_label'], (irow, 1), (1, 1), LEFT) 

288 sizer.Add(wids['set_label'], (irow, 2), (1, 1), LEFT) 

289 sizer.Add(wids['del_fit'], (irow, 3), (1, 2), LEFT) 

290 

291 irow += 1 

292 title = SimpleText(panel, '[[Fit Statistics]]', font=Font(FONTSIZE+2), 

293 colour=COLORS['title'], style=LEFT) 

294 subtitle = SimpleText(panel, ' (most recent fit is at the top)', 

295 font=Font(FONTSIZE+1), style=LEFT) 

296 

297 sizer.Add(title, (irow, 0), (1, 1), LEFT) 

298 sizer.Add(subtitle, (irow, 1), (1, 1), LEFT) 

299 

300 sview = self.wids['stats'] = dv.DataViewListCtrl(panel, style=DVSTYLE) 

301 

302 sview.SetFont(self.font_fixedwidth) 

303 

304 xw = (175, 85, 85, 130, 130, 130) 

305 

306 

307 sview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectFit) 

308 sview.AppendTextColumn('Label', width=xw[0]) 

309 sview.AppendTextColumn('N_data', width=xw[1]) 

310 sview.AppendTextColumn('N_vary', width=xw[2]) 

311 sview.AppendTextColumn('\u03c7\u00B2', width=xw[3]) 

312 sview.AppendTextColumn('reduced \u03c7\u00B2', width=xw[4]) 

313 sview.AppendTextColumn('Akaike Info', width=xw[5]) 

314 

315 for col in range(sview.ColumnCount): 

316 this = sview.Columns[col] 

317 this.Sortable = True 

318 this.Alignment = wx.ALIGN_RIGHT if col > 0 else wx.ALIGN_LEFT 

319 this.Renderer.Alignment = this.Alignment 

320 

321 sview.SetMinSize((750, 150)) 

322 

323 irow += 1 

324 sizer.Add(sview, (irow, 0), (1, 5), LEFT) 

325 

326 irow += 1 

327 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT) 

328 

329 irow += 1 

330 title = SimpleText(panel, '[[Variables]]', font=Font(FONTSIZE+2), 

331 colour=COLORS['title'], style=LEFT) 

332 sizer.Add(title, (irow, 0), (1, 1), LEFT) 

333 

334 self.wids['copy_params'] = Button(panel, 'Update Model with these values', 

335 size=(250, -1), action=self.onCopyParams) 

336 

337 sizer.Add(self.wids['copy_params'], (irow, 1), (1, 3), LEFT) 

338 

339 pview = self.wids['params'] = dv.DataViewListCtrl(panel, style=DVSTYLE) 

340 pview.SetFont(self.font_fixedwidth) 

341 self.wids['paramsdata'] = [] 

342 

343 xw = (180, 140, 150, 250) 

344 pview.AppendTextColumn('Parameter', width=xw[0]) 

345 pview.AppendTextColumn('Best Value', width=xw[1]) 

346 pview.AppendTextColumn('1-\u03c3 Uncertainty', width=xw[2]) 

347 pview.AppendTextColumn('Info ', width=xw[3]) 

348 

349 for col in range(4): 

350 this = pview.Columns[col] 

351 this.Sortable = False 

352 this.Alignment = wx.ALIGN_RIGHT if col in (1, 2) else wx.ALIGN_LEFT 

353 this.Renderer.Alignment = this.Alignment 

354 

355 pview.SetMinSize((750, 200)) 

356 pview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectParameter) 

357 

358 irow += 1 

359 sizer.Add(pview, (irow, 0), (1, 5), LEFT) 

360 

361 irow += 1 

362 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT) 

363 

364 irow += 1 

365 title = SimpleText(panel, '[[Correlations]]', font=Font(FONTSIZE+2), 

366 colour=COLORS['title'], style=LEFT) 

367 

368 self.wids['all_correl'] = Button(panel, 'Show All', 

369 size=(100, -1), action=self.onAllCorrel) 

370 

371 self.wids['min_correl'] = FloatSpin(panel, value=MIN_CORREL, 

372 min_val=0, size=(100, -1), 

373 digits=3, increment=0.1) 

374 

375 ctitle = SimpleText(panel, 'minimum correlation: ') 

376 sizer.Add(title, (irow, 0), (1, 1), LEFT) 

377 sizer.Add(ctitle, (irow, 1), (1, 1), LEFT) 

378 sizer.Add(self.wids['min_correl'], (irow, 2), (1, 1), LEFT) 

379 sizer.Add(self.wids['all_correl'], (irow, 3), (1, 1), LEFT) 

380 

381 cview = self.wids['correl'] = dv.DataViewListCtrl(panel, style=DVSTYLE) 

382 cview.SetFont(self.font_fixedwidth) 

383 

384 

385 cview.AppendTextColumn('Parameter 1', width=150) 

386 cview.AppendTextColumn('Parameter 2', width=150) 

387 cview.AppendTextColumn('Correlation', width=150) 

388 

389 for col in (0, 1, 2): 

390 this = cview.Columns[col] 

391 this.Sortable = False 

392 align = wx.ALIGN_LEFT 

393 if col == 2: 

394 align = wx.ALIGN_RIGHT 

395 this.Alignment = this.Renderer.Alignment = align 

396 cview.SetMinSize((475, 150)) 

397 

398 irow += 1 

399 sizer.Add(cview, (irow, 0), (1, 5), LEFT) 

400 

401 pack(panel, sizer) 

402 panel.SetupScrolling() 

403 

404 splitter.SplitVertically(self.filelist, panel, 1) 

405 

406 mainsizer = wx.BoxSizer(wx.VERTICAL) 

407 mainsizer.Add(splitter, 1, wx.GROW|wx.ALL, 5) 

408 

409 pack(self, mainsizer) 

410 self.Show() 

411 self.Raise() 

412 

413 def onUpdateLabel(self, event=None): 

414 result = self.get_fitresult() 

415 item = self.wids['stats'].GetSelectedRow() 

416 result.label = self.wids['fit_label'].GetValue() 

417 self.show_results() 

418 

419 def onRemoveFromHistory(self, event=None): 

420 result = self.get_fitresult() 

421 if wx.ID_YES != Popup(self, 

422 f"Remove fit '{result.label}' from history?\nThis cannot be undone.", 

423 "Remove fit?", style=wx.YES_NO): 

424 return 

425 

426 self.datagroup.prepeaks.fit_history.pop(self.nfit) 

427 self.nfit = 0 

428 self.show_results() 

429 

430 

431 def onSaveAllStats(self, evt=None): 

432 "Save Parameters and Statistics to CSV" 

433 # get first dataset to extract fit parameter names 

434 fnames = self.filelist.GetItems() 

435 if len(fnames) == 0: 

436 return 

437 

438 deffile = "PrePeaksResults.csv" 

439 wcards = 'CVS Files (*.csv)|*.csv|All files (*.*)|*.*' 

440 path = FileSave(self, 'Save Parameter and Statistics for Pre-edge Peak Fits', 

441 default_file=deffile, wildcard=wcards) 

442 if path is None: 

443 return 

444 if Path(path).exists() and uname != 'darwin': # darwin prompts in FileSave! 

445 if wx.ID_YES != Popup(self, 

446 "Overwrite existing Statistics File?", 

447 "Overwrite existing file?", style=wx.YES_NO): 

448 return 

449 

450 ppeaks_tmpl = self.datasets[fnames[0]].prepeaks 

451 res0 = ppeaks_tmpl.fit_history[0].result 

452 param_names = list(reversed(res0.params.keys())) 

453 user_opts = ppeaks_tmpl.user_options 

454 model_desc = self.get_model_desc(res0.model).replace('\n', ' ') 

455 out = ['# Pre-edge Peak Fit Report %s' % time.ctime(), 

456 '# Fitted Array name: %s' % user_opts['array_name'], 

457 '# Model form: %s' % model_desc, 

458 '# Baseline form: %s' % user_opts['baseline_form'], 

459 '# Energy fit range: [%f, %f]' % (user_opts['emin'], user_opts['emax']), 

460 '#--------------------'] 

461 

462 labels = [('Data Set' + ' '*25)[:25], 'Group name', 'n_data', 

463 'n_varys', 'chi-square', 'reduced_chi-square', 

464 'akaike_info', 'bayesian_info'] 

465 

466 for pname in param_names: 

467 labels.append(pname) 

468 labels.append(pname+'_stderr') 

469 out.append('# %s' % (', '.join(labels))) 

470 for name, dgroup in self.datasets.items(): 

471 if not hasattr(dgroup, 'prepeaks'): 

472 continue 

473 try: 

474 pkfit = dgroup.prepeaks.fit_history[0] 

475 except: 

476 continue 

477 result = pkfit.result 

478 label = dgroup.filename 

479 if len(label) < 25: 

480 label = (label + ' '*25)[:25] 

481 dat = [label, dgroup.groupname, 

482 '%d' % result.ndata, '%d' % result.nvarys] 

483 for attr in ('chisqr', 'redchi', 'aic', 'bic'): 

484 dat.append(gformat(getattr(result, attr), 11)) 

485 for pname in param_names: 

486 val = stderr = 0 

487 if pname in result.params: 

488 par = result.params[pname] 

489 dat.append(gformat(par.value, 11)) 

490 stderr = gformat(par.stderr, 11) if par.stderr is not None else 'nan' 

491 dat.append(stderr) 

492 out.append(', '.join(dat)) 

493 out.append('') 

494 

495 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh: 

496 fh.write('\n'.join(out)) 

497 

498 def onSaveFitResult(self, event=None): 

499 deffile = self.datagroup.filename.replace('.', '_') + 'peak.modl' 

500 sfile = FileSave(self, 'Save Fit Model', default_file=deffile, 

501 wildcard=ModelWcards) 

502 if sfile is not None: 

503 pkfit = self.get_fitresult() 

504 save_groups(sfile, ['#peakfit 1.0', pkfit]) 

505 

506 def onExportFitResult(self, event=None): 

507 dgroup = self.datagroup 

508 deffile = dgroup.filename.replace('.', '_') + '.xdi' 

509 wcards = 'All files (*.*)|*.*' 

510 

511 outfile = FileSave(self, 'Export Fit Result', default_file=deffile) 

512 

513 pkfit = self.get_fitresult() 

514 result = pkfit.result 

515 if outfile is not None: 

516 i1, i2 = get_xlims(dgroup.xplot, 

517 pkfit.user_options['emin'], 

518 pkfit.user_options['emax']) 

519 x = dgroup.xplot[i1:i2] 

520 y = dgroup.yplot[i1:i2] 

521 yerr = None 

522 if hasattr(dgroup, 'yerr'): 

523 yerr = 1.0*dgroup.yerr 

524 if not isinstance(yerr, np.ndarray): 

525 yerr = yerr * np.ones(len(y)) 

526 else: 

527 yerr = yerr[i1:i2] 

528 

529 export_modelresult(result, filename=outfile, 

530 datafile=dgroup.filename, ydata=y, 

531 yerr=yerr, x=x) 

532 

533 

534 def get_fitresult(self, nfit=None): 

535 if nfit is None: 

536 nfit = self.nfit 

537 self.peakfit_history = getattr(self.datagroup.prepeaks, 'fit_history', []) 

538 self.nfit = max(0, nfit) 

539 if self.nfit > len(self.peakfit_history): 

540 self.nfit = 0 

541 if len(self.peakfit_history) > 0: 

542 return self.peakfit_history[self.nfit] 

543 

544 def onPlot(self, event=None): 

545 show_resid = self.wids['plot_resid'].IsChecked() 

546 sub_bline = self.wids['plot_bline'].IsChecked() 

547 win = int(self.wids['plot_win'].GetStringSelection()) 

548 cmd = "plot_prepeaks_fit(%s, nfit=%i, show_residual=%s, subtract_baseline=%s, win=%d)" 

549 cmd = cmd % (self.datagroup.groupname, self.nfit, show_resid, sub_bline, win) 

550 self.peakframe.larch_eval(cmd) 

551 self.peakframe.controller.set_focus(topwin=self) 

552 

553 def onSelectFit(self, evt=None): 

554 if self.wids['stats'] is None: 

555 return 

556 item = self.wids['stats'].GetSelectedRow() 

557 if item > -1: 

558 self.show_fitresult(nfit=item) 

559 

560 def onSelectParameter(self, evt=None): 

561 if self.wids['params'] is None: 

562 return 

563 if not self.wids['params'].HasSelection(): 

564 return 

565 item = self.wids['params'].GetSelectedRow() 

566 pname = self.wids['paramsdata'][item] 

567 

568 cormin= self.wids['min_correl'].GetValue() 

569 self.wids['correl'].DeleteAllItems() 

570 

571 result = self.get_fitresult() 

572 this = result.result.params[pname] 

573 if this.correl is not None: 

574 sort_correl = sorted(this.correl.items(), key=lambda it: abs(it[1])) 

575 for name, corval in reversed(sort_correl): 

576 if abs(corval) > cormin: 

577 self.wids['correl'].AppendItem((pname, name, "% .4f" % corval)) 

578 

579 def onAllCorrel(self, evt=None): 

580 result = self.get_fitresult() 

581 params = result.result.params 

582 parnames = list(params.keys()) 

583 

584 cormin= self.wids['min_correl'].GetValue() 

585 correls = {} 

586 for i, name in enumerate(parnames): 

587 par = params[name] 

588 if not par.vary: 

589 continue 

590 if hasattr(par, 'correl') and par.correl is not None: 

591 for name2 in parnames[i+1:]: 

592 if (name != name2 and name2 in par.correl and 

593 abs(par.correl[name2]) > cormin): 

594 correls["%s$$%s" % (name, name2)] = par.correl[name2] 

595 

596 sort_correl = sorted(correls.items(), key=lambda it: abs(it[1])) 

597 sort_correl.reverse() 

598 

599 self.wids['correl'].DeleteAllItems() 

600 

601 for namepair, corval in sort_correl: 

602 name1, name2 = namepair.split('$$') 

603 self.wids['correl'].AppendItem((name1, name2, "% .4f" % corval)) 

604 

605 def onLoadModel(self, event=None): 

606 self.peakframe.use_modelresult(self.get_fitresult()) 

607 

608 def onCopyParams(self, evt=None): 

609 result = self.get_fitresult() 

610 self.peakframe.update_start_values(result.result.params) 

611 

612 def ShowDataSet(self, evt=None): 

613 dataset = evt.GetString() 

614 group = self.datasets.get(evt.GetString(), None) 

615 if group is not None: 

616 self.show_results(datagroup=group, show_plot=True) 

617 

618 def add_results(self, dgroup, form=None, larch_eval=None, show=True): 

619 name = dgroup.filename 

620 if name not in self.filelist.GetItems(): 

621 self.filelist.Append(name) 

622 self.datasets[name] = dgroup 

623 if show: 

624 self.show_results(datagroup=dgroup, form=form, larch_eval=larch_eval) 

625 

626 def show_results(self, datagroup=None, form=None, show_plot=False, larch_eval=None): 

627 if datagroup is not None: 

628 self.datagroup = datagroup 

629 if larch_eval is not None: 

630 self.larch_eval = larch_eval 

631 

632 datagroup = self.datagroup 

633 self.peakfit_history = getattr(self.datagroup.prepeaks, 'fit_history', []) 

634 

635 # cur = self.get_fitresult() 

636 wids = self.wids 

637 wids['stats'].DeleteAllItems() 

638 for i, res in enumerate(self.peakfit_history): 

639 args = [res.label] 

640 for attr in ('ndata', 'nvarys', 'chisqr', 'redchi', 'aic'): 

641 val = getattr(res.result, attr) 

642 if isinstance(val, int): 

643 val = '%d' % val 

644 else: 

645 val = gformat(val, 10) 

646 args.append(val) 

647 wids['stats'].AppendItem(tuple(args)) 

648 wids['data_title'].SetLabel(self.datagroup.filename) 

649 self.show_fitresult(nfit=0) 

650 

651 if show_plot: 

652 show_resid= self.wids['plot_resid'].IsChecked() 

653 sub_bline = self.wids['plot_bline'].IsChecked() 

654 cmd = "plot_prepeaks_fit(%s, nfit=0, show_residual=%s, subtract_baseline=%s)" 

655 cmd = cmd % (datagroup.groupname, show_resid, sub_bline) 

656 

657 self.peakframe.larch_eval(cmd) 

658 self.peakframe.controller.set_focus(topwin=self) 

659 

660 def get_model_desc(self, model): 

661 model_repr = model._reprstring(long=True) 

662 for word in ('Model(', ',', '(', ')', '+'): 

663 model_repr = model_repr.replace(word, ' ') 

664 words = [] 

665 mname, imodel = '', 0 

666 for word in model_repr.split(): 

667 if word.startswith('prefix'): 

668 words.append("%sModel(%s)" % (mname.title(), word)) 

669 else: 

670 mname = word 

671 if imodel > 0: 

672 delim = '+' if imodel % 2 == 1 else '+\n' 

673 words.append(delim) 

674 imodel += 1 

675 return ''.join(words) 

676 

677 

678 def show_fitresult(self, nfit=0, datagroup=None): 

679 if datagroup is not None: 

680 self.datagroup = datagroup 

681 

682 result = self.get_fitresult(nfit=nfit) 

683 wids = self.wids 

684 try: 

685 wids['fit_label'].SetValue(result.label) 

686 wids['data_title'].SetLabel(self.datagroup.filename) 

687 wids['model_desc'].SetLabel(self.get_model_desc(result.result.model)) 

688 valid_result = True 

689 except: 

690 valid_result = False 

691 

692 wids['params'].DeleteAllItems() 

693 wids['paramsdata'] = [] 

694 if valid_result: 

695 for param in reversed(result.result.params.values()): 

696 pname = param.name 

697 try: 

698 val = gformat(param.value, 10) 

699 except (TypeError, ValueError): 

700 val = ' ??? ' 

701 serr = ' N/A ' 

702 if param.stderr is not None: 

703 serr = gformat(param.stderr, 10) 

704 extra = ' ' 

705 if param.expr is not None: 

706 extra = '= %s ' % param.expr 

707 elif not param.vary: 

708 extra = '(fixed)' 

709 elif param.init_value is not None: 

710 extra = '(init=%s)' % gformat(param.init_value, 10) 

711 

712 wids['params'].AppendItem((pname, val, serr, extra)) 

713 wids['paramsdata'].append(pname) 

714 self.Refresh() 

715 

716class PrePeakPanel(TaskPanel): 

717 def __init__(self, parent=None, controller=None, **kws): 

718 TaskPanel.__init__(self, parent, controller, panel='prepeaks', **kws) 

719 self.fit_components = {} 

720 self.user_added_params = None 

721 

722 self.pick2_timer = wx.Timer(self) 

723 self.pick2_group = None 

724 self.Bind(wx.EVT_TIMER, self.onPick2Timer, self.pick2_timer) 

725 self.pick2_t0 = 0. 

726 self.pick2_timeout = 15. 

727 

728 self.pick2erase_timer = wx.Timer(self) 

729 self.pick2erase_panel = None 

730 self.Bind(wx.EVT_TIMER, self.onPick2EraseTimer, self.pick2erase_timer) 

731 

732 def onPanelExposed(self, **kws): 

733 # called when notebook is selected 

734 try: 

735 fname = self.controller.filelist.GetStringSelection() 

736 gname = self.controller.file_groups[fname] 

737 dgroup = self.controller.get_group(gname) 

738 self.ensure_xas_processed(dgroup) 

739 self.fill_form(dgroup) 

740 except: 

741 pass # print(" Cannot Fill prepeak panel from group ") 

742 

743 pkfit = getattr(self.larch.symtable, 'peakresult', None) 

744 if pkfit is not None: 

745 self.showresults_btn.Enable() 

746 self.use_modelresult(pkfit) 

747 

748 def onModelPanelExposed(self, event=None, **kws): 

749 pass 

750 

751 def build_display(self): 

752 pan = self.panel # = GridPanel(self, ncols=4, nrows=4, pad=2, itemstyle=LEFT) 

753 

754 self.wids = {} 

755 

756 fsopts = dict(digits=2, increment=0.1, min_val=-999999, 

757 max_val=999999, size=(125, -1), with_pin=True) 

758 

759 ppeak_elo = self.add_floatspin('ppeak_elo', value=-13, **fsopts) 

760 ppeak_ehi = self.add_floatspin('ppeak_ehi', value=-3, **fsopts) 

761 ppeak_emin = self.add_floatspin('ppeak_emin', value=-20, **fsopts) 

762 ppeak_emax = self.add_floatspin('ppeak_emax', value=0, **fsopts) 

763 

764 self.loadresults_btn = Button(pan, 'Load Fit Result', 

765 action=self.onLoadFitResult, size=(165, -1)) 

766 self.showresults_btn = Button(pan, 'Show Fit Results', 

767 action=self.onShowResults, size=(165, -1)) 

768 self.showresults_btn.Disable() 

769 

770 self.fitbline_btn = Button(pan,'Fit Baseline', action=self.onFitBaseline, 

771 size=(165, -1)) 

772 

773 self.plotmodel_btn = Button(pan, 

774 'Plot Current Model', 

775 action=self.onPlotModel, size=(165, -1)) 

776 self.fitmodel_btn = Button(pan, 'Fit Current Group', 

777 action=self.onFitModel, size=(165, -1)) 

778 self.fitmodel_btn.Disable() 

779 self.fitselected_btn = Button(pan, 'Fit Selected Groups', 

780 action=self.onFitSelected, size=(165, -1)) 

781 self.fitselected_btn.Disable() 

782 self.fitmodel_btn.Disable() 

783 

784 self.array_choice = Choice(pan, size=(200, -1), 

785 choices=list(PrePeak_ArrayChoices.keys())) 

786 self.array_choice.SetSelection(0) 

787 

788 self.bline_choice = Choice(pan, size=(200, -1), 

789 choices=BaselineFuncs) 

790 self.bline_choice.SetSelection(2) 

791 

792 models_peaks = Choice(pan, size=(200, -1), 

793 choices=ModelChoices['peaks'], 

794 action=self.addModel) 

795 

796 models_other = Choice(pan, size=(200, -1), 

797 choices=ModelChoices['other'], 

798 action=self.addModel) 

799 

800 self.models_peaks = models_peaks 

801 self.models_other = models_other 

802 

803 

804 self.message = SimpleText(pan, 

805 'first fit baseline, then add peaks to fit model.') 

806 

807 opts = dict(default=True, size=(75, -1), action=self.onPlot) 

808 self.show_peakrange = Check(pan, label='show?', **opts) 

809 self.show_fitrange = Check(pan, label='show?', **opts) 

810 

811 opts = dict(default=False, size=(200, -1), action=self.onPlot) 

812 

813 def add_text(text, dcol=1, newrow=True): 

814 pan.Add(SimpleText(pan, text), dcol=dcol, newrow=newrow) 

815 

816 pan.Add(SimpleText(pan, 'Pre-edge Peak Fitting', 

817 size=(350, -1), **self.titleopts), style=LEFT, dcol=5) 

818 pan.Add(self.loadresults_btn) 

819 

820 add_text('Array to fit: ') 

821 pan.Add(self.array_choice, dcol=3) 

822 pan.Add((5,5)) 

823 pan.Add(self.showresults_btn) 

824 # add_text('E0: ', newrow=False) 

825 # pan.Add(ppeak_e0) 

826 # pan.Add(self.show_e0) 

827 

828 add_text('Fit Energy Range: ') 

829 pan.Add(ppeak_emin) 

830 add_text(' : ', newrow=False) 

831 pan.Add(ppeak_emax) 

832 pan.Add(self.show_fitrange) 

833 

834 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True) 

835 add_text( 'Baseline Form: ') 

836 t = SimpleText(pan, 'Baseline Skip Range: ') 

837 SetTip(t, 'Range skipped over for baseline fit') 

838 pan.Add(self.bline_choice, dcol=3) 

839 pan.Add((10, 10)) 

840 pan.Add(self.fitbline_btn) 

841 

842 pan.Add(t, newrow=True) 

843 pan.Add(ppeak_elo) 

844 add_text(' : ', newrow=False) 

845 pan.Add(ppeak_ehi) 

846 

847 pan.Add(self.show_peakrange) 

848 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True) 

849 

850 # add model 

851 ts = wx.BoxSizer(wx.HORIZONTAL) 

852 ts.Add(models_peaks) 

853 ts.Add(models_other) 

854 

855 pan.Add(SimpleText(pan, 'Add Component: '), newrow=True) 

856 pan.Add(ts, dcol=4) 

857 pan.Add(self.plotmodel_btn) 

858 

859 

860 pan.Add(SimpleText(pan, 'Fit Model to Current Group : '), dcol=5, newrow=True) 

861 pan.Add(self.fitmodel_btn) 

862 

863 pan.Add(SimpleText(pan, 'Messages: '), newrow=True) 

864 pan.Add(self.message, dcol=4) 

865 pan.Add(self.fitselected_btn) 

866 

867 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True) 

868 pan.pack() 

869 

870 self.mod_nb = flatnotebook(self, {}, on_change=self.onModelPanelExposed) 

871 self.mod_nb_init = True 

872 dummy_panel = wx.Panel(self.mod_nb) 

873 

874 self.mod_nb.AddPage(dummy_panel, 'Empty Model', True) 

875 sizer = wx.BoxSizer(wx.VERTICAL) 

876 sizer.Add((10, 10), 0, LEFT, 3) 

877 sizer.Add(pan, 0, LEFT, 3) 

878 sizer.Add((10, 10), 0, LEFT, 3) 

879 sizer.Add(self.mod_nb, 1, LEFT|wx.GROW, 5) 

880 

881 pack(self, sizer) 

882 

883 def get_config(self, dgroup=None): 

884 """get processing configuration for a group""" 

885 if dgroup is None: 

886 dgroup = self.controller.get_group() 

887 

888 conf = getattr(dgroup, 'prepeak_config', {}) 

889 if 'e0' not in conf: 

890 conf = self.controller.get_defaultcfonfig() 

891 conf['e0'] = getattr(dgroup, 'e0', -1) 

892 

893 dgroup.prepeak_config = conf 

894 if not hasattr(dgroup, 'prepeaks'): 

895 dgroup.prepeaks = Group() 

896 

897 return conf 

898 

899 def fill_form(self, dat): 

900 if isinstance(dat, Group): 

901 if not hasattr(dat, 'norm'): 

902 self.parent.process_normalization(dat) 

903 if hasattr(dat, 'prepeaks'): 

904 self.wids['ppeak_emin'].SetValue(dat.prepeaks.emin) 

905 self.wids['ppeak_emax'].SetValue(dat.prepeaks.emax) 

906 self.wids['ppeak_elo'].SetValue(dat.prepeaks.elo) 

907 self.wids['ppeak_ehi'].SetValue(dat.prepeaks.ehi) 

908 elif isinstance(dat, dict): 

909 # self.wids['ppeak_e0'].SetValue(dat['e0']) 

910 self.wids['ppeak_emin'].SetValue(dat['emin']) 

911 self.wids['ppeak_emax'].SetValue(dat['emax']) 

912 self.wids['ppeak_elo'].SetValue(dat['elo']) 

913 self.wids['ppeak_ehi'].SetValue(dat['ehi']) 

914 

915 self.array_choice.SetStringSelection(dat['array_desc']) 

916 self.bline_choice.SetStringSelection(dat['baseline_form']) 

917 

918 self.show_fitrange.Enable(dat['show_fitrange']) 

919 self.show_peakrange.Enable(dat['show_peakrange']) 

920 

921 def read_form(self): 

922 "read for, returning dict of values" 

923 dgroup = self.controller.get_group() 

924 array_desc = self.array_choice.GetStringSelection() 

925 bline_form = self.bline_choice.GetStringSelection() 

926 form_opts = {'gname': dgroup.groupname, 

927 'filename': dgroup.filename, 

928 'array_desc': array_desc.lower(), 

929 'array_name': PrePeak_ArrayChoices[array_desc], 

930 'baseline_form': bline_form.lower(), 

931 'bkg_components': []} 

932 

933 # form_opts['e0'] = self.wids['ppeak_e0'].GetValue() 

934 form_opts['emin'] = self.wids['ppeak_emin'].GetValue() 

935 form_opts['emax'] = self.wids['ppeak_emax'].GetValue() 

936 form_opts['elo'] = self.wids['ppeak_elo'].GetValue() 

937 form_opts['ehi'] = self.wids['ppeak_ehi'].GetValue() 

938 form_opts['plot_sub_bline'] = False # self.plot_sub_bline.IsChecked() 

939 # form_opts['show_centroid'] = self.show_centroid.IsChecked() 

940 form_opts['show_peakrange'] = self.show_peakrange.IsChecked() 

941 form_opts['show_fitrange'] = self.show_fitrange.IsChecked() 

942 return form_opts 

943 

944 def onFitBaseline(self, evt=None): 

945 opts = self.read_form() 

946 bline_form = opts.get('baseline_form', 'no baseline') 

947 if bline_form.startswith('no base'): 

948 return 

949 cmd = """{gname:s}.yplot = 1.0*{gname:s}.{array_name:s} 

950pre_edge_baseline(energy={gname:s}.energy, norm={gname:s}.yplot, group={gname:s}, form='{baseline_form:s}', 

951elo={elo:.3f}, ehi={ehi:.3f}, emin={emin:.3f}, emax={emax:.3f})""" 

952 self.larch_eval(cmd.format(**opts)) 

953 

954 dgroup = self.controller.get_group() 

955 ppeaks = dgroup.prepeaks 

956 dgroup.centroid_msg = "%.4f +/- %.4f eV" % (ppeaks.centroid, 

957 ppeaks.delta_centroid) 

958 

959 self.message.SetLabel("Centroid= %s" % dgroup.centroid_msg) 

960 

961 if '+' in bline_form: 

962 bforms = [f.lower() for f in bline_form.split('+')] 

963 else: 

964 bforms = [bline_form.lower(), ''] 

965 

966 poly_model = peak_model = None 

967 for bform in bforms: 

968 if bform.startswith('line'): poly_model = 'Linear' 

969 if bform.startswith('const'): poly_model = 'Constant' 

970 if bform.startswith('quad'): poly_model = 'Quadratic' 

971 if bform.startswith('loren'): peak_model = 'Lorentzian' 

972 if bform.startswith('guass'): peak_model = 'Gaussian' 

973 if bform.startswith('voigt'): peak_model = 'Voigt' 

974 

975 if peak_model is not None: 

976 if 'bpeak_' in self.fit_components: 

977 self.onDeleteComponent(prefix='bpeak_') 

978 self.addModel(model=peak_model, prefix='bpeak_', isbkg=True) 

979 

980 if poly_model is not None: 

981 if 'bpoly_' in self.fit_components: 

982 self.onDeleteComponent(prefix='bpoly_') 

983 self.addModel(model=poly_model, prefix='bpoly_', isbkg=True) 

984 

985 for prefix in ('bpeak_', 'bpoly_'): 

986 cmp = self.fit_components[prefix] 

987 # cmp.bkgbox.SetValue(1) 

988 self.fill_model_params(prefix, dgroup.prepeaks.fit_details.params) 

989 

990 self.fill_form(dgroup) 

991 self.fitmodel_btn.Enable() 

992 self.fitselected_btn.Enable() 

993 

994 i1, i2 = self.get_xranges(dgroup.energy) 

995 

996 dgroup.yfit = dgroup.xfit = 0.0*dgroup.energy[i1:i2] 

997 

998 self.onPlot(baseline_only=True) 

999 # self.savebline_btn.Enable() 

1000 

1001 def onSaveBaseline(self, evt=None): 

1002 opts = self.read_form() 

1003 

1004 dgroup = self.controller.get_group() 

1005 ppeaks = dgroup.prepeaks 

1006 

1007 deffile = dgroup.filename.replace('.', '_') + '_baseline.dat' 

1008 sfile = FileSave(self, 'Save Pre-edge Peak Baseline', default_file=deffile, 

1009 wildcard=DataWcards) 

1010 if sfile is None: 

1011 return 

1012 opts['savefile'] = sfile 

1013 opts['centroid'] = ppeaks.centroid 

1014 opts['delta_centroid'] = ppeaks.delta_centroid 

1015 

1016 cmd = """# save baseline script: 

1017header = ['baseline data from "{filename:s}"', 

1018 'baseline form = "{baseline_form:s}"', 

1019 'baseline fit range emin = {emin:.3f}', 

1020 'baseline fit range emax = {emax:.3f}', 

1021 'baseline peak range elo = {elo:.3f}', 

1022 'baseline peak range ehi = {ehi:.3f}', 

1023 'prepeak centroid energy = {centroid:.3f} +/- {delta_centroid:.3f} eV'] 

1024i0 = index_of({gname:s}.energy, {gname:s}.prepeaks.energy[0]) 

1025i1 = index_of({gname:s}.energy, {gname:s}.prepeaks.energy[-1]) 

1026{gname:s}.prepeaks.full_baseline = {gname:s}.norm*1.0 

1027{gname:s}.prepeaks.full_baseline[i0:i1+1] = {gname:s}.prepeaks.baseline 

1028 

1029write_ascii('{savefile:s}', {gname:s}.energy, {gname:s}.norm, {gname:s}.prepeaks.full_baseline, 

1030 header=header, label='energy norm baseline') 

1031 """ 

1032 self.larch_eval(cmd.format(**opts)) 

1033 

1034 

1035 def fill_model_params(self, prefix, params): 

1036 comp = self.fit_components[prefix] 

1037 parwids = comp.parwids 

1038 for pname, par in params.items(): 

1039 pname = prefix + pname 

1040 if pname in parwids: 

1041 wids = parwids[pname] 

1042 if wids.minval is not None: 

1043 wids.minval.SetValue(par.min) 

1044 if wids.maxval is not None: 

1045 wids.maxval.SetValue(par.max) 

1046 varstr = 'vary' if par.vary else 'fix' 

1047 if par.expr is not None: 

1048 varstr = 'constrain' 

1049 if wids.vary is not None: 

1050 wids.vary.SetStringSelection(varstr) 

1051 wids.value.SetValue(par.value) 

1052 

1053 def onPlotModel(self, evt=None): 

1054 dgroup = self.controller.get_group() 

1055 g = self.build_fitmodel(dgroup.groupname) 

1056 self.onPlot(show_init=True) 

1057 

1058 def onPlot(self, evt=None, baseline_only=False, show_init=False): 

1059 opts = self.read_form() 

1060 dgroup = self.controller.get_group() 

1061 opts['group'] = opts['gname'] 

1062 self.larch_eval(COMMANDS['prepeaks_setup'].format(**opts)) 

1063 

1064 ppeaks_opts = dict(array=opts['array_name'], elo=opts['elo'], 

1065 ehi=opts['ehi'], emin=opts['emin'], 

1066 emax=opts['emax']) 

1067 dgroup.journal.add_ifnew('prepeaks_setup', ppeaks_opts) 

1068 

1069 cmd = "plot_prepeaks_fit" 

1070 args = ['{gname}'] 

1071 if baseline_only: 

1072 cmd = "plot_prepeaks_baseline" 

1073 else: 

1074 args.append("show_init=%s" % (show_init)) 

1075 cmd = "%s(%s)" % (cmd, ', '.join(args)) 

1076 self.larch_eval(cmd.format(**opts)) 

1077 self.controller.set_focus() 

1078 

1079 def addModel(self, event=None, model=None, prefix=None, isbkg=False, opts=None): 

1080 if model is None and event is not None: 

1081 model = event.GetString() 

1082 if model is None or model.startswith('<'): 

1083 return 

1084 

1085 self.models_peaks.SetSelection(0) 

1086 self.models_other.SetSelection(0) 

1087 

1088 mod_abbrev = get_model_abbrev(model) 

1089 if prefix is None: 

1090 curmodels = ["%s%i_" % (mod_abbrev, i+1) for i in range(1+len(self.fit_components))] 

1091 for comp in self.fit_components: 

1092 if comp in curmodels: 

1093 curmodels.remove(comp) 

1094 

1095 prefix = curmodels[0] 

1096 

1097 label = "%s(prefix='%s')" % (model, prefix) 

1098 title = "%s: %s " % (prefix[:-1], model) 

1099 title = prefix[:-1] 

1100 mclass_kws = {'prefix': prefix} 

1101 if 'step' in mod_abbrev: 

1102 form = mod_abbrev.replace('_step', '').strip() 

1103 for sname, fullname in (('lin', 'linear'), ('atan', 'arctan'), 

1104 ('err', 'erf'), ('logi', 'logistic')): 

1105 if form.startswith(sname): 

1106 form = fullname 

1107 if form not in ('linear', 'erf', 'arctan', 'logistic'): 

1108 if opts is None: 

1109 opts = {'form': 'linear'} 

1110 form = opts.get('form', 'linear') 

1111 

1112 label = "Step(form='%s', prefix='%s')" % (form, prefix) 

1113 title = "%s: Step %s" % (prefix[:-1], form[:3]) 

1114 mclass = lm_models.StepModel 

1115 mclass_kws['form'] = form 

1116 minst = mclass(form=form, prefix=prefix, 

1117 independent_vars=['x', 'form']) 

1118 else: 

1119 if model in ModelFuncs: 

1120 mclass = getattr(lm_models, ModelFuncs[model]) 

1121 else: 

1122 mclass = getattr(lm_models, model+'Model') 

1123 

1124 minst = mclass(prefix=prefix) 

1125 

1126 panel = GridPanel(self.mod_nb, ncols=2, nrows=5, pad=1, itemstyle=CEN) 

1127 panel.SetFont(Font(FONTSIZE)) 

1128 

1129 def SLabel(label, size=(80, -1), **kws): 

1130 return SimpleText(panel, label, 

1131 size=size, style=wx.ALIGN_LEFT, **kws) 

1132 usebox = Check(panel, default=True, label='Use in Fit?', size=(100, -1)) 

1133 bkgbox = Check(panel, default=False, label='Is Baseline?', size=(125, -1)) 

1134 if isbkg: 

1135 bkgbox.SetValue(1) 

1136 

1137 delbtn = Button(panel, 'Delete This Component', size=(200, -1), 

1138 action=partial(self.onDeleteComponent, prefix=prefix)) 

1139 

1140 pick2msg = SimpleText(panel, " ", size=(125, -1)) 

1141 pick2btn = Button(panel, 'Pick Values from Plot', size=(200, -1), 

1142 action=partial(self.onPick2Points, prefix=prefix)) 

1143 

1144 # SetTip(mname, 'Label for the model component') 

1145 SetTip(usebox, 'Use this component in fit?') 

1146 SetTip(bkgbox, 'Label this component as "background" when plotting?') 

1147 SetTip(delbtn, 'Delete this model component') 

1148 SetTip(pick2btn, 'Select X range on Plot to Guess Initial Values') 

1149 

1150 panel.Add(SLabel(label, size=(275, -1), colour='#0000AA'), 

1151 dcol=4, style=wx.ALIGN_LEFT, newrow=True) 

1152 panel.Add(usebox, dcol=2) 

1153 panel.Add(bkgbox, dcol=1, style=RIGHT) 

1154 

1155 panel.Add(pick2btn, dcol=2, style=wx.ALIGN_LEFT, newrow=True) 

1156 panel.Add(pick2msg, dcol=3, style=wx.ALIGN_RIGHT) 

1157 panel.Add(delbtn, dcol=2, style=wx.ALIGN_RIGHT) 

1158 

1159 panel.Add(SLabel("Parameter "), style=wx.ALIGN_LEFT, newrow=True) 

1160 panel.AddMany((SLabel(" Value"), SLabel(" Type"), SLabel(' Bounds'), 

1161 SLabel(" Min", size=(60, -1)), 

1162 SLabel(" Max", size=(60, -1)), SLabel(" Expression"))) 

1163 

1164 parwids = {} 

1165 parnames = sorted(minst.param_names) 

1166 

1167 for a in minst._func_allargs: 

1168 pname = "%s%s" % (prefix, a) 

1169 if (pname not in parnames and 

1170 a in minst.param_hints and 

1171 a not in minst.independent_vars): 

1172 parnames.append(pname) 

1173 

1174 for pname in parnames: 

1175 sname = pname[len(prefix):] 

1176 hints = minst.param_hints.get(sname, {}) 

1177 

1178 par = Parameter(name=pname, value=0, vary=True) 

1179 if 'min' in hints: 

1180 par.min = hints['min'] 

1181 if 'max' in hints: 

1182 par.max = hints['max'] 

1183 if 'value' in hints: 

1184 par.value = hints['value'] 

1185 if 'expr' in hints: 

1186 par.expr = hints['expr'] 

1187 

1188 pwids = ParameterWidgets(panel, par, name_size=110, 

1189 expr_size=200, 

1190 float_size=80, prefix=prefix, 

1191 widgets=('name', 'value', 'minval', 

1192 'maxval', 'vary', 'expr')) 

1193 parwids[par.name] = pwids 

1194 panel.Add(pwids.name, newrow=True) 

1195 

1196 panel.AddMany((pwids.value, pwids.vary, pwids.bounds, 

1197 pwids.minval, pwids.maxval, pwids.expr)) 

1198 

1199 for sname, hint in minst.param_hints.items(): 

1200 pname = "%s%s" % (prefix, sname) 

1201 if 'expr' in hint and pname not in parnames: 

1202 par = Parameter(name=pname, value=0, expr=hint['expr']) 

1203 pwids = ParameterWidgets(panel, par, name_size=110, 

1204 expr_size=400, 

1205 float_size=80, prefix=prefix, 

1206 widgets=('name', 'value', 'expr')) 

1207 parwids[par.name] = pwids 

1208 panel.Add(pwids.name, newrow=True) 

1209 panel.Add(pwids.value) 

1210 panel.Add(pwids.expr, dcol=5, style=wx.ALIGN_RIGHT) 

1211 pwids.value.Disable() 

1212 

1213 fgroup = Group(prefix=prefix, title=title, mclass=mclass, 

1214 mclass_kws=mclass_kws, usebox=usebox, panel=panel, 

1215 parwids=parwids, float_size=65, expr_size=150, 

1216 pick2_msg=pick2msg, bkgbox=bkgbox) 

1217 

1218 

1219 self.fit_components[prefix] = fgroup 

1220 panel.pack() 

1221 if self.mod_nb_init: 

1222 self.mod_nb.DeletePage(0) 

1223 self.mod_nb_init = False 

1224 

1225 self.mod_nb.AddPage(panel, title, True) 

1226 sx,sy = self.GetSize() 

1227 self.SetSize((sx, sy+1)) 

1228 self.SetSize((sx, sy)) 

1229 self.fitmodel_btn.Enable() 

1230 self.fitselected_btn.Enable() 

1231 

1232 

1233 def onDeleteComponent(self, evt=None, prefix=None): 

1234 fgroup = self.fit_components.get(prefix, None) 

1235 if fgroup is None: 

1236 return 

1237 

1238 for i in range(self.mod_nb.GetPageCount()): 

1239 if fgroup.title == self.mod_nb.GetPageText(i): 

1240 self.mod_nb.DeletePage(i) 

1241 

1242 for attr in dir(fgroup): 

1243 setattr(fgroup, attr, None) 

1244 

1245 self.fit_components.pop(prefix) 

1246 if len(self.fit_components) < 1: 

1247 self.fitmodel_btn.Disable() 

1248 self.fitselected_btn.Enable() 

1249 

1250 def onPick2EraseTimer(self, evt=None): 

1251 """erases line trace showing automated 'Pick 2' guess """ 

1252 self.pick2erase_timer.Stop() 

1253 panel = self.pick2erase_panel 

1254 ntrace = panel.conf.ntrace - 1 

1255 trace = panel.conf.get_mpl_line(ntrace) 

1256 panel.conf.get_mpl_line(ntrace).set_data(np.array([]), np.array([])) 

1257 panel.conf.ntrace = ntrace 

1258 panel.draw() 

1259 

1260 def onPick2Timer(self, evt=None): 

1261 """checks for 'Pick 2' events, and initiates 'Pick 2' guess 

1262 for a model from the selected data range 

1263 """ 

1264 try: 

1265 plotframe = self.controller.get_display(win=1) 

1266 curhist = plotframe.cursor_hist[:] 

1267 plotframe.Raise() 

1268 except: 

1269 return 

1270 

1271 if (time.time() - self.pick2_t0) > self.pick2_timeout: 

1272 msg = self.pick2_group.pick2_msg.SetLabel(" ") 

1273 plotframe.cursor_hist = [] 

1274 self.pick2_timer.Stop() 

1275 return 

1276 

1277 if len(curhist) < 2: 

1278 self.pick2_group.pick2_msg.SetLabel("%i/2" % (len(curhist))) 

1279 return 

1280 

1281 self.pick2_group.pick2_msg.SetLabel("done.") 

1282 self.pick2_timer.Stop() 

1283 

1284 # guess param values 

1285 xcur = (curhist[0][0], curhist[1][0]) 

1286 xmin, xmax = min(xcur), max(xcur) 

1287 

1288 dgroup = getattr(self.larch.symtable, self.controller.groupname) 

1289 i0 = index_of(dgroup.xplot, xmin) 

1290 i1 = index_of(dgroup.xplot, xmax) 

1291 x, y = dgroup.xplot[i0:i1+1], dgroup.yplot[i0:i1+1] 

1292 

1293 mod = self.pick2_group.mclass(prefix=self.pick2_group.prefix) 

1294 parwids = self.pick2_group.parwids 

1295 try: 

1296 guesses = mod.guess(y, x=x) 

1297 except: 

1298 return 

1299 for name, param in guesses.items(): 

1300 if 'amplitude' in name: 

1301 param.value *= 1.5 

1302 elif 'sigma' in name: 

1303 param.value *= 0.75 

1304 if name in parwids: 

1305 parwids[name].value.SetValue(param.value) 

1306 

1307 dgroup._tmp = mod.eval(guesses, x=dgroup.xplot) 

1308 plotframe = self.controller.get_display(win=1) 

1309 plotframe.cursor_hist = [] 

1310 plotframe.oplot(dgroup.xplot, dgroup._tmp) 

1311 self.pick2erase_panel = plotframe.panel 

1312 

1313 self.pick2erase_timer.Start(60000) 

1314 

1315 

1316 def onPick2Points(self, evt=None, prefix=None): 

1317 fgroup = self.fit_components.get(prefix, None) 

1318 if fgroup is None: 

1319 return 

1320 

1321 plotframe = self.controller.get_display(win=1) 

1322 plotframe.Raise() 

1323 

1324 plotframe.cursor_hist = [] 

1325 fgroup.npts = 0 

1326 self.pick2_group = fgroup 

1327 

1328 if fgroup.pick2_msg is not None: 

1329 fgroup.pick2_msg.SetLabel("0/2") 

1330 

1331 self.pick2_t0 = time.time() 

1332 self.pick2_timer.Start(1000) 

1333 

1334 

1335 def onLoadFitResult(self, event=None): 

1336 rfile = FileOpen(self, "Load Saved Pre-edge Model", 

1337 wildcard=ModelWcards) 

1338 if rfile is None: 

1339 return 

1340 

1341 self.larch_eval(f"# peakmodel = read_groups('{rfile}')[1]") 

1342 dat = read_groups(str(rfile)) 

1343 if len(dat) != 2 or not dat[0].startswith('#peakfit'): 

1344 Popup(self, f" '{rfile}' is not a valid Peak Model file", 

1345 "Invalid file") 

1346 

1347 self.use_modelresult(dat[1]) 

1348 

1349 def use_modelresult(self, pkfit): 

1350 for prefix in list(self.fit_components.keys()): 

1351 self.onDeleteComponent(prefix=prefix) 

1352 

1353 result = pkfit.result 

1354 bkg_comps = pkfit.user_options['bkg_components'] 

1355 for comp in result.model.components: 

1356 isbkg = comp.prefix in bkg_comps 

1357 self.addModel(model=comp.func.__name__, 

1358 prefix=comp.prefix, isbkg=isbkg, 

1359 opts=comp.opts) 

1360 

1361 for comp in result.model.components: 

1362 parwids = self.fit_components[comp.prefix].parwids 

1363 for pname, par in result.params.items(): 

1364 if pname in parwids: 

1365 wids = parwids[pname] 

1366 wids.value.SetValue(result.init_values.get(pname, par.value)) 

1367 varstr = 'vary' if par.vary else 'fix' 

1368 if par.expr is not None: varstr = 'constrain' 

1369 if wids.vary is not None: wids.vary.SetStringSelection(varstr) 

1370 if wids.minval is not None: wids.minval.SetValue(par.min) 

1371 if wids.maxval is not None: wids.maxval.SetValue(par.max) 

1372 

1373 self.fill_form(pkfit.user_options) 

1374 

1375 

1376 def get_xranges(self, x): 

1377 opts = self.read_form() 

1378 dgroup = self.controller.get_group() 

1379 en_eps = min(np.diff(dgroup.energy)) / 5. 

1380 

1381 i1 = index_of(x, opts['emin'] + en_eps) 

1382 i2 = index_of(x, opts['emax'] + en_eps) + 1 

1383 return i1, i2 

1384 

1385 def build_fitmodel(self, groupname=None): 

1386 """ use fit components to build model""" 

1387 # self.summary = {'components': [], 'options': {}} 

1388 peaks = [] 

1389 cmds = ["## set up pre-edge peak parameters", "peakpars = Parameters()"] 

1390 modcmds = ["## define pre-edge peak model"] 

1391 modop = " =" 

1392 opts = self.read_form() 

1393 if groupname is None: 

1394 groupname = opts['gname'] 

1395 

1396 opts['group'] = groupname 

1397 dgroup = self.controller.get_group(groupname) 

1398 self.larch_eval(COMMANDS['prepeaks_setup'].format(**opts)) 

1399 

1400 ppeaks_opts = dict(array=opts['array_name'], elo=opts['elo'], 

1401 ehi=opts['ehi'], emin=opts['emin'], 

1402 emax=opts['emax']) 

1403 dgroup.journal.add_ifnew('prepeaks_setup', ppeaks_opts) 

1404 

1405 

1406 for comp in self.fit_components.values(): 

1407 _cen, _amp = None, None 

1408 if comp.usebox is not None and comp.usebox.IsChecked(): 

1409 for parwids in comp.parwids.values(): 

1410 this = parwids.param 

1411 pargs = ["'%s'" % this.name, 'value=%f' % (this.value), 

1412 'min=%f' % (this.min), 'max=%f' % (this.max)] 

1413 if this.expr is not None: 

1414 pargs.append("expr='%s'" % (this.expr)) 

1415 elif not this.vary: 

1416 pargs.pop() 

1417 pargs.pop() 

1418 pargs.append("vary=False") 

1419 

1420 cmds.append("peakpars.add(%s)" % (', '.join(pargs))) 

1421 if this.name.endswith('_center'): 

1422 _cen = this.name 

1423 elif parwids.param.name.endswith('_amplitude'): 

1424 _amp = this.name 

1425 compargs = ["%s='%s'" % (k,v) for k,v in comp.mclass_kws.items()] 

1426 modcmds.append("peakmodel %s %s(%s)" % (modop, comp.mclass.__name__, 

1427 ', '.join(compargs))) 

1428 

1429 modop = "+=" 

1430 if not comp.bkgbox.IsChecked() and _cen is not None and _amp is not None: 

1431 peaks.append((_amp, _cen)) 

1432 

1433 if len(peaks) > 0: 

1434 denom = '+'.join([p[0] for p in peaks]) 

1435 numer = '+'.join(["%s*%s "% p for p in peaks]) 

1436 cmds.append("peakpars.add('fit_centroid', expr='(%s)/(%s)')" % (numer, denom)) 

1437 

1438 cmds.extend(modcmds) 

1439 cmds.append(COMMANDS['prepfit'].format(group=dgroup.groupname, 

1440 user_opts=repr(opts))) 

1441 

1442 self.larch_eval("\n".join(cmds)) 

1443 

1444 def onFitSelected(self, event=None): 

1445 dgroup = self.controller.get_group() 

1446 if dgroup is None: 

1447 return 

1448 

1449 opts = self.read_form() 

1450 

1451 self.show_subframe('prepeak_result', PrePeakFitResultFrame, 

1452 datagroup=dgroup, peakframe=self) 

1453 

1454 selected_groups = self.controller.filelist.GetCheckedStrings() 

1455 groups = [self.controller.file_groups[cn] for cn in selected_groups] 

1456 ngroups = len(groups) 

1457 for igroup, gname in enumerate(groups): 

1458 dgroup = self.controller.get_group(gname) 

1459 if not hasattr(dgroup, 'norm'): 

1460 self.parent.process_normalization(dgroup) 

1461 self.build_fitmodel(gname) 

1462 opts['group'] = opts['gname'] 

1463 self.larch_eval(COMMANDS['prepeaks_setup'].format(**opts)) 

1464 

1465 ppeaks_opts = dict(array=opts['array_name'], elo=opts['elo'], 

1466 ehi=opts['ehi'], emin=opts['emin'], 

1467 emax=opts['emax']) 

1468 dgroup.journal.add_ifnew('prepeaks_setup', ppeaks_opts) 

1469 ppeaks = dgroup.prepeaks 

1470 

1471 # add bkg_component to saved user options 

1472 bkg_comps = [] 

1473 for label, comp in self.fit_components.items(): 

1474 if comp.bkgbox.IsChecked(): 

1475 bkg_comps.append(label) 

1476 

1477 opts['bkg_components'] = bkg_comps 

1478 imin, imax = self.get_xranges(dgroup.xplot) 

1479 cmds = ["## do peak fit for group %s / %s " % (gname, dgroup.filename) ] 

1480 

1481 yerr_type = 'set_yerr_const' 

1482 yerr = getattr(dgroup, 'yerr', None) 

1483 if yerr is None: 

1484 if hasattr(dgroup, 'norm_std'): 

1485 cmds.append("{group}.yerr = {group}.norm_std") 

1486 yerr_type = 'set_yerr_array' 

1487 elif hasattr(dgroup, 'mu_std'): 

1488 cmds.append("{group}.yerr = {group}.mu_std/(1.e-15+{group}.edge_step)") 

1489 yerr_type = 'set_yerr_array' 

1490 else: 

1491 cmds.append("{group}.yerr = 1") 

1492 elif isinstance(dgroup.yerr, np.ndarray): 

1493 yerr_type = 'set_yerr_array' 

1494 

1495 cmds.extend([COMMANDS[yerr_type], COMMANDS['dofit']]) 

1496 cmd = '\n'.join(cmds) 

1497 self.larch_eval(cmd.format(group=dgroup.groupname, 

1498 imin=imin, imax=imax, 

1499 user_opts=repr(opts))) 

1500 

1501 pkfit = self.larch_get("peakresult") 

1502 jnl = {'label': pkfit.label, 'var_names': pkfit.result.var_names, 

1503 'model': repr(pkfit.result.model)} 

1504 jnl.update(pkfit.user_options) 

1505 dgroup.journal.add('peakfit', jnl) 

1506 if igroup == 0: 

1507 self.autosave_modelresult(pkfit) 

1508 

1509 self.subframes['prepeak_result'].add_results(dgroup, form=opts, 

1510 larch_eval=self.larch_eval, 

1511 show=igroup==ngroups-1) 

1512 

1513 def onFitModel(self, event=None): 

1514 dgroup = self.controller.get_group() 

1515 if dgroup is None: 

1516 return 

1517 self.build_fitmodel(dgroup.groupname) 

1518 opts = self.read_form() 

1519 

1520 dgroup = self.controller.get_group() 

1521 opts['group'] = opts['gname'] 

1522 self.larch_eval(COMMANDS['prepeaks_setup'].format(**opts)) 

1523 

1524 ppeaks_opts = dict(array=opts['array_name'], elo=opts['elo'], 

1525 ehi=opts['ehi'], emin=opts['emin'], 

1526 emax=opts['emax']) 

1527 dgroup.journal.add_ifnew('prepeaks_setup', ppeaks_opts) 

1528 

1529 ppeaks = dgroup.prepeaks 

1530 

1531 # add bkg_component to saved user options 

1532 bkg_comps = [] 

1533 for label, comp in self.fit_components.items(): 

1534 if comp.bkgbox.IsChecked(): 

1535 bkg_comps.append(label) 

1536 opts['bkg_components'] = bkg_comps 

1537 

1538 imin, imax = self.get_xranges(dgroup.xplot) 

1539 

1540 cmds = ["## do peak fit: "] 

1541 yerr_type = 'set_yerr_const' 

1542 yerr = getattr(dgroup, 'yerr', None) 

1543 if yerr is None: 

1544 if hasattr(dgroup, 'norm_std'): 

1545 cmds.append("{group}.yerr = {group}.norm_std") 

1546 yerr_type = 'set_yerr_array' 

1547 elif hasattr(dgroup, 'mu_std'): 

1548 cmds.append("{group}.yerr = {group}.mu_std/(1.e-15+{group}.edge_step)") 

1549 yerr_type = 'set_yerr_array' 

1550 else: 

1551 cmds.append("{group}.yerr = 1") 

1552 elif isinstance(dgroup.yerr, np.ndarray): 

1553 yerr_type = 'set_yerr_array' 

1554 

1555 cmds.extend([COMMANDS[yerr_type], COMMANDS['dofit']]) 

1556 cmd = '\n'.join(cmds) 

1557 self.larch_eval(cmd.format(group=dgroup.groupname, 

1558 imin=imin, imax=imax, 

1559 user_opts=repr(opts))) 

1560 

1561 # journal about peakresult 

1562 pkfit = self.larch_get("peakresult") 

1563 jnl = {'label': pkfit.label, 'var_names': pkfit.result.var_names, 

1564 'model': repr(pkfit.result.model)} 

1565 jnl.update(pkfit.user_options) 

1566 dgroup.journal.add('peakfit', jnl) 

1567 

1568 self.autosave_modelresult(pkfit) 

1569 self.onPlot() 

1570 self.showresults_btn.Enable() 

1571 

1572 

1573 self.show_subframe('prepeak_result', PrePeakFitResultFrame, peakframe=self) 

1574 self.subframes['prepeak_result'].add_results(dgroup, form=opts, 

1575 larch_eval=self.larch_eval) 

1576 

1577 def onShowResults(self, event=None): 

1578 self.show_subframe('prepeak_result', PrePeakFitResultFrame, 

1579 peakframe=self) 

1580 

1581 

1582 def update_start_values(self, params): 

1583 """fill parameters with best fit values""" 

1584 allparwids = {} 

1585 for comp in self.fit_components.values(): 

1586 if comp.usebox is not None and comp.usebox.IsChecked(): 

1587 for name, parwids in comp.parwids.items(): 

1588 allparwids[name] = parwids 

1589 

1590 for pname, par in params.items(): 

1591 if pname in allparwids: 

1592 allparwids[pname].value.SetValue(par.value) 

1593 

1594 def autosave_modelresult(self, result, fname=None): 

1595 """autosave model result to user larch folder""" 

1596 confdir = self.controller.larix_folder 

1597 if fname is None: 

1598 fname = 'autosave_peakfile.modl' 

1599 save_groups(Path(confdir, fname).as_posix(), ['#peakfit 1.0', result])