Coverage for /Users/Newville/Codes/xraylarch/larch/wxxas/prepeak_panel.py: 9%

1046 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1import time 

2import os 

3import sys 

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 

16 

17import lmfit.models as lm_models 

18 

19from larch import Group, site_config 

20from larch.utils import uname, gformat, mkdir 

21from larch.math import index_of 

22from larch.io.export_modelresult import export_modelresult 

23from larch.io import save_groups, read_groups 

24 

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

26 SetTip, GridPanel, get_icon, SimpleText, pack, 

27 Button, HLine, Choice, Check, MenuItem, COLORS, 

28 set_color, CEN, RIGHT, LEFT, FRAMESTYLE, Font, 

29 FONTSIZE, FONTSIZE_FW, FileSave, FileOpen, 

30 flatnotebook, Popup, EditableListBox, ExceptionPopup) 

31 

32from larch.wxlib.parameter import ParameterWidgets 

33from larch.wxlib.plotter import last_cursor_pos 

34from .taskpanel import TaskPanel 

35from .config import PrePeak_ArrayChoices, PlotWindowChoices 

36 

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

38 

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

40 'Quadratic', 'Exponential', 'PowerLaw' 

41 'Linear Step', 'Arctan Step', 

42 'ErrorFunction Step', 'Logistic Step', 'Rectangle'), 

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

44 'Voigt', 'PseudoVoigt', 'DampedHarmonicOscillator', 

45 'Pearson7', 'StudentsT', 'SkewedGaussian', 

46 'Moffat', 'BreitWigner', 'Doniach', 'Lognormal'), 

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 

73BaselineFuncs = ['No Baseline', 

74 'Constant+Lorentzian', 

75 'Linear+Lorentzian', 

76 'Constant+Gaussian', 

77 'Linear+Gaussian', 

78 'Constant+Voigt', 

79 'Linear+Voigt', 

80 'Quadratic', 'Linear'] 

81 

82 

83PLOT_BASELINE = 'Data+Baseline' 

84PLOT_FIT = 'Data+Fit' 

85PLOT_INIT = 'Data+Init Fit' 

86PLOT_RESID = 'Data+Residual' 

87PlotChoices = [PLOT_BASELINE, PLOT_FIT, PLOT_RESID] 

88 

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

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

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

92 

93 

94MIN_CORREL = 0.10 

95 

96COMMANDS = {} 

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

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

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

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

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

102""" 

103 

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

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

106{group:s}.xdat = 1.0*{group:s}.energy 

107{group:s}.ydat = 1.0*{group:s}.{array_name:s} 

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

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

110""" 

111 

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

113COMMANDS['set_yerr_array'] = """ 

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

115yerr_min = 1.e-9*{group}.prepeaks.ydat.mean() 

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

117""" 

118 

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

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

121peakresult.user_options = {user_opts:s} 

