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
« 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')
7from functools import partial
8import json
10import wx
11import wx.lib.scrolledpanel as scrolled
13import wx.dataview as dv
15from lmfit import Parameter
16import lmfit.models as lm_models
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
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)
31from larch.wxlib.parameter import ParameterWidgets
32from larch.wxlib.plotter import last_cursor_pos
33from .taskpanel import TaskPanel
34from .config import PrePeak_ArrayChoices, PlotWindowChoices
36DVSTYLE = dv.DV_SINGLE|dv.DV_VERT_RULES|dv.DV_ROW_LINES
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 }
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'}
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'}
96BaselineFuncs = ['No Baseline',
97 'Constant+Lorentzian',
98 'Linear+Lorentzian',
99 'Constant+Gaussian',
100 'Linear+Gaussian',
101 'Constant+Voigt',
102 'Linear+Voigt',
103 'Quadratic', 'Linear']
106PLOT_BASELINE = 'Data+Baseline'
107PLOT_FIT = 'Data+Fit'
108PLOT_INIT = 'Data+Init Fit'
109PLOT_RESID = 'Data+Residual'
110PlotChoices = [PLOT_BASELINE, PLOT_FIT, PLOT_RESID]
112FitMethods = ("Levenberg-Marquardt", "Nelder-Mead", "Powell")
113ModelWcards = "Fit Models(*.modl)|*.modl|All files (*.*)|*.*"
114DataWcards = "Data Files(*.dat)|*.dat|All files (*.*)|*.*"
117MIN_CORREL = 0.10
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"""
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"""
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"""
142COMMANDS['dofit'] = """# do fit
143peakresult = prepeaks_fit({group}, peakmodel, peakpars)
144peakresult.user_options = {user_opts:s}
145"""
148def get_model_abbrev(modelname):
149 if modelname in ModelAbbrevs:
150 return ModelAbbrevs[modelname]
151 return fix_varname(modelname).lower()
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
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
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()
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)
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)
198 MenuItem(self, fmenu, "Save Fit and Components for Current Fit",
199 "Save Arrays and Results to Text File",
200 self.onExportFitResult)
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)
209 def build(self):
210 sizer = wx.GridBagSizer(3, 3)
211 sizer.SetVGap(3)
212 sizer.SetHGap(3)
214 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
215 splitter.SetMinimumPaneSize(200)
217 self.filelist = EditableListBox(splitter, self.ShowDataSet,
218 size=(250, -1))
219 set_color(self.filelist, 'list_fg', bg='list_bg')
221 panel = scrolled.ScrolledPanel(splitter)
223 panel.SetMinSize((775, 575))
224 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.NORMAL)
226 # title row
227 self.wids = wids = {}
228 title = SimpleText(panel, 'Fit Results', font=Font(FONTSIZE+2),
229 colour=COLORS['title'], style=LEFT)
231 wids['data_title'] = SimpleText(panel, '< > ', font=Font(FONTSIZE+2),
232 minsize=(350, -1),
233 colour=COLORS['title'], style=LEFT)
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')
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)
249 pack(ppanel, psizer)
251 wids['load_model'] = Button(panel, 'Load this Model for Fitting',
252 size=(250, -1), action=self.onLoadModel)
254 wids['plot_choice'] = Button(panel, 'Plot This Fit',
255 size=(125, -1), action=self.onPlot)
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)
265 irow = 0
266 sizer.Add(title, (irow, 0), (1, 1), LEFT)
267 sizer.Add(wids['data_title'], (irow, 1), (1, 3), LEFT)
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)
274 irow += 1
275 sizer.Add(wids['load_model'],(irow, 0), (1, 2), LEFT)
277 irow += 1
278 sizer.Add(wids['plot_choice'],(irow, 0), (1, 1), LEFT)
279 sizer.Add(ppanel, (irow, 1), (1, 4), LEFT)
282 irow += 1
283 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT)
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)
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)
297 sizer.Add(title, (irow, 0), (1, 1), LEFT)
298 sizer.Add(subtitle, (irow, 1), (1, 1), LEFT)
300 sview = self.wids['stats'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
302 sview.SetFont(self.font_fixedwidth)
304 xw = (175, 85, 85, 130, 130, 130)
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])
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
321 sview.SetMinSize((750, 150))
323 irow += 1
324 sizer.Add(sview, (irow, 0), (1, 5), LEFT)
326 irow += 1
327 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT)
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)
334 self.wids['copy_params'] = Button(panel, 'Update Model with these values',
335 size=(250, -1), action=self.onCopyParams)
337 sizer.Add(self.wids['copy_params'], (irow, 1), (1, 3), LEFT)
339 pview = self.wids['params'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
340 pview.SetFont(self.font_fixedwidth)
341 self.wids['paramsdata'] = []
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])
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
355 pview.SetMinSize((750, 200))
356 pview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectParameter)
358 irow += 1
359 sizer.Add(pview, (irow, 0), (1, 5), LEFT)
361 irow += 1
362 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT)
364 irow += 1
365 title = SimpleText(panel, '[[Correlations]]', font=Font(FONTSIZE+2),
366 colour=COLORS['title'], style=LEFT)
368 self.wids['all_correl'] = Button(panel, 'Show All',
369 size=(100, -1), action=self.onAllCorrel)
371 self.wids['min_correl'] = FloatSpin(panel, value=MIN_CORREL,
372 min_val=0, size=(100, -1),
373 digits=3, increment=0.1)
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)
381 cview = self.wids['correl'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
382 cview.SetFont(self.font_fixedwidth)
385 cview.AppendTextColumn('Parameter 1', width=150)
386 cview.AppendTextColumn('Parameter 2', width=150)
387 cview.AppendTextColumn('Correlation', width=150)
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))
398 irow += 1
399 sizer.Add(cview, (irow, 0), (1, 5), LEFT)
401 pack(panel, sizer)
402 panel.SetupScrolling()
404 splitter.SplitVertically(self.filelist, panel, 1)
406 mainsizer = wx.BoxSizer(wx.VERTICAL)
407 mainsizer.Add(splitter, 1, wx.GROW|wx.ALL, 5)
409 pack(self, mainsizer)
410 self.Show()
411 self.Raise()
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()
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
426 self.datagroup.prepeaks.fit_history.pop(self.nfit)
427 self.nfit = 0
428 self.show_results()
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
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
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 '#--------------------']
462 labels = [('Data Set' + ' '*25)[:25], 'Group name', 'n_data',
463 'n_varys', 'chi-square', 'reduced_chi-square',
464 'akaike_info', 'bayesian_info']
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('')
495 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
496 fh.write('\n'.join(out))
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])
506 def onExportFitResult(self, event=None):
507 dgroup = self.datagroup
508 deffile = dgroup.filename.replace('.', '_') + '.xdi'
509 wcards = 'All files (*.*)|*.*'
511 outfile = FileSave(self, 'Export Fit Result', default_file=deffile)
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]
529 export_modelresult(result, filename=outfile,
530 datafile=dgroup.filename, ydata=y,
531 yerr=yerr, x=x)
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]
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)
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)
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]
568 cormin= self.wids['min_correl'].GetValue()
569 self.wids['correl'].DeleteAllItems()
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))
579 def onAllCorrel(self, evt=None):
580 result = self.get_fitresult()
581 params = result.result.params
582 parnames = list(params.keys())
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]
596 sort_correl = sorted(correls.items(), key=lambda it: abs(it[1]))
597 sort_correl.reverse()
599 self.wids['correl'].DeleteAllItems()
601 for namepair, corval in sort_correl:
602 name1, name2 = namepair.split('$$')
603 self.wids['correl'].AppendItem((name1, name2, "% .4f" % corval))
605 def onLoadModel(self, event=None):
606 self.peakframe.use_modelresult(self.get_fitresult())
608 def onCopyParams(self, evt=None):
609 result = self.get_fitresult()
610 self.peakframe.update_start_values(result.result.params)
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)
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)
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
632 datagroup = self.datagroup
633 self.peakfit_history = getattr(self.datagroup.prepeaks, 'fit_history', [])
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)
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)
657 self.peakframe.larch_eval(cmd)
658 self.peakframe.controller.set_focus(topwin=self)
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)
678 def show_fitresult(self, nfit=0, datagroup=None):
679 if datagroup is not None:
680 self.datagroup = datagroup
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
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)
712 wids['params'].AppendItem((pname, val, serr, extra))
713 wids['paramsdata'].append(pname)
714 self.Refresh()
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
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.
728 self.pick2erase_timer = wx.Timer(self)
729 self.pick2erase_panel = None
730 self.Bind(wx.EVT_TIMER, self.onPick2EraseTimer, self.pick2erase_timer)
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 ")
743 pkfit = getattr(self.larch.symtable, 'peakresult', None)
744 if pkfit is not None:
745 self.showresults_btn.Enable()
746 self.use_modelresult(pkfit)
748 def onModelPanelExposed(self, event=None, **kws):
749 pass
751 def build_display(self):
752 pan = self.panel # = GridPanel(self, ncols=4, nrows=4, pad=2, itemstyle=LEFT)
754 self.wids = {}
756 fsopts = dict(digits=2, increment=0.1, min_val=-999999,
757 max_val=999999, size=(125, -1), with_pin=True)
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)
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()
770 self.fitbline_btn = Button(pan,'Fit Baseline', action=self.onFitBaseline,
771 size=(165, -1))
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()
784 self.array_choice = Choice(pan, size=(200, -1),
785 choices=list(PrePeak_ArrayChoices.keys()))
786 self.array_choice.SetSelection(0)
788 self.bline_choice = Choice(pan, size=(200, -1),
789 choices=BaselineFuncs)
790 self.bline_choice.SetSelection(2)
792 models_peaks = Choice(pan, size=(200, -1),
793 choices=ModelChoices['peaks'],
794 action=self.addModel)
796 models_other = Choice(pan, size=(200, -1),
797 choices=ModelChoices['other'],
798 action=self.addModel)
800 self.models_peaks = models_peaks
801 self.models_other = models_other
804 self.message = SimpleText(pan,
805 'first fit baseline, then add peaks to fit model.')
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)
811 opts = dict(default=False, size=(200, -1), action=self.onPlot)
813 def add_text(text, dcol=1, newrow=True):
814 pan.Add(SimpleText(pan, text), dcol=dcol, newrow=newrow)
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)
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)
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)
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)
842 pan.Add(t, newrow=True)
843 pan.Add(ppeak_elo)
844 add_text(' : ', newrow=False)
845 pan.Add(ppeak_ehi)
847 pan.Add(self.show_peakrange)
848 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True)
850 # add model
851 ts = wx.BoxSizer(wx.HORIZONTAL)
852 ts.Add(models_peaks)
853 ts.Add(models_other)
855 pan.Add(SimpleText(pan, 'Add Component: '), newrow=True)
856 pan.Add(ts, dcol=4)
857 pan.Add(self.plotmodel_btn)
860 pan.Add(SimpleText(pan, 'Fit Model to Current Group : '), dcol=5, newrow=True)
861 pan.Add(self.fitmodel_btn)
863 pan.Add(SimpleText(pan, 'Messages: '), newrow=True)
864 pan.Add(self.message, dcol=4)
865 pan.Add(self.fitselected_btn)
867 pan.Add(HLine(pan, size=(600, 2)), dcol=6, newrow=True)
868 pan.pack()
870 self.mod_nb = flatnotebook(self, {}, on_change=self.onModelPanelExposed)
871 self.mod_nb_init = True
872 dummy_panel = wx.Panel(self.mod_nb)
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)
881 pack(self, sizer)
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()
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)
893 dgroup.prepeak_config = conf
894 if not hasattr(dgroup, 'prepeaks'):
895 dgroup.prepeaks = Group()
897 return conf
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'])
915 self.array_choice.SetStringSelection(dat['array_desc'])
916 self.bline_choice.SetStringSelection(dat['baseline_form'])
918 self.show_fitrange.Enable(dat['show_fitrange'])
919 self.show_peakrange.Enable(dat['show_peakrange'])
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': []}
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
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))
954 dgroup = self.controller.get_group()
955 ppeaks = dgroup.prepeaks
956 dgroup.centroid_msg = "%.4f +/- %.4f eV" % (ppeaks.centroid,
957 ppeaks.delta_centroid)
959 self.message.SetLabel("Centroid= %s" % dgroup.centroid_msg)
961 if '+' in bline_form:
962 bforms = [f.lower() for f in bline_form.split('+')]
963 else:
964 bforms = [bline_form.lower(), '']
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'
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)
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)
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)
990 self.fill_form(dgroup)
991 self.fitmodel_btn.Enable()
992 self.fitselected_btn.Enable()
994 i1, i2 = self.get_xranges(dgroup.energy)
996 dgroup.yfit = dgroup.xfit = 0.0*dgroup.energy[i1:i2]
998 self.onPlot(baseline_only=True)
999 # self.savebline_btn.Enable()
1001 def onSaveBaseline(self, evt=None):
1002 opts = self.read_form()
1004 dgroup = self.controller.get_group()
1005 ppeaks = dgroup.prepeaks
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
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
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))
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)
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)
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))
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)
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()
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
1085 self.models_peaks.SetSelection(0)
1086 self.models_other.SetSelection(0)
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)
1095 prefix = curmodels[0]
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')
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')
1124 minst = mclass(prefix=prefix)
1126 panel = GridPanel(self.mod_nb, ncols=2, nrows=5, pad=1, itemstyle=CEN)
1127 panel.SetFont(Font(FONTSIZE))
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)
1137 delbtn = Button(panel, 'Delete This Component', size=(200, -1),
1138 action=partial(self.onDeleteComponent, prefix=prefix))
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))
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')
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)
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)
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")))
1164 parwids = {}
1165 parnames = sorted(minst.param_names)
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)
1174 for pname in parnames:
1175 sname = pname[len(prefix):]
1176 hints = minst.param_hints.get(sname, {})
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']
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)
1196 panel.AddMany((pwids.value, pwids.vary, pwids.bounds,
1197 pwids.minval, pwids.maxval, pwids.expr))
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()
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)
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
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()
1233 def onDeleteComponent(self, evt=None, prefix=None):
1234 fgroup = self.fit_components.get(prefix, None)
1235 if fgroup is None:
1236 return
1238 for i in range(self.mod_nb.GetPageCount()):
1239 if fgroup.title == self.mod_nb.GetPageText(i):
1240 self.mod_nb.DeletePage(i)
1242 for attr in dir(fgroup):
1243 setattr(fgroup, attr, None)
1245 self.fit_components.pop(prefix)
1246 if len(self.fit_components) < 1:
1247 self.fitmodel_btn.Disable()
1248 self.fitselected_btn.Enable()
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()
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
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
1277 if len(curhist) < 2:
1278 self.pick2_group.pick2_msg.SetLabel("%i/2" % (len(curhist)))
1279 return
1281 self.pick2_group.pick2_msg.SetLabel("done.")
1282 self.pick2_timer.Stop()
1284 # guess param values
1285 xcur = (curhist[0][0], curhist[1][0])
1286 xmin, xmax = min(xcur), max(xcur)
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]
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)
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
1313 self.pick2erase_timer.Start(60000)
1316 def onPick2Points(self, evt=None, prefix=None):
1317 fgroup = self.fit_components.get(prefix, None)
1318 if fgroup is None:
1319 return
1321 plotframe = self.controller.get_display(win=1)
1322 plotframe.Raise()
1324 plotframe.cursor_hist = []
1325 fgroup.npts = 0
1326 self.pick2_group = fgroup
1328 if fgroup.pick2_msg is not None:
1329 fgroup.pick2_msg.SetLabel("0/2")
1331 self.pick2_t0 = time.time()
1332 self.pick2_timer.Start(1000)
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
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")
1347 self.use_modelresult(dat[1])
1349 def use_modelresult(self, pkfit):
1350 for prefix in list(self.fit_components.keys()):
1351 self.onDeleteComponent(prefix=prefix)
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)
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)
1373 self.fill_form(pkfit.user_options)
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.
1381 i1 = index_of(x, opts['emin'] + en_eps)
1382 i2 = index_of(x, opts['emax'] + en_eps) + 1
1383 return i1, i2
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']
1396 opts['group'] = groupname
1397 dgroup = self.controller.get_group(groupname)
1398 self.larch_eval(COMMANDS['prepeaks_setup'].format(**opts))
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)
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")
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)))
1429 modop = "+="
1430 if not comp.bkgbox.IsChecked() and _cen is not None and _amp is not None:
1431 peaks.append((_amp, _cen))
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))
1438 cmds.extend(modcmds)
1439 cmds.append(COMMANDS['prepfit'].format(group=dgroup.groupname,
1440 user_opts=repr(opts)))
1442 self.larch_eval("\n".join(cmds))
1444 def onFitSelected(self, event=None):
1445 dgroup = self.controller.get_group()
1446 if dgroup is None:
1447 return
1449 opts = self.read_form()
1451 self.show_subframe('prepeak_result', PrePeakFitResultFrame,
1452 datagroup=dgroup, peakframe=self)
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))
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
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)
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) ]
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'
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)))
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)
1509 self.subframes['prepeak_result'].add_results(dgroup, form=opts,
1510 larch_eval=self.larch_eval,
1511 show=igroup==ngroups-1)
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()
1520 dgroup = self.controller.get_group()
1521 opts['group'] = opts['gname']
1522 self.larch_eval(COMMANDS['prepeaks_setup'].format(**opts))
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)
1529 ppeaks = dgroup.prepeaks
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
1538 imin, imax = self.get_xranges(dgroup.xplot)
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'
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)))
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)
1568 self.autosave_modelresult(pkfit)
1569 self.onPlot()
1570 self.showresults_btn.Enable()
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)
1577 def onShowResults(self, event=None):
1578 self.show_subframe('prepeak_result', PrePeakFitResultFrame,
1579 peakframe=self)
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
1590 for pname, par in params.items():
1591 if pname in allparwids:
1592 allparwids[pname].value.SetValue(par.value)
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])