Coverage for larch/wxxas/xas_dialogs.py: 10%
1403 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 sys
2import copy
3import time
4from collections import namedtuple
5from functools import partial
6from pathlib import Path
7import numpy as np
8from lmfit import Parameters, minimize, fit_report
9from matplotlib.ticker import FuncFormatter
11import wx
12from wxmplot import PlotPanel
13from xraydb import guess_edge
14from larch import Group, isgroup
15from larch.math import index_of, index_nearest, interp
16from larch.utils.strutils import file2groupname, unique_name
17from larch.utils import path_split
19from larch.wxlib import (GridPanel, BitmapButton, FloatCtrl, FloatSpin,
20 set_color, FloatSpinWithPin, get_icon, SimpleText,
21 Choice, SetTip, Check, Button, HLine, OkCancel,
22 LEFT, pack, plotlabels, ReportFrame, DictFrame,
23 FileCheckList, Font, FONTSIZE, plotlabels,
24 get_zoomlimits, set_zoomlimits)
26from larch.xafs import etok, ktoe, find_energy_step
27from larch.utils.physical_constants import PI, DEG2RAD, PLANCK_HC, ATOM_SYMS
28from larch.math import smooth
30Plot_Choices = {'Normalized': 'norm', 'Derivative': 'dmude'}
33EDGE_LIST = ('K', 'L3', 'L2', 'L1', 'M5', 'M4', 'M3')
35NORM_MU = 'Normalized \u03BC(E)'
37DEGLITCH_PLOTS = {'Raw \u03BC(E)': 'mu',
38 NORM_MU: 'norm',
39 '\u03c7(E)': 'chie',
40 '\u03c7(E)*(E-E_0)': 'chiew'}
42SESSION_PLOTS = {'Normalized \u03BC(E)': 'norm',
43 'Raw \u03BC(E)': 'mu',
44 'k^2\u03c7(k)': 'chikw'}
47def ensure_en_orig(dgroup):
48 if not hasattr(dgroup, 'energy_orig'):
49 dgroup.energy_orig = dgroup.energy[:]
53def fit_dialog_window(dialog, panel):
54 sizer = wx.BoxSizer(wx.VERTICAL)
55 sizer.Add(panel, 1, LEFT, 5)
56 pack(dialog, sizer)
57 dialog.Fit()
58 w0, h0 = dialog.GetSize()
59 w1, h1 = dialog.GetBestSize()
60 dialog.SetSize((max(w0, w1)+25, max(h0, h1)+25))
63def get_view_limits(ppanel):
64 "get last zoom limits for a plot panel"
65 xlim = ppanel.axes.get_xlim()
66 ylim = ppanel.axes.get_ylim()
67 return (xlim, ylim)
69def set_view_limits(ppanel, xlim, ylim):
70 "set zoom limits for a plot panel, as found from get_view_limits"
71 ppanel.axes.set_xlim(xlim, emit=True)
72 ppanel.axes.set_ylim(ylim, emit=True)
75class OverAbsorptionDialog(wx.Dialog):
76 """dialog for correcting over-absorption"""
77 def __init__(self, parent, controller, **kws):
78 self.parent = parent
79 self.controller = controller
80 self.dgroup = self.controller.get_group()
81 groupnames = list(self.controller.file_groups.keys())
83 self.data = [self.dgroup.energy[:], self.dgroup.norm[:]]
85 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
86 title="Correct Over-absorption")
87 self.SetFont(Font(FONTSIZE))
88 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
89 self.wids = wids = {}
91 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1),
92 action=self.on_groupchoice)
94 wids['grouplist'].SetStringSelection(self.dgroup.filename)
96 opts = dict(size=(90, -1), precision=1, act_on_losefocus=True,
97 minval=-90, maxval=180)
99 fs_opts = dict(size=(90, -1), value=45, digits=1, increment=1)
100 wids['phi_in'] = FloatSpin(panel, **fs_opts)
101 wids['phi_out'] = FloatSpin(panel, **fs_opts)
103 wids['elem'] = Choice(panel, choices=ATOM_SYMS[:98], size=(50, -1))
104 wids['edge'] = Choice(panel, choices=EDGE_LIST, size=(50, -1))
106 wids['formula'] = wx.TextCtrl(panel, -1, '', size=(250, -1))
108 self.set_default_elem_edge(self.dgroup)
110 #wids['apply'] = Button(panel, 'Save / Overwrite', size=(150, -1),
111 # action=self.on_apply)
112 #SetTip(wids['apply'], 'Save corrected data, overwrite current arrays')
114 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1),
115 action=self.on_saveas)
116 SetTip(wids['save_as'], 'Save corrected data as new group')
118 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_abscorr',
119 size=(250, -1))
120 wids['correct'] = Button(panel, 'Do Correction',
121 size=(150, -1), action=self.on_correct)
122 SetTip(wids['correct'], 'Calculate Correction')
124 def add_text(text, dcol=1, newrow=True):
125 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow)
127 add_text(' Correction for Group: ', newrow=False)
128 panel.Add(wids['grouplist'], dcol=5)
130 add_text(' Absorbing Element: ')
131 panel.Add(wids['elem'])
133 add_text(' Edge: ', newrow=False)
134 panel.Add(wids['edge'])
136 add_text(' Material Formula: ')
137 panel.Add(wids['formula'], dcol=3)
139 add_text(' Incident Angle (deg): ')
140 panel.Add(wids['phi_in'])
142 add_text(' Exit Angle (deg): ')
143 panel.Add(wids['phi_out'])
145 panel.Add(wids['correct'], newrow=True)
146 # panel.Add(wids['apply'], dcol=2, newrow=True)
148 panel.Add(wids['save_as'], newrow=True)
149 panel.Add(wids['save_as_name'], dcol=3)
150 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone),
151 newrow=True)
152 panel.pack()
153 fit_dialog_window(self, panel)
154 self.plot_results(keep_limits=False)
157 def onDone(self, event=None):
158 self.Destroy()
160 def set_default_elem_edge(self, dgroup):
161 elem, edge = guess_edge(dgroup.e0)
162 self.wids['elem'].SetStringSelection(elem)
163 self.wids['edge'].SetStringSelection(edge)
165 def on_groupchoice(self, event=None):
166 fname = self.wids['grouplist'].GetStringSelection()
167 self.dgroup = self.controller.get_group(fname)
168 self.set_default_elem_edge(self.dgroup)
169 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_abscorr')
172 def on_correct(self, event=None):
173 wids = self.wids
174 dgroup = self.dgroup
175 anginp = wids['phi_in'].GetValue()
176 angout = wids['phi_out'].GetValue()
177 elem = wids['elem'].GetStringSelection()
178 edge = wids['edge'].GetStringSelection()
179 formula = wids['formula'].GetValue()
180 if len(formula) < 1:
181 return
183 cmd = """fluo_corr(%s.energy, %s.mu, '%s', '%s', edge='%s', group=%s,
184 anginp=%.1f, angout=%.1f)""" % (dgroup.groupname, dgroup.groupname,
185 formula, elem, edge, dgroup.groupname,
186 anginp, angout)
187 self.cmd = cmd
188 self.controller.larch.eval(cmd)
189 self.plot_results()
191 def on_apply(self, event=None):
192 xplot, yplot = self.data
193 dgroup = self.dgroup
194 dgroup.xplot = dgroup.energy = xplot
195 self.parent.process_normalization(dgroup)
196 dgroup.journal.add('fluor_corr_command', self.cmd)
197 self.plot_results()
199 def on_saveas(self, event=None):
200 wids = self.wids
201 fname = self.wids['grouplist'].GetStringSelection()
202 new_fname = wids['save_as_name'].GetValue()
203 ngroup = self.controller.copy_group(fname, new_filename=new_fname)
205 if hasattr(self.dgroup, 'norm_corr' ):
206 ngroup.mu = ngroup.norm_corr*1.0
207 del ngroup.norm_corr
209 ogroup = self.controller.get_group(fname)
210 self.parent.install_group(ngroup, journal=ogroup.journal)
211 olddesc = ogroup.journal.get('source_desc').value
212 ngroup.journal.add('source_desc', f"fluo_corrected({olddesc})")
213 ngroup.journal.add('fluor_correction_command', self.cmd)
215 def plot_results(self, event=None, keep_limits=True):
216 ppanel = self.controller.get_display(stacked=False).panel
218 dgroup = self.dgroup
219 xlim, ylim = get_view_limits(ppanel)
220 path, fname = path_split(dgroup.filename)
222 opts = dict(linewidth=3, ylabel=plotlabels.norm,
223 xlabel=plotlabels.energy, delay_draw=True,
224 show_legend=True)
226 if self.controller.plot_erange is not None:
227 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0]
228 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1]
230 if not hasattr(dgroup, 'norm_corr'):
231 dgroup.norm_corr = dgroup.norm[:]
233 ppanel.plot(dgroup.energy, dgroup.norm_corr, zorder=10, marker=None,
234 title='Over-absorption Correction:\n %s' % fname,
235 label='corrected', **opts)
237 ppanel.oplot(dgroup.energy, dgroup.norm, zorder=10, marker='o',
238 markersize=3, label='original', **opts)
239 if keep_limits:
240 set_view_limits(ppanel, xlim, ylim)
241 ppanel.canvas.draw()
242 ppanel.conf.draw_legend(show=True)
244 def GetResponse(self):
245 raise AttributeError("use as non-modal dialog!")
248class EnergyCalibrateDialog(wx.Dialog):
249 """dialog for calibrating energy"""
250 def __init__(self, parent, controller, **kws):
252 self.parent = parent
253 self.controller = controller
254 self.dgroup = self.controller.get_group()
255 groupnames = list(self.controller.file_groups.keys())
257 ensure_en_orig(self.dgroup)
259 self.data = [self.dgroup.energy_orig[:], self.dgroup.norm[:]]
260 xmin = min(self.dgroup.energy_orig)
261 xmax = max(self.dgroup.energy_orig)
262 e0val = getattr(self.dgroup, 'e0', xmin)
264 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
265 title="Calibrate / Align Energy")
266 self.SetFont(Font(FONTSIZE))
267 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
269 self.wids = wids = {}
270 wids['grouplist'] = Choice(panel, choices=groupnames, size=(275, -1),
271 action=self.on_groupchoice)
272 wids['grouplist'].SetStringSelection(self.dgroup.filename)
274 refgroups = ['None'] + groupnames
275 wids['reflist'] = Choice(panel, choices=refgroups, size=(275, -1),
276 action=self.on_align, default=0)
278 opts = dict(size=(90, -1), digits=3, increment=0.1)
280 opts['action'] = partial(self.on_calib, name='eshift')
281 wids['eshift'] = FloatSpin(panel, value=0, **opts)
283 self.plottype = Choice(panel, choices=list(Plot_Choices.keys()),
284 size=(275, -1), action=self.plot_results)
285 wids['do_align'] = Button(panel, 'Auto Align', size=(100, -1),
286 action=self.on_align)
288 wids['apply_one'] = Button(panel, 'Apply to Current Group', size=(200, -1),
289 action=self.on_apply_one)
291 wids['apply_sel'] = Button(panel, 'Apply to Selected Groups',
292 size=(250, -1), action=self.on_apply_sel)
293 SetTip(wids['apply_sel'], 'Apply the Energy Shift to all Selected Groups')
295 wids['save_as'] = Button(panel, 'Save As New Group ', size=(200, -1),
296 action=self.on_saveas)
297 SetTip(wids['save_as'], 'Save shifted data as new group')
299 wids['save_as_name'] = wx.TextCtrl(panel, -1,
300 self.dgroup.filename + '_eshift',
301 size=(275, -1))
303 wids['sharedref_msg'] = wx.StaticText(panel, label="1 groups share this energy reference")
304 wids['select_sharedref'] = Button(panel, 'Select Groups with shared reference',
305 size=(300, -1), action=self.on_select_sharedrefs)
307 def add_text(text, dcol=1, newrow=True):
308 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow)
310 add_text(' Current Group: ', newrow=False)
311 panel.Add(wids['grouplist'], dcol=2)
313 add_text(' Auto-Align to : ')
314 panel.Add(wids['reflist'], dcol=2)
316 add_text(' Plot Arrays as: ')
317 panel.Add(self.plottype, dcol=2)
319 add_text(' Energy Shift (eV): ')
320 panel.Add(wids['eshift'], dcol=1)
321 panel.Add(wids['do_align'], dcol=1)
322 panel.Add(HLine(panel, size=(500, 3)), dcol=4, newrow=True)
323 # panel.Add(apply_one, newrow=True)
325 panel.Add(wids['sharedref_msg'], dcol=2, newrow=True)
326 panel.Add(wids['select_sharedref'], dcol=2)
327 panel.Add(wids['apply_one'], dcol=1, newrow=True)
328 panel.Add(wids['apply_sel'], dcol=2)
330 panel.Add(HLine(panel, size=(500, 3)), dcol=4, newrow=True)
332 panel.Add(wids['save_as'], newrow=True)
333 panel.Add(wids['save_as_name'], dcol=3)
335 panel.pack()
337 fit_dialog_window(self, panel)
339 self.plot_results(keep_limits=False)
340 wx.CallAfter(self.get_groups_shared_energyrefs)
342 def on_select(self, event=None, opt=None):
343 _x, _y = self.controller.get_cursor()
344 if opt in self.wids:
345 self.wids[opt].SetValue(_x)
347 def get_groups_shared_energyrefs(self, dgroup=None):
348 if dgroup is None:
349 dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection())
350 sharedrefs = [dgroup.filename]
351 try:
352 eref = dgroup.config.xasnorm.get('energy_ref', None)
353 except:
354 eref = None
355 if eref is None:
356 eref = dgroup.groupname
359 for key, val in self.controller.file_groups.items():
360 if dgroup.groupname == val:
361 continue
362 g = self.controller.get_group(val)
363 try:
364 geref = g.config.xasnorm.get('energy_ref', None)
365 except:
366 geref = None
367 # print(key, val, geref, geref == ref_filename)
368 if geref == eref or geref == dgroup.filename:
369 sharedrefs.append(key)
370 self.wids['sharedref_msg'].SetLabel(f" {len(sharedrefs):d} groups share this energy reference")
371 return sharedrefs
373 def on_select_sharedrefs(self, event=None):
374 groups = self.get_groups_shared_energyrefs()
375 self.controller.filelist.SetCheckedStrings(groups)
377 def on_groupchoice(self, event=None):
378 dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection())
379 self.dgroup = dgroup
380 others = self.get_groups_shared_energyrefs(dgroup)
381 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_eshift')
382 self.plot_results()
384 def on_align(self, event=None, name=None, value=None):
385 ref = self.controller.get_group(self.wids['reflist'].GetStringSelection())
386 dat = self.dgroup
387 ensure_en_orig(dat)
388 ensure_en_orig(ref)
390 dat.xplot = dat.energy_orig[:]
391 ref.xplot = ref.energy_orig[:]
392 estep = find_energy_step(dat.xplot)
393 i1 = index_of(ref.energy_orig, ref.e0-20)
394 i2 = index_of(ref.energy_orig, ref.e0+20)
396 def resid(pars, ref, dat, i1, i2):
397 "fit residual"
398 newx = dat.xplot + pars['eshift'].value
399 scale = pars['scale'].value
400 y = interp(newx, dat.dmude, ref.xplot, kind='cubic')
401 return smooth(ref.xplot, y*scale-ref.dmude, xstep=estep, sigma=0.50)[i1:i2]
403 params = Parameters()
404 ex0 = ref.e0-dat.e0
405 emax = 50.0
406 if abs(ex0) > 75:
407 ex0 = 0.00
408 emax = (abs(ex0) + 75.0)
409 elif abs(ex0) > 10:
410 emax = (abs(ex0) + 75.0)
411 params.add('eshift', value=ex0, min=-emax, max=emax)
412 params.add('scale', value=1, min=1.e-8, max=50)
413 # print("Fit params ", params)
414 result = minimize(resid, params, args=(ref, dat, i1, i2),
415 max_nfev=1000)
416 # print(fit_report(result))
417 eshift = result.params['eshift'].value
418 self.wids['eshift'].SetValue(eshift)
420 ensure_en_orig(self.dgroup)
421 xnew = self.dgroup.energy_orig + eshift
422 self.data = xnew, self.dgroup.norm[:]
423 self.plot_results()
425 def on_calib(self, event=None, name=None):
426 wids = self.wids
427 eshift = wids['eshift'].GetValue()
428 ensure_en_orig(self.dgroup)
429 xnew = self.dgroup.energy_orig + eshift
430 self.data = xnew, self.dgroup.norm[:]
431 self.plot_results()
433 def on_apply_one(self, event=None):
434 xplot, yplot = self.data
435 dgroup = self.dgroup
436 eshift = self.wids['eshift'].GetValue()
438 ensure_en_orig(dgroup)
440 idx, norm_page = self.parent.get_nbpage('xasnorm')
441 norm_page.wids['energy_shift'].SetValue(eshift)
443 dgroup.energy_shift = eshift
444 dgroup.xplot = dgroup.energy = eshift + dgroup.energy_orig[:]
445 dgroup.journal.add('energy_shift ', eshift)
446 self.parent.process_normalization(dgroup)
447 self.plot_results()
449 def on_apply_sel(self, event=None):
450 eshift = self.wids['eshift'].GetValue()
451 idx, norm_page = self.parent.get_nbpage('xasnorm')
452 for checked in self.controller.filelist.GetCheckedStrings():
453 fname = self.controller.file_groups[str(checked)]
454 dgroup = self.controller.get_group(fname)
455 ensure_en_orig(dgroup)
456 dgroup.energy_shift = eshift
457 norm_page.wids['energy_shift'].SetValue(eshift)
459 dgroup.xplot = dgroup.energy = eshift + dgroup.energy_orig[:]
460 dgroup.journal.add('energy_shift ', eshift)
461 self.parent.process_normalization(dgroup)
463 def on_saveas(self, event=None):
464 wids = self.wids
465 fname = wids['grouplist'].GetStringSelection()
466 eshift = wids['eshift'].GetValue()
467 new_fname = wids['save_as_name'].GetValue()
468 ngroup = self.controller.copy_group(fname, new_filename=new_fname)
470 ensure_en_orig(ngroup)
471 ngroup.xplot = ngroup.energy = eshift + ngroup.energy_orig[:]
472 ngroup.energy_shift = 0
473 ngroup.energy_ref = ngroup.groupname
475 ogroup = self.controller.get_group(fname)
476 self.parent.install_group(ngroup, journal=ogroup.journal)
477 olddesc = ogroup.journal.get('source_desc').value
478 ngroup.journal.add('source_desc', f"energy_shifted({olddesc}, {eshift:.4f})")
479 ngroup.journal.add('energy_shift ', 0.0)
481 def plot_results(self, event=None, keep_limits=True):
482 ppanel = self.controller.get_display(stacked=False).panel
483 ppanel.oplot
484 xnew, ynew = self.data
485 dgroup = self.dgroup
487 xlim, ylim = get_view_limits(ppanel)
488 path, fname = path_split(dgroup.filename)
490 wids = self.wids
491 eshift = wids['eshift'].GetValue()
492 e0_old = dgroup.e0
493 e0_new = dgroup.e0 + eshift
495 xmin = min(e0_old, e0_new) - 25
496 xmax = max(e0_old, e0_new) + 50
498 use_deriv = self.plottype.GetStringSelection().lower().startswith('deriv')
500 ylabel = plotlabels.norm
501 if use_deriv:
502 ynew = np.gradient(ynew)/np.gradient(xnew)
503 ylabel = plotlabels.dmude
505 opts = dict(xmin=xmin, xmax=xmax, ylabel=ylabel, delay_draw=True,
506 xlabel=plotlabels.energy, show_legend=True)
508 if self.controller.plot_erange is not None:
509 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0]
510 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1]
512 xold, yold = self.dgroup.energy_orig, self.dgroup.norm
513 if use_deriv:
514 yold = np.gradient(yold)/np.gradient(xold)
516 ppanel.plot(xold, yold, zorder=10, marker='o', markersize=3,
517 label='original', linewidth=2, color='#1f77b4',
518 title=f'Energy Calibration:\n {fname}', **opts)
520 ppanel.oplot(xnew, ynew, zorder=15, marker='+', markersize=3,
521 linewidth=2, label='shifted',
522 color='#d62728', **opts)
524 if wids['reflist'].GetStringSelection() != 'None':
525 refgroup = self.controller.get_group(wids['reflist'].GetStringSelection())
526 xref, yref = refgroup.energy, refgroup.norm
527 if use_deriv:
528 yref = np.gradient(yref)/np.gradient(xref)
530 ppanel.oplot(xref, yref, style='solid', zorder=5, color='#2ca02c',
531 marker=None, label=refgroup.filename, **opts)
532 if keep_limits:
533 set_view_limits(ppanel, xlim, ylim)
534 ppanel.canvas.draw()
537 def GetResponse(self):
538 raise AttributeError("use as non-modal dialog!")
540class RebinDataDialog(wx.Dialog):
541 """dialog for rebinning data to standard XAFS grid"""
542 def __init__(self, parent, controller, **kws):
544 self.parent = parent
545 self.controller = controller
546 self.dgroup = self.controller.get_group()
547 groupnames = list(self.controller.file_groups.keys())
549 xmin = min(self.dgroup.energy)
550 xmax = max(self.dgroup.energy)
551 e0val = getattr(self.dgroup, 'e0', xmin)
552 xanes_step = 0.05 * (1 + max(1, int(e0val / 2000.0)))
553 self.data = [self.dgroup.energy[:], self.dgroup.mu[:],
554 self.dgroup.mu*0, e0val]
556 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
557 title="Rebin mu(E) Data")
558 self.SetFont(Font(FONTSIZE))
559 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT)
561 self.wids = wids = {}
563 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1),
564 action=self.on_groupchoice)
566 wids['grouplist'].SetStringSelection(self.dgroup.groupname)
568 opts = dict(size=(90, -1), precision=3, act_on_losefocus=True)
570 wids['e0'] = FloatCtrl(panel, value=e0val, minval=xmin, maxval=xmax, **opts)
571 pre1 = 10.0*(1+int((xmin-e0val)/10.0))
572 wids['pre1'] = FloatCtrl(panel, value=pre1, **opts)
573 wids['pre2'] = FloatCtrl(panel, value=-15, **opts)
574 wids['xanes1'] = FloatCtrl(panel, value=-15, **opts)
575 wids['xanes2'] = FloatCtrl(panel, value=15, **opts)
576 wids['exafs1'] = FloatCtrl(panel, value=etok(15), **opts)
577 wids['exafs2'] = FloatCtrl(panel, value=etok(xmax-e0val), **opts)
579 wids['pre_step'] = FloatCtrl(panel, value=2.0, **opts)
580 wids['xanes_step'] = FloatCtrl(panel, value=xanes_step, **opts)
581 wids['exafs_step'] = FloatCtrl(panel, value=0.05, **opts)
583 for wname, wid in wids.items():
584 if wname != 'grouplist':
585 wid.SetAction(partial(self.on_rebin, name=wname))
587 #wids['apply'] = Button(panel, 'Save / Overwrite', size=(150, -1),
588 # action=self.on_apply)
589 #SetTip(wids['apply'], 'Save rebinned data, overwrite current arrays')
591 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1),
592 action=self.on_saveas)
593 SetTip(wids['save_as'], 'Save corrected data as new group')
595 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_rebin',
596 size=(250, -1))
598 def add_text(text, dcol=1, newrow=True):
599 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow)
601 add_text('Rebin Data for Group: ', dcol=2, newrow=False)
602 panel.Add(wids['grouplist'], dcol=3)
604 add_text('E0: ')
605 panel.Add(wids['e0'])
606 add_text(' eV', newrow=False)
608 add_text('Region ')
609 add_text('Start ', newrow=False)
610 add_text('Stop ', newrow=False)
611 add_text('Step ', newrow=False)
612 add_text('Units ', newrow=False)
614 add_text('Pre-Edge: ')
615 panel.Add(wids['pre1'])
616 panel.Add(wids['pre2'])
617 panel.Add(wids['pre_step'])
618 add_text(' eV', newrow=False)
620 add_text('XANES: ')
621 panel.Add(wids['xanes1'])
622 panel.Add(wids['xanes2'])
623 panel.Add(wids['xanes_step'])
624 add_text(' eV', newrow=False)
626 add_text('EXAFS: ')
627 panel.Add(wids['exafs1'])
628 panel.Add(wids['exafs2'])
629 panel.Add(wids['exafs_step'])
630 add_text('1/\u212B', newrow=False)
632 # panel.Add(wids['apply'], dcol=2, newrow=True)
633 panel.Add(wids['save_as'], dcol=2, newrow=True)
634 panel.Add(wids['save_as_name'], dcol=3)
635 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone),
636 newrow=True)
637 panel.pack()
639 fit_dialog_window(self, panel)
641 self.on_rebin()
642 self.plot_results(keep_limits=False)
644 def onDone(self, event=None):
645 self.Destroy()
647 def on_groupchoice(self, event=None):
648 self.dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection())
649 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_rebin')
650 self.plot_results()
652 def on_rebin(self, event=None, name=None, value=None):
653 wids = self.wids
654 if name == 'pre2':
655 val = wids['pre2'].GetValue()
656 wids['xanes1'].SetValue(val, act=False)
657 elif name == 'xanes1':
658 val = wids['xanes1'].GetValue()
659 wids['pre2'].SetValue(val, act=False)
660 elif name == 'xanes2':
661 val = wids['xanes2'].GetValue()
662 wids['exafs1'].SetValue(etok(val), act=False)
663 elif name == 'exafs1':
664 val = wids['exafs1'].GetValue()
665 wids['xanes2'].SetValue(ktoe(val), act=False)
667 e0 = wids['e0'].GetValue()
668 args = dict(group=self.dgroup.groupname, e0=e0,
669 pre1=wids['pre1'].GetValue(),
670 pre2=wids['pre2'].GetValue(),
671 pre_step=wids['pre_step'].GetValue(),
672 exafs1=ktoe(wids['exafs1'].GetValue()),
673 exafs2=ktoe(wids['exafs2'].GetValue()),
674 exafs_kstep=wids['exafs_step'].GetValue(),
675 xanes_step=wids['xanes_step'].GetValue())
677 # do rebin:
678 cmd = """rebin_xafs({group}, e0={e0:f}, pre1={pre1:f}, pre2={pre2:f},
679 pre_step={pre_step:f}, xanes_step={xanes_step:f}, exafs1={exafs1:f},
680 exafs2={exafs2:f}, exafs_kstep={exafs_kstep:f})""".format(**args)
681 self.cmd = cmd
682 self.controller.larch.eval(cmd)
684 if hasattr(self.dgroup, 'rebinned'):
685 xnew = self.dgroup.rebinned.energy
686 ynew = self.dgroup.rebinned.mu
687 yerr = self.dgroup.rebinned.delta_mu
688 self.data = xnew, ynew, yerr, e0
689 self.plot_results()
691 def on_apply(self, event=None):
692 xplot, yplot, yerr, e0 = self.data
693 dgroup = self.dgroup
694 dgroup.energy = dgroup.xplot = xplot
695 dgroup.mu = dgroup.yplot = yplot
696 dgroup.journal.add('rebin_command ', self.cmd)
697 self.parent.process_normalization(dgroup)
698 self.plot_results()
700 def on_saveas(self, event=None):
701 wids = self.wids
702 fname = wids['grouplist'].GetStringSelection()
703 new_fname = wids['save_as_name'].GetValue()
704 ngroup = self.controller.copy_group(fname, new_filename=new_fname)
705 xplot, yplot, yerr, de0 = self.data
706 ngroup.energy = ngroup.xplot = xplot
707 ngroup.mu = ngroup.yplot = yplot
709 ogroup = self.controller.get_group(fname)
710 olddesc = ogroup.journal.get('source_desc').value
712 ngroup.delta_mu = getattr(ngroup, 'yerr', 1.0)
713 self.parent.process_normalization(ngroup)
715 self.parent.install_group(ngroup, journal=ogroup.journal)
716 ngroup.journal.add('source_desc', f"rebinned({olddesc})")
717 ngroup.journal.add('rebin_command ', self.cmd)
719 def on_done(self, event=None):
720 self.Destroy()
722 def plot_results(self, event=None, keep_limits=True):
723 ppanel = self.controller.get_display(stacked=False).panel
724 xnew, ynew, yerr, e0 = self.data
725 dgroup = self.dgroup
726 xlim, ylim = get_view_limits(ppanel)
727 path, fname = path_split(dgroup.filename)
729 opts = {'delay_draw': True}
730 if self.controller.plot_erange is not None:
731 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0]
732 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1]
734 ppanel.plot(xnew, ynew, zorder=20, marker='square',
735 linewidth=3, title='Enegy rebinning:\n %s' % fname,
736 label='rebinned', xlabel=plotlabels.energy,
737 ylabel=plotlabels.mu, **opts)
739 xold, yold = self.dgroup.energy, self.dgroup.mu
740 ppanel.oplot(xold, yold, zorder=10,
741 marker='o', markersize=4, linewidth=2.0,
742 label='original', show_legend=True, **opts)
743 if keep_limits:
744 set_view_limits(ppanel, xlim, ylim)
745 ppanel.canvas.draw()
747 def GetResponse(self):
748 raise AttributeError("use as non-modal dialog!")
750class SmoothDataDialog(wx.Dialog):
751 """dialog for smoothing data"""
752 def __init__(self, parent, controller, **kws):
754 self.parent = parent
755 self.controller = controller
756 self.dgroup = self.controller.get_group()
757 groupnames = list(self.controller.file_groups.keys())
759 self.data = [self.dgroup.energy[:], self.dgroup.mu[:]]
762 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
763 title="Smooth mu(E) Data")
764 self.SetFont(Font(FONTSIZE))
765 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
767 self.wids = wids = {}
769 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1),
770 action=self.on_groupchoice)
772 wids['grouplist'].SetStringSelection(self.dgroup.filename)
773 SetTip(wids['grouplist'], 'select a new group, clear undo history')
775 smooth_ops = ('None', 'Boxcar', 'Savitzky-Golay', 'Convolution')
776 conv_ops = ('Lorenztian', 'Gaussian')
778 self.smooth_op = Choice(panel, choices=smooth_ops, size=(150, -1),
779 action=self.on_smooth)
780 self.smooth_op.SetSelection(0)
782 self.conv_op = Choice(panel, choices=conv_ops, size=(150, -1),
783 action=self.on_smooth)
784 self.conv_op.SetSelection(0)
786 opts = dict(size=(50, -1), act_on_losefocus=True, odd_only=False)
788 self.sigma = FloatSpin(panel, value=1, digits=2, min_val=0, increment=0.1,
789 size=(60, -1), action=self.on_smooth)
791 self.par_n = FloatSpin(panel, value=2, digits=0, min_val=1, increment=1,
792 size=(60, -1), action=self.on_smooth)
794 self.par_o = FloatSpin(panel, value=1, digits=0, min_val=1, increment=1,
795 size=(60, -1), action=self.on_smooth)
797 # for fc in (self.sigma, self.par_n, self.par_o):
798 # self.sigma.SetAction(self.on_smooth)
800 self.message = SimpleText(panel, label=' ', size=(200, -1))
803 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1),
804 action=self.on_saveas)
805 SetTip(wids['save_as'], 'Save corrected data as new group')
807 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_smooth',
808 size=(250, -1))
810 def add_text(text, dcol=1, newrow=True):
811 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow)
813 add_text('Smooth Data for Group: ', newrow=False)
814 panel.Add(wids['grouplist'], dcol=5)
816 add_text('Smoothing Method: ')
817 panel.Add(self.smooth_op)
818 add_text(' n = ', newrow=False)
819 panel.Add(self.par_n)
820 add_text(' order= ', newrow=False)
821 panel.Add(self.par_o)
823 add_text('Convolution Form: ')
824 panel.Add(self.conv_op)
825 add_text(' sigma: ', newrow=False)
826 panel.Add(self.sigma)
828 panel.Add((10, 10), newrow=True)
829 panel.Add(self.message, dcol=5)
831 # panel.Add(wids['apply'], newrow=True)
833 panel.Add(wids['save_as'], newrow=True)
834 panel.Add(wids['save_as_name'], dcol=5)
835 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone),
836 newrow=True)
837 panel.pack()
838 fit_dialog_window(self, panel)
840 self.plot_results(keep_limits=False)
842 def onDone(self, event=None):
843 self.Destroy()
846 def on_groupchoice(self, event=None):
847 self.dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection())
848 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_smooth')
849 self.plot_results()
851 def on_smooth(self, event=None, value=None):
852 smoothop = self.smooth_op.GetStringSelection().lower()
854 convop = self.conv_op.GetStringSelection()
855 self.conv_op.Enable(smoothop.startswith('conv'))
856 self.sigma.Enable(smoothop.startswith('conv'))
857 self.message.SetLabel('')
858 self.par_n.SetMin(1)
859 self.par_n.odd_only = False
860 par_n = int(self.par_n.GetValue())
861 par_o = int(self.par_o.GetValue())
862 sigma = self.sigma.GetValue()
863 cmd = '{group:s}.mu' # No smoothing
864 estep = find_energy_step(self.data[0])
865 if smoothop.startswith('box'):
866 self.par_n.Enable()
867 cmd = "boxcar({group:s}.mu, {par_n:d})"
868 self.conv_op.Disable()
869 elif smoothop.startswith('savi'):
870 self.par_n.Enable()
871 self.par_n.odd_only = True
872 self.par_o.Enable()
874 x0 = max(par_o + 1, par_n)
875 if x0 % 2 == 0:
876 x0 += 1
877 self.par_n.SetMin(par_o + 1)
878 if par_n != x0:
879 self.par_n.SetValue(x0)
880 self.message.SetLabel('n must odd and > order+1')
882 cmd = "savitzky_golay({group:s}.mu, {par_n:d}, {par_o:d})"
884 elif smoothop.startswith('conv'):
885 cmd = "smooth({group:s}.energy, {group:s}.mu, xstep={estep:f}, sigma={sigma:f}, form='{convop:s}')"
886 self.cmd = cmd.format(group=self.dgroup.groupname, convop=convop,
887 estep=estep, sigma=sigma, par_n=par_n, par_o=par_o)
889 self.controller.larch.eval("_tmpy = %s" % self.cmd)
890 self.data = self.dgroup.energy[:], self.controller.symtable._tmpy
891 self.plot_results()
893 def on_apply(self, event=None):
894 xplot, yplot = self.data
895 dgroup = self.dgroup
896 dgroup.energy = xplot
897 dgroup.mu = yplot
898 dgroup.journal.add('smooth_command', self.cmd)
899 self.parent.process_normalization(dgroup)
900 self.plot_results()
902 def on_saveas(self, event=None):
903 wids = self.wids
904 fname = wids['grouplist'].GetStringSelection()
905 new_fname = wids['save_as_name'].GetValue()
906 ngroup = self.controller.copy_group(fname, new_filename=new_fname)
908 xplot, yplot = self.data
909 ngroup.energy = ngroup.xplot = xplot
910 ngroup.mu = ngroup.yplot = yplot
912 ogroup = self.controller.get_group(fname)
913 olddesc = ogroup.journal.get('source_desc').value
915 self.parent.install_group(ngroup, journal=ogroup.journal)
916 ngroup.journal.add('source_desc', f"smoothed({olddesc})")
917 ngroup.journal.add('smooth_command', self.cmd)
918 self.parent.process_normalization(ngroup)
920 def on_done(self, event=None):
921 self.Destroy()
923 def plot_results(self, event=None, keep_limits=True):
924 ppanel = self.controller.get_display(stacked=False).panel
925 xnew, ynew = self.data
926 dgroup = self.dgroup
927 path, fname = path_split(dgroup.filename)
928 opts = {'delay_draw': True}
929 xlim, ylim = get_view_limits(ppanel)
931 if self.controller.plot_erange is not None:
932 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0]
933 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1]
935 ppanel.plot(xnew, ynew, zorder=20, marker=None,
936 linewidth=3, title='Smoothing:\n %s' % fname,
937 label='smoothed', xlabel=plotlabels.energy,
938 ylabel=plotlabels.mu, **opts)
940 xold, yold = self.dgroup.energy, self.dgroup.mu
941 ppanel.oplot(xold, yold, zorder=10,
942 marker='o', markersize=4, linewidth=2.0,
943 label='original', show_legend=True, **opts)
944 if keep_limits:
945 set_view_limits(ppanel, xlim, ylim)
946 ppanel.canvas.draw()
948 def GetResponse(self):
949 raise AttributeError("use as non-modal dialog!")
951class DeconvolutionDialog(wx.Dialog):
952 """dialog for energy deconvolution"""
953 def __init__(self, parent, controller, **kws):
955 self.parent = parent
956 self.controller = controller
957 self.dgroup = self.controller.get_group()
958 groupnames = list(self.controller.file_groups.keys())
960 self.data = [self.dgroup.energy[:], self.dgroup.norm[:]]
963 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
964 title="Deconvolve mu(E) Data")
965 self.SetFont(Font(FONTSIZE))
966 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
968 self.wids = wids = {}
970 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1),
971 action=self.on_groupchoice)
973 wids['grouplist'].SetStringSelection(self.dgroup.groupname)
974 SetTip(wids['grouplist'], 'select a new group, clear undo history')
976 deconv_ops = ('Lorenztian', 'Gaussian')
978 wids['deconv_op'] = Choice(panel, choices=deconv_ops, size=(150, -1),
979 action=self.on_deconvolve)
981 wids['esigma'] = FloatSpin(panel, value=0.5, digits=2, size=(90, -1),
982 increment=0.1, action=self.on_deconvolve)
984 #wids['apply'] = Button(panel, 'Save / Overwrite', size=(150, -1),
985 # action=self.on_apply)
986 #SetTip(wids['apply'], 'Save corrected data, overwrite current arrays')
988 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1),
989 action=self.on_saveas)
990 SetTip(wids['save_as'], 'Save corrected data as new group')
992 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_deconv',
993 size=(250, -1))
995 def add_text(text, dcol=1, newrow=True):
996 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow)
998 add_text('Deconvolve Data for Group: ', newrow=False)
999 panel.Add(wids['grouplist'], dcol=5)
1001 add_text('Functional Form: ')
1002 panel.Add(wids['deconv_op'])
1004 add_text(' sigma= ')
1005 panel.Add(wids['esigma'])
1006 # panel.Add(wids['apply'], newrow=True)
1007 panel.Add(wids['save_as'], newrow=True)
1008 panel.Add(wids['save_as_name'], dcol=5)
1009 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone),
1010 newrow=True)
1011 panel.pack()
1013 fit_dialog_window(self, panel)
1014 self.plot_results(keep_limits=False)
1016 def onDone(self, event=None):
1017 self.Destroy()
1019 def on_saveas(self, event=None):
1020 wids = self.wids
1021 fname = wids['grouplist'].GetStringSelection()
1022 new_fname = wids['save_as_name'].GetValue()
1023 ngroup = self.controller.copy_group(fname, new_filename=new_fname)
1024 xplot, yplot = self.data
1025 ngroup.energy = ngroup.xplot = xplot
1026 ngroup.mu = ngroup.yplot = yplot
1028 ogroup = self.controller.get_group(fname)
1029 olddesc = ogroup.journal.get('source_desc').value
1031 self.parent.install_group(ngroup, journal=ogroup.journal)
1032 ngroup.journal.add('source_desc', f"deconvolved({olddesc})")
1033 ngroup.journal.add('deconvolve_command', self.cmd)
1034 self.parent.process_normalization(ngroup)
1037 def on_groupchoice(self, event=None):
1038 self.dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection())
1039 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_deconv')
1040 self.plot_results()
1042 def on_deconvolve(self, event=None, value=None):
1043 deconv_form = self.wids['deconv_op'].GetStringSelection()
1045 esigma = self.wids['esigma'].GetValue()
1047 dopts = [self.dgroup.groupname,
1048 "form='%s'" % (deconv_form),
1049 "esigma=%.4f" % (esigma)]
1050 self.cmd = "xas_deconvolve(%s)" % (', '.join(dopts))
1051 self.controller.larch.eval(self.cmd)
1053 self.data = self.dgroup.energy[:], self.dgroup.deconv[:]
1054 self.plot_results()
1056 def on_apply(self, event=None):
1057 xplot, yplot = self.data
1058 dgroup = self.dgroup
1059 dgroup.energy = xplot
1060 dgroup.mu = yplot
1061 dgroup.journal.add('deconvolve_command ', self.cmd)
1062 self.parent.process_normalization(dgroup)
1063 self.plot_results()
1065 def plot_results(self, event=None, keep_limits=True):
1066 ppanel = self.controller.get_display(stacked=False).panel
1067 xnew, ynew = self.data
1068 dgroup = self.dgroup
1069 xlim, ylim = get_view_limits(ppanel)
1070 path, fname = path_split(dgroup.filename)
1072 opts = {'delay_draw': True}
1073 if self.controller.plot_erange is not None:
1074 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0]
1075 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1]
1077 ppanel.plot(xnew, ynew, zorder=20, marker=None,
1078 linewidth=3, title='Deconvolving:\n %s' % fname,
1079 label='deconvolved', xlabel=plotlabels.energy,
1080 ylabel=plotlabels.mu, **opts)
1082 xold, yold = self.dgroup.energy, self.dgroup.norm
1083 ppanel.oplot(xold, yold, zorder=10,
1084 marker='o', markersize=4, linewidth=2.0,
1085 label='original', show_legend=True, **opts)
1086 if keep_limits:
1087 set_view_limits(ppanel, xlim, ylim)
1088 ppanel.canvas.draw()
1090 def GetResponse(self):
1091 raise AttributeError("use as non-modal dialog!")
1093class DeglitchDialog(wx.Dialog):
1094 """dialog for deglitching or removing unsightly data points"""
1095 def __init__(self, parent, controller, **kws):
1096 self.parent = parent
1097 self.controller = controller
1098 self.wids = {}
1099 self.plot_markers = None
1100 self.dgroup = self.controller.get_group()
1101 groupnames = list(self.controller.file_groups.keys())
1103 self.reset_data_history()
1104 xplot, yplot = self.data
1106 xrange = (max(xplot) - min(xplot))
1107 xmax = int(max(xplot) + xrange/5.0)
1108 xmin = int(min(xplot) - xrange/5.0)
1110 lastx, lasty = self.controller.get_cursor()
1111 if lastx is None:
1112 lastx = max(xplot)
1114 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
1115 title="Select Points to Remove")
1116 self.SetFont(Font(FONTSIZE))
1117 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
1118 wids = self.wids
1120 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1),
1121 action=self.on_groupchoice)
1123 wids['grouplist'].SetStringSelection(self.dgroup.filename)
1124 SetTip(wids['grouplist'], 'select a new group, clear undo history')
1126 br_xlast = Button(panel, 'Remove point', size=(125, -1),
1127 action=partial(self.on_remove, opt='x'))
1129 br_range = Button(panel, 'Remove range', size=(125, -1),
1130 action=partial(self.on_remove, opt='range'))
1132 undo = Button(panel, 'Undo remove', size=(125, -1),
1133 action=self.on_undo)
1135 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1),
1136 action=self.on_saveas)
1137 SetTip(wids['save_as'], 'Save deglitched data as new group')
1139 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_clean',
1140 size=(250, -1))
1142 self.history_message = SimpleText(panel, '')
1144 opts = dict(size=(125, -1), digits=2, increment=0.1)
1145 for wname in ('xlast', 'range1', 'range2'):
1146 if wname == 'range2':
1147 lastx += 1
1148 pin_action = partial(self.parent.onSelPoint, opt=wname,
1149 relative_e0=False,
1150 callback=self.on_pinvalue)
1152 float_action=partial(self.on_floatvalue, opt=wname)
1153 fspin, pinb = FloatSpinWithPin(panel, value=lastx,
1154 pin_action=pin_action,
1155 action=float_action)
1156 wids[wname] = fspin
1157 wids[wname+'_pin'] = pinb
1159 self.choice_range = Choice(panel, choices=('above', 'below', 'between'),
1160 size=(90, -1), action=self.on_rangechoice)
1162 self.choice_range.SetStringSelection('above')
1163 wids['range2'].Disable()
1165 wids['plotopts'] = Choice(panel, choices=list(DEGLITCH_PLOTS.keys()),
1166 size=(175, -1),
1167 action=self.on_plotchoice)
1169 wids['plotopts'].SetStringSelection(NORM_MU)
1171 def add_text(text, dcol=1, newrow=True):
1172 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow)
1174 add_text('Deglitch Data for Group: ', dcol=2, newrow=False)
1175 panel.Add(wids['grouplist'], dcol=5)
1177 add_text('Single Energy : ', dcol=2)
1178 panel.Add(wids['xlast'])
1179 panel.Add(wids['xlast_pin'])
1180 panel.Add(br_xlast)
1182 add_text('Plot Data as: ', dcol=2)
1183 panel.Add(wids['plotopts'], dcol=5)
1185 add_text('Energy Range : ')
1186 panel.Add(self.choice_range)
1187 panel.Add(wids['range1'])
1188 panel.Add(wids['range1_pin'])
1189 panel.Add(br_range)
1191 panel.Add((10, 10), dcol=2, newrow=True)
1192 panel.Add(wids['range2'])
1193 panel.Add(wids['range2_pin'])
1195 # panel.Add(wids['apply'], dcol=2, newrow=True)
1197 panel.Add(wids['save_as'], dcol=2, newrow=True)
1198 panel.Add(wids['save_as_name'], dcol=4)
1199 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone),
1200 dcol=2, newrow=True)
1201 panel.Add(self.history_message, dcol=2)
1202 panel.Add(undo)
1204 panel.pack()
1206 fit_dialog_window(self, panel)
1207 self.plot_results(keep_limits=False)
1209 def onDone(self, event=None):
1210 self.Destroy()
1212 def reset_data_history(self):
1213 plottype = 'norm'
1214 if 'plotopts' in self.wids:
1215 plotstr = self.wids['plotopts'].GetStringSelection()
1216 plottype = DEGLITCH_PLOTS[plotstr]
1217 self.data = self.get_xydata(datatype=plottype)
1218 self.xmasks = [np.ones(len(self.data[0]), dtype=bool)]
1219 self.plot_markers = None
1221 def get_xydata(self, datatype='mu'):
1222 if hasattr(self.dgroup, 'energy'):
1223 xplot = self.dgroup.energy[:]
1224 else:
1225 xplot = self.dgroup.xplot[:]
1226 yplot = self.dgroup.yplot[:]
1227 if datatype == 'mu' and hasattr(self.dgroup, 'mu'):
1228 yplot = self.dgroup.mu[:]
1229 elif datatype == 'norm':
1230 if not hasattr(self.dgroup, 'norm'):
1231 self.parent.process_normalization(dgroup)
1232 yplot = self.dgroup.norm[:]
1233 elif datatype in ('chie', 'chiew'):
1234 if not hasattr(self.dgroup, 'chie'):
1235 self.parent.process_exafs(self.dgroup)
1236 yplot = self.dgroup.chie[:]
1237 if datatype == 'chiew':
1238 yplot = self.dgroup.chie[:] * (xplot-self.dgroup.e0)
1239 return (xplot, yplot)
1241 def on_groupchoice(self, event=None):
1242 self.dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection())
1243 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_clean')
1244 self.reset_data_history()
1245 self.plot_results(use_zoom=True)
1247 def on_rangechoice(self, event=None):
1248 sel = self.choice_range.GetStringSelection()
1249 self.wids['range2'].Enable(sel == 'between')
1252 def on_plotchoice(self, event=None):
1253 plotstr = self.wids['plotopts'].GetStringSelection()
1254 plottype = DEGLITCH_PLOTS[plotstr]
1255 self.data = self.get_xydata(datatype=plottype)
1256 self.plot_results()
1258 def on_pinvalue(self, opt='__', xsel=None, **kws):
1259 if xsel is not None and opt in self.wids:
1260 self.wids[opt].SetValue(xsel)
1261 self.plot_markers = opt
1262 self.plot_results()
1264 def on_floatvalue(self, val=None, opt='_', **kws):
1265 self.plot_markers = opt
1266 self.plot_results()
1268 def on_remove(self, event=None, opt=None):
1269 xwork, ywork = self.data
1270 mask = copy.deepcopy(self.xmasks[-1])
1271 if opt == 'x':
1272 bad = index_nearest(xwork, self.wids['xlast'].GetValue())
1273 mask[bad] = False
1274 elif opt == 'range':
1275 rchoice = self.choice_range.GetStringSelection().lower()
1276 x1 = index_nearest(xwork, self.wids['range1'].GetValue())
1277 x2 = None
1278 if rchoice == 'below':
1279 x2, x1 = x1, x2
1280 elif rchoice == 'between':
1281 x2 = index_nearest(xwork, self.wids['range2'].GetValue())
1282 if x1 > x2:
1283 x1, x2 = x2, x1
1284 mask[x1:x2] = False
1285 self.xmasks.append(mask)
1286 self.plot_results()
1288 def on_undo(self, event=None):
1289 if len(self.xmasks) == 1:
1290 self.xmasks = [np.ones(len(self.data[0]), dtype=bool)]
1291 else:
1292 self.xmasks.pop()
1293 self.plot_results()
1295 def on_apply(self, event=None):
1296 xplot, yplot = self.get_xydata(datatype='xydata')
1297 mask = self.xmasks[-1]
1298 dgroup = self.dgroup
1299 energies_removed = xplot[np.where(~mask)].tolist()
1300 dgroup.energy = dgroup.xplot = xplot[mask]
1301 dgroup.mu = dgroup.yplot = yplot[mask]
1302 self.reset_data_history()
1303 dgroup.journal.add('deglitch_removed_energies', energies_removed)
1304 self.parent.process_normalization(dgroup)
1305 self.plot_results()
1307 def on_saveas(self, event=None):
1308 fname = self.wids['grouplist'].GetStringSelection()
1309 new_fname = self.wids['save_as_name'].GetValue()
1310 ngroup = self.controller.copy_group(fname, new_filename=new_fname)
1311 xplot, yplot = self.get_xydata(datatype='mu')
1312 mask = self.xmasks[-1]
1313 energies_removed = xplot[np.where(~mask)].tolist()
1315 ngroup.energy = ngroup.xplot = xplot[mask]
1316 ngroup.mu = ngroup.yplot = yplot[mask]
1317 ngroup.energy_orig = 1.0*ngroup.energy
1319 ogroup = self.controller.get_group(fname)
1320 olddesc = ogroup.journal.get('source_desc').value
1322 self.parent.install_group(ngroup, journal=ogroup.journal)
1323 ngroup.journal.add('source_desc', f"deglitched({olddesc})")
1324 ngroup.journal.add('deglitch_removed_energies', energies_removed)
1326 self.parent.process_normalization(ngroup)
1328 def plot_results(self, event=None, keep_limits=True):
1329 ppanel = self.controller.get_display(stacked=False).panel
1331 xplot, yplot = self.data
1333 xmin = min(xplot) - 0.025*(max(xplot) - min(xplot))
1334 xmax = max(xplot) + 0.025*(max(xplot) - min(xplot))
1335 ymin = min(yplot) - 0.025*(max(yplot) - min(yplot))
1336 ymax = max(yplot) + 0.025*(max(yplot) - min(yplot))
1338 dgroup = self.dgroup
1340 path, fname = path_split(dgroup.filename)
1342 plotstr = self.wids['plotopts'].GetStringSelection()
1343 plottype = DEGLITCH_PLOTS[plotstr]
1345 xlabel=plotlabels.energy
1346 if plottype in ('chie', 'chiew'):
1347 xmin = self.dgroup.e0
1348 xlabel = xlabel=plotlabels.ewithk
1350 opts = dict(xlabel=xlabel, title='De-glitching:\n %s' % fname,
1351 delay_draw=True)
1353 ylabel = {'mu': plotlabels.mu,
1354 'norm': plotlabels.norm,
1355 'chie': plotlabels.chie,
1356 'chiew': plotlabels.chiew.format(1),
1357 }.get(plottype, plotlabels.norm)
1359 dgroup.plot_xlabel = xlabel
1360 dgroup.plot_ylabel = ylabel
1362 xlim, ylim = get_view_limits(ppanel)
1364 ppanel.plot(xplot, yplot, zorder=10, marker=None, linewidth=3,
1365 label='original', ylabel=ylabel, **opts)
1367 if len(self.xmasks) > 1:
1368 mask = self.xmasks[-1]
1369 ppanel.oplot(xplot[mask], yplot[mask], zorder=15,
1370 marker='o', markersize=3, linewidth=2.0,
1371 label='current', show_legend=True, **opts)
1373 def ek_formatter(x, pos):
1374 ex = float(x) - self.dgroup.e0
1375 s = '' if ex < 0 else '\n[%.1f]' % (etok(ex))
1376 return r"%1.4g%s" % (x, s)
1378 if keep_limits:
1379 set_view_limits(ppanel, xlim, ylim)
1380 if plottype in ('chie', 'chiew'):
1381 ppanel.axes.xaxis.set_major_formatter(FuncFormatter(ek_formatter))
1383 if self.plot_markers is not None:
1384 rchoice = self.choice_range.GetStringSelection().lower()
1385 xwork, ywork = self.data
1386 opts = dict(marker='o', markersize=6, zorder=2, label='_nolegend_',
1387 markerfacecolor='#66000022', markeredgecolor='#440000')
1388 if self.plot_markers == 'xlast':
1389 bad = index_nearest(xwork, self.wids['xlast'].GetValue())
1390 ppanel.axes.plot([xwork[bad]], [ywork[bad]], **opts)
1391 else:
1392 bad = index_nearest(xwork, self.wids['range1'].GetValue())
1393 if rchoice == 'above':
1394 ppanel.axes.plot([xwork[bad:]], [ywork[bad:]], **opts)
1395 elif rchoice == 'below':
1396 ppanel.axes.plot([xwork[:bad+1]], [ywork[:bad+1]], **opts)
1397 elif rchoice == 'between':
1398 bad2 = index_nearest(xwork, self.wids['range2'].GetValue())
1399 ppanel.axes.plot([xwork[bad:bad2+1]],
1400 [ywork[bad:bad2+1]], **opts)
1403 ppanel.canvas.draw()
1405 self.history_message.SetLabel('%i items in history' % (len(self.xmasks)-1))
1407 def GetResponse(self):
1408 raise AttributeError("use as non-modal dialog!")
1411SPECCALC_SETUP = """#From SpectraCalc dialog:
1412_x = {group:s}.{xname:s}
1413a = {group:s}.{yname:s}
1414b = c = d = e = f = g = None
1415"""
1417SPECCALC_INTERP = "{key:s} = interp({group:s}.{xname:s}, {group:s}.{yname:s}, _x)"
1418SPECCALC_PLOT = """plot(_x, ({expr:s}), label='{expr:s}', new=True,
1419 show_legend=True, xlabel='{xname:s}', title='Spectral Calculation')"""
1421SPECCALC_SAVE = """{new:s} = copy_xafs_group({group:s})
1422{new:s}.groupname = '{new:s}'
1423{new:s}.mu = ({expr:s})
1424{new:s}.filename = '{fname:s}'
1425{new:s}.journal.add('calc_groups', {group_map:s})
1426{new:s}.journal.add('calc_arrayname', '{yname:s}')
1427{new:s}.journal.add('calc_expression', '{expr:s}')
1428del _x, a, b, c, d, e, f, g"""
1431class SpectraCalcDialog(wx.Dialog):
1432 """dialog for adding and subtracting spectra"""
1433 def __init__(self, parent, controller, **kws):
1435 self.parent = parent
1436 self.controller = controller
1437 self.dgroup = self.controller.get_group()
1438 self.group_a = None
1439 groupnames = list(self.controller.file_groups.keys())
1441 self.data = [self.dgroup.energy[:], self.dgroup.norm[:]]
1442 xmin = min(self.dgroup.energy)
1443 xmax = max(self.dgroup.energy)
1444 e0val = getattr(self.dgroup, 'e0', xmin)
1446 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(475, 525),
1447 title="Spectra Calculations: Add, Subtract Spectra")
1448 self.SetFont(Font(FONTSIZE))
1449 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
1451 def add_text(text, dcol=1, newrow=True):
1452 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow)
1454 self.wids = wids = {}
1455 array_choices = ('Normalized \u03BC(E)', 'Raw \u03BC(E)')
1457 wids['array'] = Choice(panel, choices=array_choices, size=(250, -1))
1459 add_text('Array to use: ', newrow=True)
1460 panel.Add(wids['array'], dcol=2)
1462 # group 'a' cannot be none, and defaults to current group
1463 gname = 'a'
1464 wname = 'group_%s' % gname
1465 wids[wname] = Choice(panel, choices=groupnames, size=(250, -1))
1466 wids[wname].SetStringSelection(self.dgroup.filename)
1467 add_text(' %s = ' % gname, newrow=True)
1468 panel.Add(wids[wname], dcol=2)
1470 groupnames.insert(0, 'None')
1471 for gname in ('b', 'c', 'd', 'e', 'f', 'g'):
1472 wname = 'group_%s' % gname
1473 wids[wname] = Choice(panel, choices=groupnames, size=(250, -1))
1474 wids[wname].SetSelection(0)
1475 add_text(' %s = ' % gname, newrow=True)
1476 panel.Add(wids[wname], dcol=2)
1478 wids['formula'] = wx.TextCtrl(panel, -1, 'a-b', size=(250, -1))
1479 add_text('Expression = ', newrow=True)
1480 panel.Add(wids['formula'], dcol=2)
1482 wids['docalc'] = Button(panel, 'Calculate',
1483 size=(150, -1), action=self.on_docalc)
1485 panel.Add(wids['docalc'], dcol=2, newrow=True)
1487 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1),
1488 action=self.on_saveas)
1489 SetTip(wids['save_as'], 'Save as new group')
1491 wids['save_as_name'] = wx.TextCtrl(panel, -1,
1492 self.dgroup.filename + '_calc',
1493 size=(250, -1))
1494 panel.Add(wids['save_as'], newrow=True)
1495 panel.Add(wids['save_as_name'], dcol=2)
1496 wids['save_as'].Disable()
1497 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone),
1498 newrow=True)
1499 panel.pack()
1500 fit_dialog_window(self, panel)
1502 def onDone(self, event=None):
1503 self.Destroy()
1505 def on_docalc(self, event=None):
1506 self.expr = self.wids['formula'].GetValue()
1508 self.yname = 'mu'
1509 if self.wids['array'].GetStringSelection().lower().startswith('norm'):
1510 self.yname = 'norm'
1512 groups = {}
1513 for aname in ('a', 'b', 'c', 'd', 'e', 'f', 'g'):
1514 fname = self.wids['group_%s' % aname].GetStringSelection()
1515 if fname not in (None, 'None'):
1516 grp = self.controller.get_group(fname)
1517 groups[aname] = grp
1519 self.group_map = {key: group.groupname for key, group in groups.items()}
1520 # note: 'a' cannot be None, all others can be None
1521 group_a = self.group_a = groups.pop('a')
1522 xname = 'energy'
1523 if not hasattr(group_a, xname):
1524 xname = 'xplot'
1526 cmds = [SPECCALC_SETUP.format(group=group_a.groupname,
1527 xname=xname, yname=self.yname)]
1529 for key, group in groups.items():
1530 cmds.append(SPECCALC_INTERP.format(key=key, group=group.groupname,
1531 xname=xname, yname=self.yname))
1533 cmds.append(SPECCALC_PLOT.format(expr=self.expr, xname=xname))
1534 self.controller.larch.eval('\n'.join(cmds))
1535 self.wids['save_as'].Enable()
1537 def on_saveas(self, event=None):
1538 wids = self.wids
1539 _larch = self.controller.larch
1540 fname = wids['group_a'].GetStringSelection()
1541 new_fname =self.wids['save_as_name'].GetValue()
1542 new_gname = file2groupname(new_fname, slen=5, symtable=_larch.symtable)
1544 gmap = []
1545 for k, v in self.group_map.items():
1546 gmap.append(f'"{k}": "{v}"')
1547 gmap = '{%s}' % (', '.join(gmap))
1549 _larch.eval(SPECCALC_SAVE.format(new=new_gname, fname=new_fname,
1550 group=self.group_a.groupname,
1551 group_map=gmap,
1552 yname=self.yname, expr=self.expr))
1555 journal={'source_desc': f"{new_fname}: calc({self.expr})",
1556 'calc_groups': gmap, 'calc_expression': self.expr}
1558 ngroup = getattr(_larch.symtable, new_gname, None)
1559 if ngroup is not None:
1560 self.parent.install_group(ngroup, source=journal['source_desc'],
1561 journal=journal)
1563 def GetResponse(self):
1564 raise AttributeError("use as non-modal dialog!")
1566class EnergyUnitsDialog(wx.Dialog):
1567 """dialog for selecting, changing energy units, forcing data to eV"""
1568 unit_choices = ['eV', 'keV', 'deg', 'steps']
1570 def __init__(self, parent, energy_array, unitname='eV',dspace=1, **kws):
1572 self.parent = parent
1573 self.energy = 1.0*energy_array
1575 title = "Select Energy Units to convert to 'eV'"
1576 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title)
1577 self.SetFont(Font(FONTSIZE))
1578 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT)
1580 self.en_units = Choice(panel, choices=self.unit_choices, size=(125, -1),
1581 action=self.onUnits)
1582 self.en_units.SetStringSelection(unitname)
1583 self.mono_dspace = FloatCtrl(panel, value=dspace, minval=0, maxval=100.0,
1584 precision=6, size=(125, -1))
1585 self.steps2deg = FloatCtrl(panel, value=1.0, minval=0,
1586 precision=1, size=(125, -1))
1588 self.mono_dspace.Disable()
1589 self.steps2deg.Disable()
1591 panel.Add(SimpleText(panel, 'Energy Units : '), newrow=True)
1592 panel.Add(self.en_units)
1594 panel.Add(SimpleText(panel, 'Mono D spacing : '), newrow=True)
1595 panel.Add(self.mono_dspace)
1597 panel.Add(SimpleText(panel, 'Mono Steps per Degree : '), newrow=True)
1598 panel.Add(self.steps2deg)
1599 panel.Add((5, 5))
1601 panel.Add(OkCancel(panel), dcol=2, newrow=True)
1602 panel.pack()
1604 fit_dialog_window(self, panel)
1607 def onUnits(self, event=None):
1608 units = self.en_units.GetStringSelection()
1609 self.steps2deg.Enable(units == 'steps')
1610 self.mono_dspace.Enable(units in ('steps', 'deg'))
1612 def GetResponse(self, master=None, gname=None, ynorm=True):
1613 self.Raise()
1614 response = namedtuple('EnergyUnitsResponse',
1615 ('ok', 'units', 'energy', 'dspace'))
1616 ok, units, en, dspace = False, 'eV', None, -1
1618 if self.ShowModal() == wx.ID_OK:
1619 units = self.en_units.GetStringSelection()
1620 if units == 'eV':
1621 en = self.energy
1622 elif units == 'keV':
1623 en = self.energy * 1000.0
1624 elif units in ('steps', 'deg'):
1625 dspace = float(self.mono_dspace.GetValue())
1626 if units == 'steps':
1627 self.energy /= self.steps2deg.GetValue()
1628 en = PLANCK_HC/(2*dspace*np.sin(self.energy * DEG2RAD))
1629 ok = True
1630 return response(ok, units, en, dspace)
1632class MergeDialog(wx.Dialog):
1633 """dialog for merging groups"""
1634 ychoices = ['raw mu(E)', 'normalized mu(E)']
1636 def __init__(self, parent, groupnames, outgroup='merge', **kws):
1637 title = "Merge %i Selected Groups" % (len(groupnames))
1638 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title)
1640 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT)
1642 self.master_group = Choice(panel, choices=groupnames, size=(250, -1))
1643 self.yarray_name = Choice(panel, choices=self.ychoices, size=(250, -1))
1644 self.group_name = wx.TextCtrl(panel, -1, outgroup, size=(250, -1))
1646 panel.Add(SimpleText(panel, 'Match Energy to : '), newrow=True)
1647 panel.Add(self.master_group)
1649 panel.Add(SimpleText(panel, 'Array to merge : '), newrow=True)
1650 panel.Add(self.yarray_name)
1652 panel.Add(SimpleText(panel, 'New group name : '), newrow=True)
1653 panel.Add(self.group_name)
1655 panel.Add(OkCancel(panel), dcol=2, newrow=True)
1657 panel.pack()
1658 fit_dialog_window(self, panel)
1661 def GetResponse(self, master=None, gname=None, ynorm=True):
1662 self.Raise()
1663 response = namedtuple('MergeResponse', ('ok', 'master', 'ynorm', 'group'))
1664 ok = False
1665 if self.ShowModal() == wx.ID_OK:
1666 master= self.master_group.GetStringSelection()
1667 ynorm = 'norm' in self.yarray_name.GetStringSelection().lower()
1668 gname = self.group_name.GetValue()
1669 ok = True
1670 return response(ok, master, ynorm, gname)
1673class ExportCSVDialog(wx.Dialog):
1674 """dialog for exporting groups to CSV file"""
1676 def __init__(self, parent, groupnames, **kws):
1677 title = "Export Selected Groups"
1678 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title)
1679 self.SetFont(Font(FONTSIZE))
1680 self.xchoices = {'Energy': 'energy',
1681 'k': 'k',
1682 'R': 'r',
1683 'q': 'q'}
1685 self.ychoices = {'normalized mu(E)': 'norm',
1686 'raw mu(E)': 'mu',
1687 'flattened mu(E)': 'flat',
1688 'd mu(E) / dE': 'dmude',
1689 'chi(k)': 'chi',
1690 'chi(E)': 'chie',
1691 'chi(q)': 'chiq',
1692 '|chi(R)|': 'chir_mag',
1693 'Re[chi(R)]': 'chir_re'}
1695 self.delchoices = {'comma': ',',
1696 'space': ' ',
1697 'tab': '\t'}
1699 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT)
1700 self.save_individual_files = Check(panel, default=False, label='Save individual files', action=self.onSaveIndividualFiles)
1701 self.master_group = Choice(panel, choices=groupnames, size=(200, -1))
1702 self.xarray_name = Choice(panel, choices=list(self.xchoices.keys()), size=(200, -1))
1703 self.yarray_name = Choice(panel, choices=list(self.ychoices.keys()), action=self.onYChoice, size=(200, -1))
1704 self.del_name = Choice(panel, choices=list(self.delchoices.keys()), size=(200, -1))
1706 panel.Add(self.save_individual_files, newrow=True)
1708 panel.Add(SimpleText(panel, 'Group for Energy Array: '), newrow=True)
1709 panel.Add(self.master_group)
1711 panel.Add(SimpleText(panel, 'X Array to Export: '), newrow=True)
1712 panel.Add(self.xarray_name)
1714 panel.Add(SimpleText(panel, 'Y Array to Export: '), newrow=True)
1715 panel.Add(self.yarray_name)
1717 panel.Add(SimpleText(panel, 'Delimeter for File: '), newrow=True)
1718 panel.Add(self.del_name)
1719 panel.Add(OkCancel(panel), dcol=2, newrow=True)
1720 panel.pack()
1721 fit_dialog_window(self, panel)
1723 def onYChoice(self, event=None):
1724 ychoice = self.yarray_name.GetStringSelection()
1725 yval = self.ychoices[ychoice]
1726 xarray = 'Energy'
1727 if yval in ('chi', 'chiq'):
1728 xarray = 'k'
1729 elif yval in ('chir_mag', 'chir_re'):
1730 xarray = 'R'
1731 self.xarray_name.SetStringSelection(xarray)
1733 def onSaveIndividualFiles(self, event=None):
1734 save_individual = self.save_individual_files.IsChecked()
1735 self.master_group.Enable(not save_individual)
1737 def GetResponse(self, master=None, gname=None, ynorm=True):
1738 self.Raise()
1739 response = namedtuple('ExportCSVResponse',
1740 ('ok', 'individual', 'master', 'xarray', 'yarray', 'delim'))
1741 ok = False
1742 individual = master = ''
1743 xarray, yarray, delim = 'Energy', '', ','
1744 if self.ShowModal() == wx.ID_OK:
1745 individual = self.save_individual_files.IsChecked()
1746 master = self.master_group.GetStringSelection()
1747 xarray = self.xchoices[self.xarray_name.GetStringSelection()]
1748 yarray = self.ychoices[self.yarray_name.GetStringSelection()]
1749 delim = self.delchoices[self.del_name.GetStringSelection()]
1750 ok = True
1751 return response(ok, individual, master, xarray, yarray, delim)
1753class QuitDialog(wx.Dialog):
1754 """dialog for quitting, prompting to save project"""
1756 def __init__(self, parent, message, **kws):
1757 title = "Quit Larch Larix?"
1758 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title, size=(500, 150))
1759 self.SetFont(Font(FONTSIZE))
1760 self.needs_save = True
1761 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT)
1763 status, filename, stime = message
1764 warn_msg = 'All work in this session will be lost!'
1766 panel.Add((5, 5))
1767 if len(stime) > 2:
1768 status = f"{status} at {stime} to file"
1769 warn_msg = 'Changes made after that will be lost!'
1771 panel.Add(wx.StaticText(panel, label=status), dcol=2)
1773 if len(filename) > 0:
1774 if filename.startswith("'") and filename.endswith("'"):
1775 filename = filename[1:-1]
1776 panel.Add((15, 5), newrow=True)
1777 panel.Add(wx.StaticText(panel, label=filename), dcol=2)
1779 panel.Add((5, 5), newrow=True)
1780 panel.Add(wx.StaticText(panel, label=warn_msg), dcol=2)
1781 panel.Add(HLine(panel, size=(500, 3)), dcol=3, newrow=True)
1782 panel.Add((5, 5), newrow=True)
1783 panel.Add(OkCancel(panel), dcol=2, newrow=True)
1784 panel.pack()
1786 fit_dialog_window(self, panel)
1788 def GetResponse(self):
1789 self.Raise()
1790 response = namedtuple('QuitResponse', ('ok',))
1791 ok = (self.ShowModal() == wx.ID_OK)
1792 return response(ok,)
1794class RenameDialog(wx.Dialog):
1795 """dialog for renaming group"""
1796 def __init__(self, parent, oldname, **kws):
1797 title = "Rename Group %s" % (oldname)
1798 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title)
1799 self.SetFont(Font(FONTSIZE))
1800 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT)
1802 self.newname = wx.TextCtrl(panel, -1, oldname, size=(250, -1))
1804 panel.Add(SimpleText(panel, 'Old Name : '), newrow=True)
1805 panel.Add(SimpleText(panel, oldname))
1806 panel.Add(SimpleText(panel, 'New Name : '), newrow=True)
1807 panel.Add(self.newname)
1808 panel.Add(OkCancel(panel), dcol=2, newrow=True)
1810 panel.pack()
1811 fit_dialog_window(self, panel)
1814 def GetResponse(self, newname=None):
1815 self.Raise()
1816 response = namedtuple('RenameResponse', ('ok', 'newname'))
1817 ok = False
1818 if self.ShowModal() == wx.ID_OK:
1819 newname = self.newname.GetValue()
1820 ok = True
1821 return response(ok, newname)
1823class RemoveDialog(wx.Dialog):
1824 """dialog for removing groups"""
1825 def __init__(self, parent, grouplist, **kws):
1826 title = "Remove %i Selected Group" % len(grouplist)
1827 self.grouplist = grouplist
1828 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title)
1829 self.SetFont(Font(FONTSIZE))
1830 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT)
1832 panel.Add(SimpleText(panel, 'Remove %i Selected Groups?' % (len(grouplist))),
1833 newrow=True, dcol=2)
1835 panel.Add(OkCancel(panel), dcol=2, newrow=True)
1836 panel.pack()
1837 fit_dialog_window(self, panel)
1839 def GetResponse(self, ngroups=None):
1840 self.Raise()
1841 response = namedtuple('RemoveResponse', ('ok','ngroups'))
1842 ok = False
1843 if self.ShowModal() == wx.ID_OK:
1844 ngroups = len(self.grouplist)
1845 ok = True
1846 return response(ok, ngroups)
1849class LoadSessionDialog(wx.Frame):
1850 """Read, show data from saved larch session"""
1852 xasgroups_name = '_xasgroups'
1853 feffgroups_name = ['_feffpaths', '_feffcache']
1855 def __init__(self, parent, session, filename, controller, **kws):
1856 self.parent = parent
1857 self.session = session
1858 self.filename = filename
1859 self.controller = controller
1860 title = f"Read Larch Session from '{filename}'"
1861 wx.Frame.__init__(self, parent, wx.ID_ANY, title=title)
1863 x0, y0 = parent.GetPosition()
1864 self.SetPosition((x0+450, y0+75))
1866 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
1867 splitter.SetMinimumPaneSize(250)
1869 leftpanel = wx.Panel(splitter)
1870 rightpanel = wx.Panel(splitter)
1872 ltop = wx.Panel(leftpanel)
1874 sel_none = Button(ltop, 'Select None', size=(100, 30), action=self.onSelNone)
1875 sel_all = Button(ltop, 'Select All', size=(100, 30), action=self.onSelAll)
1876 sel_imp = Button(ltop, 'Import Selected Data', size=(200, 30),
1877 action=self.onImport)
1879 self.select_imported = sel_imp
1880 self.grouplist = FileCheckList(leftpanel, select_action=self.onShowGroup)
1881 set_color(self.grouplist, 'list_fg', bg='list_bg')
1883 tsizer = wx.GridBagSizer(2, 2)
1884 tsizer.Add(sel_all, (0, 0), (1, 1), LEFT, 0)
1885 tsizer.Add(sel_none, (0, 1), (1, 1), LEFT, 0)
1886 tsizer.Add(sel_imp, (1, 0), (1, 2), LEFT, 0)
1888 pack(ltop, tsizer)
1890 sizer = wx.BoxSizer(wx.VERTICAL)
1891 sizer.Add(ltop, 0, LEFT|wx.GROW, 1)
1892 sizer.Add(self.grouplist, 1, LEFT|wx.GROW|wx.ALL, 1)
1893 pack(leftpanel, sizer)
1896 panel = GridPanel(rightpanel, ncols=3, nrows=4, pad=2, itemstyle=LEFT)
1897 self.wids = wids = {}
1899 top_message = 'Larch Session File: No XAFS Groups'
1900 symtable = controller.symtable
1902 self.allgroups = session.symbols.get(self.xasgroups_name, {})
1903 self.extra_groups = []
1904 for key, val in session.symbols.items():
1905 if key == self.xasgroups_name or key in self.feffgroups_name:
1906 continue
1907 if key in self.allgroups:
1908 continue
1909 if hasattr(val, 'energy') and hasattr(val, 'mu'):
1910 if key in self.allgroups.keys() or key in self.allgroups.values():
1911 continue
1912 self.allgroups[key] = key
1913 self.extra_groups.append(key)
1916 checked = []
1917 for fname, gname in self.allgroups.items():
1918 self.grouplist.Append(fname)
1919 checked.append(fname)
1921 self.grouplist.SetCheckedStrings(checked)
1923 group_names = list(self.allgroups.values())
1924 group_names.append(self.xasgroups_name)
1925 group_names.extend(self.feffgroups_name)
1927 wids['view_conf'] = Button(panel, 'Show Session Configuration',
1928 size=(200, 30), action=self.onShowConfig)
1929 wids['view_cmds'] = Button(panel, 'Show Session Commands',
1930 size=(200, 30), action=self.onShowCommands)
1932 wids['plotopt'] = Choice(panel, choices=list(SESSION_PLOTS.keys()),
1933 action=self.onPlotChoice, size=(175, -1))
1935 panel.Add(wids['view_conf'], dcol=1)
1936 panel.Add(wids['view_cmds'], dcol=1, newrow=False)
1937 panel.Add(HLine(panel, size=(450, 2)), dcol=3, newrow=True)
1939 over_msg = 'Importing these Groups/Data will overwrite values in the current session:'
1940 panel.Add(SimpleText(panel, over_msg), dcol=2, newrow=True)
1941 panel.Add(SimpleText(panel, "Symbol Name"), dcol=1, newrow=True)
1942 panel.Add(SimpleText(panel, "Import/Overwrite?"), dcol=1)
1943 i = 0
1944 self.overwrite_checkboxes = {}
1945 for g in self.session.symbols:
1946 if g not in group_names and hasattr(symtable, g):
1947 chbox = Check(panel, default=True)
1948 panel.Add(SimpleText(panel, g), dcol=1, newrow=True)
1949 panel.Add(chbox, dcol=1)
1950 self.overwrite_checkboxes[g] = chbox
1952 i += 1
1954 panel.Add((5, 5), newrow=True)
1955 panel.Add(HLine(panel, size=(450, 2)), dcol=3, newrow=True)
1956 panel.Add(SimpleText(panel, 'Plot Type:'), newrow=True)
1957 panel.Add(wids['plotopt'], dcol=2, newrow=False)
1958 panel.pack()
1960 self.plotpanel = PlotPanel(rightpanel, messenger=self.plot_messages)
1961 self.plotpanel.SetSize((475, 450))
1962 plotconf = self.controller.get_config('plot')
1963 self.plotpanel.conf.set_theme(plotconf['theme'])
1964 self.plotpanel.conf.enable_grid(plotconf['show_grid'])
1966 sizer = wx.BoxSizer(wx.VERTICAL)
1967 sizer.Add(panel, 0, LEFT, 2)
1968 sizer.Add(self.plotpanel, 1, LEFT, 2)
1970 pack(rightpanel, sizer)
1972 splitter.SplitVertically(leftpanel, rightpanel, 1)
1973 self.SetSize((750, 725))
1975 self.Show()
1976 self.Raise()
1978 def plot_messages(self, msg, panel=1):
1979 pass
1981 def onSelAll(self, event=None):
1982 self.grouplist.SetCheckedStrings(list(self.allgroups.keys()))
1984 def onSelNone(self, event=None):
1985 self.grouplist.SetCheckedStrings([])
1987 def onShowGroup(self, event=None):
1988 """column selections changed calc xplot and yplot"""
1989 fname = event.GetString()
1990 gname = self.allgroups.get(fname, None)
1991 if gname in self.session.symbols:
1992 self.plot_group(gname, fname)
1994 def onPlotChoice(self, event=None):
1995 fname = self.grouplist.GetStringSelection()
1996 gname = self.allgroups.get(fname, None)
1997 self.plot_group(gname, fname)
1999 def plot_group(self, gname, fname):
2000 grp = self.session.symbols[gname]
2001 plottype = SESSION_PLOTS.get(self.wids['plotopt'].GetStringSelection(), 'norm')
2002 xdef = np.zeros(1)
2003 xplot = getattr(grp, 'energy', xdef)
2004 yplot = getattr(grp, 'mu', xdef)
2005 xlabel = plotlabels.energy
2006 ylabel = plotlabels.mu
2007 if plottype == 'norm' and hasattr(grp, 'norm'):
2008 yplot = getattr(grp, 'norm', xdef)
2009 ylabel = plotlabels.norm
2010 elif plottype == 'chikw' and hasattr(grp, 'chi'):
2011 xplot = getattr(grp, 'k', xdef)
2012 yplot = getattr(grp, 'chi', xdef)
2013 yplot = yplot*xplot*xplot
2014 xlabel = plotlabels.chikw.format(2)
2016 if len(yplot) > 1:
2017 self.plotpanel.plot(xplot, yplot, xlabel=xlabel,
2018 ylabel=ylabel, title=fname)
2021 def onShowConfig(self, event=None):
2022 DictFrame(parent=self.parent,
2023 data=self.session.config,
2024 title=f"Session Configuration for '{self.filename}'")
2026 def onShowCommands(self, event=None):
2027 oname = self.filename.replace('.larix', '.lar')
2028 wildcard='Larch Command Files (*.lar)|*.lar'
2029 text = '\n'.join(self.session.command_history)
2030 ReportFrame(parent=self.parent,
2031 text=text,
2032 title=f"Session Commands from '{self.filename}'",
2033 default_filename=oname,
2034 wildcard=wildcard)
2036 def onClose(self, event=None):
2037 self.Destroy()
2039 def onImport(self, event=None):
2040 ignore = []
2041 for gname, chbox in self.overwrite_checkboxes.items():
2042 if not chbox.IsChecked():
2043 ignore.append(gname)
2045 sel_groups = self.grouplist.GetCheckedStrings()
2046 for fname, gname in self.allgroups.items():
2047 if fname not in sel_groups:
2048 ignore.append(gname)
2050 fname = Path(self.filename).as_posix()
2051 if fname.endswith('/'):
2052 fname = fname[:-1]
2053 lcmd = [f"load_session('{fname}'"]
2054 if len(ignore) > 0:
2055 ignore = repr(ignore)
2056 lcmd.append(f", ignore_groups={ignore}")
2057 if len(self.extra_groups) > 0:
2058 extra = repr(self.extra_groups)
2059 lcmd.append(f", include_xasgroups={extra}")
2061 lcmd = ''.join(lcmd) + ')'
2063 cmds = ["# Loading Larch Session with ", lcmd, '######']
2065 self.controller.larch.eval('\n'.join(cmds))
2066 last_fname = None
2067 xasgroups = getattr(self.controller.symtable, self.xasgroups_name, {})
2068 for key, val in xasgroups.items():
2069 if key not in self.controller.filelist.GetItems():
2070 self.controller.filelist.Append(key)
2071 last_fname = key
2073 self.controller.recentfiles.append((time.time(), self.filename))
2075 wx.CallAfter(self.Destroy)
2076 if last_fname is not None:
2077 self.parent.ShowFile(filename=last_fname)