122""" 

123 

124def get_xlims(x, xmin, xmax): 

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

126 i1 = index_of(x, xmin + xeps) 

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

128 return i1, i2 

129 

130class PrePeakFitResultFrame(wx.Frame): 

131 config_sect = 'prepeak' 

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

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

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

135 self.peakframe = peakframe 

136 

137 if datagroup is not None: 

138 self.datagroup = datagroup 

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

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

141 self.parent = parent 

142 self.datasets = {} 

143 self.form = {} 

144 self.larch_eval = self.peakframe.larch_eval 

145 self.nfit = 0 

146 self.createMenus() 

147 self.build() 

148 

149 if datagroup is None: 

150 symtab = self.peakframe.larch.symtable 

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

152 if xasgroups is not None: 

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

154 dgroup = getattr(symtab, dgroup, None) 

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

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

157 if hist is not None: 

158 self.add_results(dgroup, show=True) 

159 

160 

161 def createMenus(self): 

162 self.menubar = wx.MenuBar() 

163 fmenu = wx.Menu() 

164 m = {} 

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

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

167 self.onSaveFitResult) 

168 

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

170 "Save Arrays and Results to Text File", 

171 self.onExportFitResult) 

172 

173 fmenu.AppendSeparator() 

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

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

176 self.onSaveAllStats) 

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

178 self.SetMenuBar(self.menubar) 

179 

180 def build(self): 

181 sizer = wx.GridBagSizer(3, 3) 

182 sizer.SetVGap(3) 

183 sizer.SetHGap(3) 

184 

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

186 splitter.SetMinimumPaneSize(200) 

187 

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

189 size=(250, -1)) 

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

191 

192 panel = scrolled.ScrolledPanel(splitter) 

193 

194 panel.SetMinSize((775, 575)) 

195 

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

197 

198 # title row 

199 self.wids = wids = {} 

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

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

202 

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

204 minsize=(350, -1), 

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

206 

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

208 ppanel = wx.Panel(panel) 

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

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

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

212 action=self.onPlot) 

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

214 

215 psizer = wx.BoxSizer(wx.HORIZONTAL) 

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

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

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

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

220 

221 pack(ppanel, psizer) 

222 

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

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

225 

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

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

228 

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

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

231 action=self.onUpdateLabel) 

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

233 action=self.onRemoveFromHistory) 

234 

235 

236 

237 irow = 0 

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

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

240 

241 irow += 1 

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

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

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

245 

246 irow += 1 

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

248 

249 irow += 1 

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

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

252 

253 

254 irow += 1 

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

256 

257 irow += 1 

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

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

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

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

262 

263 irow += 1 

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

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

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

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

268 

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

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

271 

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

273 sview.SetFont(self.font_fixedwidth) 

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

275 sview.AppendTextColumn(' Label', width=120) 

276 sview.AppendTextColumn(' N_data', width=75) 

277 sview.AppendTextColumn(' N_vary', width=75) 

278 sview.AppendTextColumn('\u03c7\u00B2', width=110) 

279 sview.AppendTextColumn('reduced \u03c7\u00B2', width=110) 

280 sview.AppendTextColumn('Akaike Info', width=110) 

281 

282 for col in range(sview.ColumnCount): 

283 this = sview.Columns[col] 

284 this.Sortable = True 

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

286 this.Renderer.Alignment = this.Alignment 

287 

288 sview.SetMinSize((725, 150)) 

289 

290 irow += 1 

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

292 

293 irow += 1 

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

295 

296 irow += 1 

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

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

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

300 

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

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

303 

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

305 

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

307 pview.SetFont(self.font_fixedwidth) 

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

309 pview.AppendTextColumn('Parameter', width=150) 

310 pview.AppendTextColumn('Best-Fit Value', width=125) 

311 pview.AppendTextColumn('Standard Error', width=125) 

312 pview.AppendTextColumn('Info ', width=300) 

313 

314 for col in range(4): 

315 this = pview.Columns[col] 

316 this.Sortable = False 

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

318 this.Renderer.Alignment = this.Alignment 

319 

320 pview.SetMinSize((725, 200)) 

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

322 

323 irow += 1 

324 sizer.Add(pview, (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, '[[Correlations]]', font=Font(FONTSIZE+2), 

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

332 

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

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

335 

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

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

338 digits=3, increment=0.1) 

339 

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

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

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

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

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

345 

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

347 cview.SetFont(self.font_fixedwidth) 

348 

349 

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

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

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

353 

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

355 this = cview.Columns[col] 

356 this.Sortable = False 

357 align = wx.ALIGN_LEFT 

358 if col == 2: 

359 align = wx.ALIGN_RIGHT 

360 this.Alignment = this.Renderer.Alignment = align 

361 cview.SetMinSize((475, 150)) 

362 

363 irow += 1 

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

365 

366 pack(panel, sizer) 

367 panel.SetupScrolling() 

368 

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

370 

371 mainsizer = wx.BoxSizer(wx.VERTICAL) 

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

373 

374 pack(self, mainsizer) 

375 self.Show() 

376 self.Raise() 

377 

378 def onUpdateLabel(self, event=None): 

379 result = self.get_fitresult() 

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

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

382 self.show_results() 

383 

384 def onRemoveFromHistory(self, event=None): 

385 result = self.get_fitresult() 

386 if wx.ID_YES != Popup(self, 

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

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

389 return 

390 

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

392 self.nfit = 0 

393 self.show_results() 

394 

395 

396 def onSaveAllStats(self, evt=None): 

397 "Save Parameters and Statistics to CSV" 

398 # get first dataset to extract fit parameter names 

399 fnames = self.filelist.GetItems() 

400 if len(fnames) == 0: 

401 return 

402 

403 deffile = "PrePeaksResults.csv" 

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

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

406 default_file=deffile, wildcard=wcards) 

407 if path is None: 

408 return 

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

410 if wx.ID_YES != Popup(self, 

411 "Overwrite existing Statistics File?", 

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

413 return 

414 

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

416 res0 = ppeaks_tmpl.fit_history[0].result 

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

418 user_opts = ppeaks_tmpl.user_options 

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

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

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

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

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

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

425 '#--------------------'] 

426 

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

428 'n_varys', 'chi-square', 'reduced_chi-square', 

429 'akaike_info', 'bayesian_info'] 

430 

431 for pname in param_names: 

432 labels.append(pname) 

433 labels.append(pname+'_stderr') 

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

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

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

437 continue 

438 try: 

439 pkfit = dgroup.prepeaks.fit_history[0] 

440 except: 

441 continue 

442 result = pkfit.result 

443 label = dgroup.filename 

444 if len(label) < 25: 

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

446 dat = [label, dgroup.groupname, 

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

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

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

450 for pname in param_names: 

451 val = stderr = 0 

452 if pname in result.params: 

453 par = result.params[pname] 

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

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

456 dat.append(stderr) 

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

458 out.append('') 

459 

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

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

462 

463 

464 def onSaveFitResult(self, event=None): 

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

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

467 wildcard=ModelWcards) 

468 if sfile is not None: 

469 pkfit = self.get_fitresult() 

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

471 

472 def onExportFitResult(self, event=None): 

473 dgroup = self.datagroup 

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

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

476 

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

478 

479 pkfit = self.get_fitresult() 

480 result = pkfit.result 

481 if outfile is not None: 

482 i1, i2 = get_xlims(dgroup.xdat, 

483 pkfit.user_options['emin'], 

484 pkfit.user_options['emax']) 

485 x = dgroup.xdat[i1:i2] 

486 y = dgroup.ydat[i1:i2] 

487 yerr = None 

488 if hasattr(dgroup, 'yerr'): 

489 yerr = 1.0*dgroup.yerr 

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

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

492 else: 

493 yerr = yerr[i1:i2] 

494 

495 export_modelresult(result, filename=outfile, 

496 datafile=dgroup.filename, ydata=y, 

497 yerr=yerr, x=x) 

498 

499 

500 def get_fitresult(self, nfit=None): 

501 if nfit is None: 

502 nfit = self.nfit 

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

504 self.nfit = max(0, nfit) 

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

506 self.nfit = 0 

507 if len(self.peakfit_history) > 0: 

508 return self.peakfit_history[self.nfit] 

509 

510 def onPlot(self, event=None): 

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

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

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

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

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

516 self.peakframe.larch_eval(cmd) 

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

518 

519 def onSelectFit(self, evt=None): 

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

521 return 

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

523 if item > -1: 

524 self.show_fitresult(nfit=item) 

525 

526 def onSelectParameter(self, evt=None): 

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

528 return 

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

530 return 

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

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

533 

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

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

536 

537 result = self.get_fitresult() 

538 this = result.result.params[pname] 

539 if this.correl is not None: 

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

541 for name, corval in reversed(sort_correl): 

542 if abs(corval) > cormin: 

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

544 

545 def onAllCorrel(self, evt=None): 

546 result = self.get_fitresult() 

547 params = result.result.params 

548 parnames = list(params.keys()) 

549 

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

551 correls = {} 

552 for i, name in enumerate(parnames): 

553 par = params[name] 

554 if not par.vary: 

555 continue 

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

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

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

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

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

561 

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

563 sort_correl.reverse() 

564 

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

566 

567 for namepair, corval in sort_correl: 

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

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

570 

571 def onLoadModel(self, event=None): 

572 self.peakframe.use_modelresult(self.get_fitresult()) 

573 

574 def onCopyParams(self, evt=None): 

575 result = self.get_fitresult() 

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

577 

578 def ShowDataSet(self, evt=None): 

579 dataset = evt.GetString() 

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

581 if group is not None: 

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

583 

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

585 name = dgroup.filename 

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

587 self.filelist.Append(name) 

588 self.datasets[name] = dgroup 

589 if show: 

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

591 

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

593 if datagroup is not None: 

594 self.datagroup = datagroup 

595 if larch_eval is not None: 

596 self.larch_eval = larch_eval 

597 

598 datagroup = self.datagroup 

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

600 

601 # cur = self.get_fitresult() 

602 wids = self.wids 

603 wids['stats'].DeleteAllItems() 

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

605 args = [res.label] 

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

607 val = getattr(res.result, attr) 

608 if isinstance(val, int): 

609 val = '%d' % val 

610 else: 

611 val = gformat(val, 10) 

612 args.append(val) 

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

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

615 self.show_fitresult(nfit=0) 

616 

617 if show_plot: 

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

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

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

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

622 

623 self.peakframe.larch_eval(cmd) 

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

625 

626 def get_model_desc(self, model): 

627 model_repr = model._reprstring(long=True) 

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

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

630 words = [] 

631 mname, imodel = '', 0 

632 for word in model_repr.split(): 

633 if word.startswith('prefix'): 

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

635 else: 

636 mname = word 

637 if imodel > 0: 

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

639 words.append(delim) 

640 imodel += 1 

641 return ''.join(words) 

642 

643 

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

645 if datagroup is not None: 

646 self.datagroup = datagroup 

647 

648 result = self.get_fitresult(nfit=nfit) 

649 wids = self.wids 

650 try: 

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

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

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

654 valid_result = True 

655 except: 

656 valid_result = False 

657 

658 wids['params'].DeleteAllItems() 

659 wids['paramsdata'] = [] 

660 if valid_result: 

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

662 pname = param.name 

663 try: 

664 val = gformat(param.value, 10) 

665 except (TypeError, ValueError): 

666 val = ' ??? ' 

667 serr = ' N/A ' 

668 if param.stderr is not None: 

669 serr = gformat(param.stderr, 10) 

670 extra = ' ' 

671 if param.expr is not None: 

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

673 elif not param.vary: 

674 extra = '(fixed)' 

675 elif param.init_value is not None: 

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

677 

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

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

680 self.Refresh() 

681 

682class PrePeakPanel(TaskPanel): 

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

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

685 self.fit_components = {} 

686 self.user_added_params = None 

687 

688 self.pick2_timer = wx.Timer(self) 

689 self.pick2_group = None 

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

691 self.pick2_t0 = 0. 

692 self.pick2_timeout = 15. 

693 

694 self.pick2erase_timer = wx.Timer(self) 

695 self.pick2erase_panel = None 

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

697 

698 def onPanelExposed(self, **kws): 

699 # called when notebook is selected 

700 try: 

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

702 gname = self.controller.file_groups[fname] 

703 dgroup = self.controller.get_group(gname) 

704 self.ensure_xas_processed(dgroup) 

705 self.fill_form(dgroup) 

706 except: 

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

708 

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

710 if pkfit is not None: 

711 self.showresults_btn.Enable() 

712 self.use_modelresult(pkfit) 

713 

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

715 pass 

716 

717 def build_display(self): 

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

719 

720 self.wids = {} 

721 

722 fsopts = dict(digits=2, increment=0.1, min_val=-9999, 

723 max_val=9999, size=(125, -1), with_pin=True) 

724 

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

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

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

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

729 

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

731 action=self.onLoadFitResult, size=(150, -1)) 

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

733 action=self.onShowResults, size=(150, -1)) 

734 self.showresults_btn.Disable() 

735 

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

737 size=(150, -1)) 

738 

739 self.plotmodel_btn = Button(pan, 

740 'Plot Current Model', 

741 action=self.onPlotModel, size=(150, -1)) 

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

743 action=self.onFitModel, size=(150, -1)) 

744 self.fitmodel_btn.Disable() 

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

746 action=self.onFitSelected, size=(150, -1)) 

747 self.fitselected_btn.Disable() 

748 self.fitmodel_btn.Disable() 

749 

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

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

752 self.array_choice.SetSelection(0) 

753 

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

755 choices=BaselineFuncs) 

756 self.bline_choice.SetSelection(2) 

757 

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

759 choices=ModelChoices['peaks'], 

760 action=self.addModel) 

761 

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

763 choices=ModelChoices['other'], 

764 action=self.addModel) 

765 

766 self.models_peaks = models_peaks 

767 self.models_other = models_other 

768 

769 

770 self.message = SimpleText(pan, 

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

772 

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

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

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

776 

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

778 

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

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

781 

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

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

784 pan.Add(self.loadresults_btn) 

785 

786 add_text('Array to fit: ') 

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

788 pan.Add((5,5)) 

789 pan.Add(self.showresults_btn) 

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

791 # pan.Add(ppeak_e0) 

792 # pan.Add(self.show_e0) 

793 

794 add_text('Fit Energy Range: ') 

795 pan.Add(ppeak_emin) 

796 add_text(' : ', newrow=False) 

797 pan.Add(ppeak_emax) 

798 pan.Add(self.show_fitrange) 

799 

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

801 add_text( 'Baseline Form: ') 

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

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

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

805 pan.Add((10, 10)) 

806 pan.Add(self.fitbline_btn) 

807 

808 pan.Add(t, newrow=True) 

809 pan.Add(ppeak_elo) 

810 add_text(' : ', newrow=False) 

811 pan.Add(ppeak_ehi) 

812 

813 pan.Add(self.show_peakrange) 

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

815 

816 # add model 

817 ts = wx.BoxSizer(wx.HORIZONTAL) 

818 ts.Add(models_peaks) 

819 ts.Add(models_other) 

820 

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

822 pan.Add(ts, dcol=4) 

823 pan.Add(self.plotmodel_btn) 

824 

825 

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

827 pan.Add(self.fitmodel_btn) 

828 

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

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

831 pan.Add(self.fitselected_btn) 

832 

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

834 pan.pack() 

835 

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

837 self.mod_nb_init = True 

838 dummy_panel = wx.Panel(self.mod_nb) 

839 

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

841 sizer = wx.BoxSizer(wx.VERTICAL) 

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

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

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

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

846 

847 pack(self, sizer) 

848 

849 def get_config(self, dgroup=None): 

850 """get processing configuration for a group""" 

851 if dgroup is None: 

852 dgroup = self.controller.get_group() 

853 

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

855 if 'e0' not in conf: 

856 conf = self.controller.get_defaultcfonfig() 

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

858 

859 dgroup.prepeak_config = conf 

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

861 dgroup.prepeaks = Group() 

862 

863 return conf 

864 

865 def fill_form(self, dat): 

866 if isinstance(dat, Group): 

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

868 self.xasmain.process_normalization(dat) 

869 if hasattr(dat, 'prepeaks'): 

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

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

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

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

874 elif isinstance(dat, dict): 

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

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

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

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

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

880 

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

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

883 

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

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

886 

887 def read_form(self): 

888 "read for, returning dict of values" 

889 dgroup = self.controller.get_group() 

890 array_desc = self.array_choice.GetStringSelection() 

891 bline_form = self.bline_choice.GetStringSelection() 

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

893 'filename': dgroup.filename, 

894 'array_desc': array_desc.lower(), 

895 'array_name': PrePeak_ArrayChoices[array_desc], 

896 'baseline_form': bline_form.lower(), 

897 'bkg_components': []} 

898 

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

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

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

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

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

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

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

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

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

908 return form_opts 

909 

910 def onFitBaseline(self, evt=None): 

911 opts = self.read_form() 

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

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

914 return 

915 cmd = """{gname:s}.ydat = 1.0*{gname:s}.{array_name:s} 

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

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

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

919 

920 dgroup = self.controller.get_group() 

921 ppeaks = dgroup.prepeaks 

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

923 ppeaks.delta_centroid) 

924 

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

926 

927 if '+' in bline_form: 

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

929 else: 

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

931 

932 poly_model = peak_model = None 

933 for bform in bforms: 

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

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

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

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

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

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

940 

941 if peak_model is not None: 

942 if 'bpeak_' in self.fit_components: 

943 self.onDeleteComponent(prefix='bpeak_') 

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

945 

946 if poly_model is not None: 

947 if 'bpoly_' in self.fit_components: 

948 self.onDeleteComponent(prefix='bpoly_') 

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

950 

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

952 cmp = self.fit_components[prefix] 

953 # cmp.bkgbox.SetValue(1) 

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

955 

956 self.fill_form(dgroup) 

957 self.fitmodel_btn.Enable() 

958 self.fitselected_btn.Enable() 

959 

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

961 

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

963 

964 self.onPlot(baseline_only=True) 

965 # self.savebline_btn.Enable() 

966 

967 def onSaveBaseline(self, evt=None): 

968 opts = self.read_form() 

969 

970 dgroup = self.controller.get_group() 

971 ppeaks = dgroup.prepeaks 

972 

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

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

975 wildcard=DataWcards) 

976 if sfile is None: 

977 return 

978 opts['savefile'] = sfile 

979 opts['centroid'] = ppeaks.centroid 

980 opts['delta_centroid'] = ppeaks.delta_centroid 

981 

982 cmd = """# save baseline script: 

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

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

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

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

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

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

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

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

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

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

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

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

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

996 """ 

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

998 

999 

1000 def fill_model_params(self, prefix, params): 

1001 comp = self.fit_components[prefix] 

1002 parwids = comp.parwids 

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

1004 pname = prefix + pname 

1005 if pname in parwids: 

1006 wids = parwids[pname] 

1007 if wids.minval is not None: 

1008 wids.minval.SetValue(par.min) 

1009 if wids.maxval is not None: 

1010 wids.maxval.SetValue(par.max) 

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

1012 if par.expr is not None: 

1013 varstr = 'constrain' 

1014 if wids.vary is not None: 

1015 wids.vary.SetStringSelection(varstr) 

1016 wids.value.SetValue(par.value) 

1017 

1018 def onPlotModel(self, evt=None): 

1019 dgroup = self.controller.get_group() 

1020 g = self.build_fitmodel(dgroup.groupname) 

1021 self.onPlot(show_init=True) 

1022 

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

1024 opts = self.read_form() 

1025 dgroup = self.controller.get_group() 

1026 opts['group'] = opts['gname'] 

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

1028 

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

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

1031 emax=opts['emax']) 

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

1033 

1034 cmd = "plot_prepeaks_fit" 

1035 args = ['{gname}'] 

1036 if baseline_only: 

1037 cmd = "plot_prepeaks_baseline" 

1038 else: 

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

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

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

1042 self.controller.set_focus() 

1043 

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

1045 if model is None and event is not None: 

1046 model = event.GetString() 

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

1048 return 

1049 

1050 self.models_peaks.SetSelection(0) 

1051 self.models_other.SetSelection(0) 

1052 

1053 if prefix is None: 

1054 p = model[:5].lower() 

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

1056 for comp in self.fit_components: 

1057 if comp in curmodels: 

1058 curmodels.remove(comp) 

1059 

1060 prefix = curmodels[0] 

1061 

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

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

1064 title = prefix[:-1] 

1065 mclass_kws = {'prefix': prefix} 

1066 if 'step' in model.lower(): 

1067 form = model.lower().replace('step', '').strip() 

1068 if form.startswith('err'): 

1069 form = 'erf' 

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

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

1072 mclass = lm_models.StepModel 

1073 mclass_kws['form'] = form 

1074 minst = mclass(form=form, prefix=prefix) 

1075 else: 

1076 if model in ModelFuncs: 

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

1078 else: 

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

1080 

1081 minst = mclass(prefix=prefix) 

1082 

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

1084 panel.SetFont(Font(FONTSIZE)) 

1085 

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

1087 return SimpleText(panel, label, 

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

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

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

1091 if isbkg: 

1092 bkgbox.SetValue(1) 

1093 

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

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

1096 

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

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

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

1100 

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

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

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

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

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

1106 

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

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

1109 panel.Add(usebox, dcol=2) 

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

1111 

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

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

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

1115 

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

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

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

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

1120 

1121 parwids = {} 

1122 parnames = sorted(minst.param_names) 

1123 

1124 for a in minst._func_allargs: 

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

1126 if (pname not in parnames and 

1127 a in minst.param_hints and 

1128 a not in minst.independent_vars): 

1129 parnames.append(pname) 

1130 

1131 for pname in parnames: 

1132 sname = pname[len(prefix):] 

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

1134 

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

1136 if 'min' in hints: 

1137 par.min = hints['min'] 

1138 if 'max' in hints: 

1139 par.max = hints['max'] 

1140 if 'value' in hints: 

1141 par.value = hints['value'] 

1142 if 'expr' in hints: 

1143 par.expr = hints['expr'] 

1144 

1145 pwids = ParameterWidgets(panel, par, name_size=100, expr_size=150, 

1146 float_size=80, prefix=prefix, 

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

1148 'maxval', 'vary', 'expr')) 

1149 parwids[par.name] = pwids 

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

1151 

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

1153 pwids.minval, pwids.maxval, pwids.expr)) 

1154 

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

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

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

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

1159 pwids = ParameterWidgets(panel, par, name_size=100, expr_size=400, 

1160 float_size=80, prefix=prefix, 

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

1162 parwids[par.name] = pwids 

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

1164 panel.Add(pwids.value) 

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

1166 pwids.value.Disable() 

1167 

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

1169 mclass_kws=mclass_kws, usebox=usebox, panel=panel, 

1170 parwids=parwids, float_size=65, expr_size=150, 

1171 pick2_msg=pick2msg, bkgbox=bkgbox) 

1172 

1173 

1174 self.fit_components[prefix] = fgroup 

1175 panel.pack() 

1176 if self.mod_nb_init: 

1177 self.mod_nb.DeletePage(0) 

1178 self.mod_nb_init = False 

1179 

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

1181 sx,sy = self.GetSize() 

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

1183 self.SetSize((sx, sy)) 

1184 self.fitmodel_btn.Enable() 

1185 self.fitselected_btn.Enable() 

1186 

1187 

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

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

1190 if fgroup is None: 

1191 return 

1192 

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

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

1195 self.mod_nb.DeletePage(i) 

1196 

1197 for attr in dir(fgroup): 

1198 setattr(fgroup, attr, None) 

1199 

1200 self.fit_components.pop(prefix) 

1201 if len(self.fit_components) < 1: 

1202 self.fitmodel_btn.Disable() 

1203 self.fitselected_btn.Enable() 

1204 

1205 def onPick2EraseTimer(self, evt=None): 

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

1207 self.pick2erase_timer.Stop() 

1208 panel = self.pick2erase_panel 

1209 ntrace = panel.conf.ntrace - 1 

1210 trace = panel.conf.get_mpl_line(ntrace) 

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

1212 panel.conf.ntrace = ntrace 

1213 panel.draw() 

1214 

1215 def onPick2Timer(self, evt=None): 

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

1217 for a model from the selected data range 

1218 """ 

1219 try: 

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

1221 curhist = plotframe.cursor_hist[:] 

1222 plotframe.Raise() 

1223 except: 

1224 return 

1225 

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

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

1228 plotframe.cursor_hist = [] 

1229 self.pick2_timer.Stop() 

1230 return 

1231 

1232 if len(curhist) < 2: 

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

1234 return 

1235 

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

1237 self.pick2_timer.Stop() 

1238 

1239 # guess param values 

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

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

1242 

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

1244 x, y = dgroup.xdat, dgroup.ydat 

1245 i0 = index_of(dgroup.xdat, xmin) 

1246 i1 = index_of(dgroup.xdat, xmax) 

1247 x, y = dgroup.xdat[i0:i1+1], dgroup.ydat[i0:i1+1] 

1248 

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

1250 parwids = self.pick2_group.parwids 

1251 try: 

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

1253 except: 

1254 return 

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

1256 if 'amplitude' in name: 

1257 param.value *= 1.5 

1258 elif 'sigma' in name: 

1259 param.value *= 0.75 

1260 if name in parwids: 

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

1262 

1263 dgroup._tmp = mod.eval(guesses, x=dgroup.xdat) 

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

1265 plotframe.cursor_hist = [] 

1266 plotframe.oplot(dgroup.xdat, dgroup._tmp) 

1267 self.pick2erase_panel = plotframe.panel 

1268 

1269 self.pick2erase_timer.Start(60000) 

1270 

1271 

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

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

1274 if fgroup is None: 

1275 return 

1276 

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

1278 plotframe.Raise() 

1279 

1280 plotframe.cursor_hist = [] 

1281 fgroup.npts = 0 

1282 self.pick2_group = fgroup 

1283 

1284 if fgroup.pick2_msg is not None: 

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

1286 

1287 self.pick2_t0 = time.time() 

1288 self.pick2_timer.Start(1000) 

1289 

1290 

1291 def onLoadFitResult(self, event=None): 

1292 dlg = wx.FileDialog(self, message="Load Saved Pre-edge Model", 

1293 wildcard=ModelWcards, style=wx.FD_OPEN) 

1294 rfile = None 

1295 if dlg.ShowModal() == wx.ID_OK: 

1296 rfile = dlg.GetPath() 

1297 dlg.Destroy() 

1298 

1299 if rfile is None: 

1300 return 

1301 

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

1303 dat = read_groups(str(rfile)) 

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

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

1306 "Invalid file") 

1307 

1308 self.use_modelresult(dat[1]) 

1309 

1310 def use_modelresult(self, pkfit): 

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

1312 self.onDeleteComponent(prefix=prefix) 

1313 

1314 result = pkfit.result 

1315 bkg_comps = pkfit.user_options['bkg_components'] 

1316 for comp in result.model.components: 

1317 isbkg = comp.prefix in bkg_comps 

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

1319 prefix=comp.prefix, isbkg=isbkg) 

1320 

1321 for comp in result.model.components: 

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

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

1324 if pname in parwids: 

1325 wids = parwids[pname] 

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

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

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

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

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

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

1332 

1333 self.fill_form(pkfit.user_options) 

1334 

1335 

1336 def get_xranges(self, x): 

1337 opts = self.read_form() 

1338 dgroup = self.controller.get_group() 

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

1340 

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

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

1343 return i1, i2 

1344 

1345 def build_fitmodel(self, groupname=None): 

1346 """ use fit components to build model""" 

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

1348 peaks = [] 

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

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

1351 modop = " =" 

1352 opts = self.read_form() 

1353 if groupname is None: 

1354 groupname = opts['gname'] 

1355 

1356 opts['group'] = groupname 

1357 dgroup = self.controller.get_group(groupname) 

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

1359 

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

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

1362 emax=opts['emax']) 

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

1364 

1365 

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

1367 _cen, _amp = None, None 

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

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

1370 this = parwids.param 

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

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

1373 if this.expr is not None: 

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

1375 elif not this.vary: 

1376 pargs.pop() 

1377 pargs.pop() 

1378 pargs.append("vary=False") 

1379 

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

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

1382 _cen = this.name 

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

1384 _amp = this.name 

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

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

1387 ', '.join(compargs))) 

1388 

1389 modop = "+=" 

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

1391 peaks.append((_amp, _cen)) 

1392 

1393 if len(peaks) > 0: 

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

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

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

1397 

1398 cmds.extend(modcmds) 

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

1400 user_opts=repr(opts))) 

1401 

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

1403 

1404 def onFitSelected(self, event=None): 

1405 dgroup = self.controller.get_group() 

1406 if dgroup is None: 

1407 return 

1408 

1409 opts = self.read_form() 

1410 

1411 self.show_subframe('prepeak_result', PrePeakFitResultFrame, 

1412 datagroup=dgroup, peakframe=self) 

1413 

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

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

1416 ngroups = len(groups) 

1417 for igroup, gname in enumerate(groups): 

1418 dgroup = self.controller.get_group(gname) 

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

1420 self.xasmain.process_normalization(dgroup) 

1421 self.build_fitmodel(gname) 

1422 opts['group'] = opts['gname'] 

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

1424 

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

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

1427 emax=opts['emax']) 

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

1429 ppeaks = dgroup.prepeaks 

1430 

1431 # add bkg_component to saved user options 

1432 bkg_comps = [] 

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

1434 if comp.bkgbox.IsChecked(): 

1435 bkg_comps.append(label) 

1436 

1437 opts['bkg_components'] = bkg_comps 

1438 imin, imax = self.get_xranges(dgroup.xdat) 

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

1440 

1441 yerr_type = 'set_yerr_const' 

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

1443 if yerr is None: 

1444 if hasattr(dgroup, 'norm_std'): 

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

1446 yerr_type = 'set_yerr_array' 

1447 elif hasattr(dgroup, 'mu_std'): 

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

1449 yerr_type = 'set_yerr_array' 

1450 else: 

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

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

1453 yerr_type = 'set_yerr_array' 

1454 

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

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

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

1458 imin=imin, imax=imax, 

1459 user_opts=repr(opts))) 

1460 

1461 pkfit = self.larch_get("peakresult") 

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

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

1464 jnl.update(pkfit.user_options) 

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

1466 if igroup == 0: 

1467 self.autosave_modelresult(pkfit) 

1468 

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

1470 larch_eval=self.larch_eval, 

1471 show=igroup==ngroups-1) 

1472 

1473 def onFitModel(self, event=None): 

1474 dgroup = self.controller.get_group() 

1475 if dgroup is None: 

1476 return 

1477 self.build_fitmodel(dgroup.groupname) 

1478 opts = self.read_form() 

1479 

1480 dgroup = self.controller.get_group() 

1481 opts['group'] = opts['gname'] 

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

1483 

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

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

1486 emax=opts['emax']) 

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

1488 

1489 ppeaks = dgroup.prepeaks 

1490 

1491 # add bkg_component to saved user options 

1492 bkg_comps = [] 

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

1494 if comp.bkgbox.IsChecked(): 

1495 bkg_comps.append(label) 

1496 opts['bkg_components'] = bkg_comps 

1497 

1498 imin, imax = self.get_xranges(dgroup.xdat) 

1499 

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

1501 yerr_type = 'set_yerr_const' 

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

1503 if yerr is None: 

1504 if hasattr(dgroup, 'norm_std'): 

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

1506 yerr_type = 'set_yerr_array' 

1507 elif hasattr(dgroup, 'mu_std'): 

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

1509 yerr_type = 'set_yerr_array' 

1510 else: 

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

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

1513 yerr_type = 'set_yerr_array' 

1514 

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

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

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

1518 imin=imin, imax=imax, 

1519 user_opts=repr(opts))) 

1520 

1521 # journal about peakresult 

1522 pkfit = self.larch_get("peakresult") 

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

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

1525 jnl.update(pkfit.user_options) 

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

1527 

1528 self.autosave_modelresult(pkfit) 

1529 self.onPlot() 

1530 self.showresults_btn.Enable() 

1531 

1532 

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

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

1535 larch_eval=self.larch_eval) 

1536 

1537 def onShowResults(self, event=None): 

1538 self.show_subframe('prepeak_result', PrePeakFitResultFrame, 

1539 peakframe=self) 

1540 

1541 

1542 def update_start_values(self, params): 

1543 """fill parameters with best fit values""" 

1544 allparwids = {} 

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

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

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

1548 allparwids[name] = parwids 

1549 

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

1551 if pname in allparwids: 

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

1553 

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

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

1556 confdir = self.controller.larix_folder 

1557 if fname is None: 

1558 fname = 'autosave_peakfile.modl' 

1559 save_groups(os.path.join(confdir, fname), ['#peakfit 1.0', result])