Coverage for /Users/Newville/Codes/xraylarch/larch/wxxas/xas_dialogs.py: 10%

1368 statements  

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

1import sys 

2import os 

3import copy 

4import time 

5from collections import namedtuple 

6from functools import partial 

7import numpy as np 

8from lmfit import Parameters, minimize, fit_report 

9from matplotlib.ticker import FuncFormatter 

10 

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.io import unixpath 

18 

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) 

25 

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 

29 

30Plot_Choices = {'Normalized': 'norm', 'Derivative': 'dmude'} 

31 

32 

33EDGE_LIST = ('K', 'L3', 'L2', 'L1', 'M5', 'M4', 'M3') 

34 

35NORM_MU = 'Normalized \u03BC(E)' 

36 

37DEGLITCH_PLOTS = {'Raw \u03BC(E)': 'mu', 

38 NORM_MU: 'norm', 

39 '\u03c7(E)': 'chie', 

40 '\u03c7(E)*(E-E_0)': 'chiew'} 

41 

42SESSION_PLOTS = {'Normalized \u03BC(E)': 'norm', 

43 'Raw \u03BC(E)': 'mu', 

44 'k^2\u03c7(k)': 'chikw'} 

45 

46 

47def ensure_en_orig(dgroup): 

48 if not hasattr(dgroup, 'energy_orig'): 

49 dgroup.energy_orig = dgroup.energy[:] 

50 

51 

52 

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)) 

61 

62 

63 

64def add_floatspin(name, value, panel, with_pin=True, xasmain=None, 

65 callback=None, relative_e0=False, **kws): 

66 """create FloatSpin with Pin button for onSelPoint""" 

67 if with_pin and xasmain is not None: 

68 pin_action = partial(xasmain.onSelPoint, opt=name, 

69 relative_e0=relative_e0, 

70 callback=callback) 

71 fspin, pinb = FloatSpinWithPin(panel, value=value, 

72 pin_action=pin_action, **kws) 

73 else: 

74 fspin = FloatSpin(panel, value=value, **kws) 

75 pinb = None 

76 return fspin, pinb 

77 

78 

79def get_view_limits(ppanel): 

80 "get last zoom limits for a plot panel" 

81 xlim = ppanel.axes.get_xlim() 

82 ylim = ppanel.axes.get_ylim() 

83 return (xlim, ylim) 

84 

85def set_view_limits(ppanel, xlim, ylim): 

86 "set zoom limits for a plot panel, as found from get_view_limits" 

87 ppanel.axes.set_xlim(xlim, emit=True) 

88 ppanel.axes.set_ylim(ylim, emit=True) 

89 

90 

91class OverAbsorptionDialog(wx.Dialog): 

92 """dialog for correcting over-absorption""" 

93 def __init__(self, parent, controller, **kws): 

94 self.parent = parent 

95 self.controller = controller 

96 self.dgroup = self.controller.get_group() 

97 groupnames = list(self.controller.file_groups.keys()) 

98 

99 self.data = [self.dgroup.energy[:], self.dgroup.norm[:]] 

100 

101 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400), 

102 title="Correct Over-absorption") 

103 self.SetFont(Font(FONTSIZE)) 

104 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT) 

105 self.wids = wids = {} 

106 

107 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1), 

108 action=self.on_groupchoice) 

109 

110 wids['grouplist'].SetStringSelection(self.dgroup.filename) 

111 

112 opts = dict(size=(90, -1), precision=1, act_on_losefocus=True, 

113 minval=-90, maxval=180) 

114 

115 fs_opts = dict(size=(90, -1), value=45, digits=1, increment=1) 

116 wids['phi_in'] = FloatSpin(panel, **fs_opts) 

117 wids['phi_out'] = FloatSpin(panel, **fs_opts) 

118 

119 wids['elem'] = Choice(panel, choices=ATOM_SYMS[:98], size=(50, -1)) 

120 wids['edge'] = Choice(panel, choices=EDGE_LIST, size=(50, -1)) 

121 

122 wids['formula'] = wx.TextCtrl(panel, -1, '', size=(250, -1)) 

123 

124 self.set_default_elem_edge(self.dgroup) 

125 

126 #wids['apply'] = Button(panel, 'Save / Overwrite', size=(150, -1), 

127 # action=self.on_apply) 

128 #SetTip(wids['apply'], 'Save corrected data, overwrite current arrays') 

129 

130 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1), 

131 action=self.on_saveas) 

132 SetTip(wids['save_as'], 'Save corrected data as new group') 

133 

134 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_abscorr', 

135 size=(250, -1)) 

136 wids['correct'] = Button(panel, 'Do Correction', 

137 size=(150, -1), action=self.on_correct) 

138 SetTip(wids['correct'], 'Calculate Correction') 

139 

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

141 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow) 

142 

143 add_text(' Correction for Group: ', newrow=False) 

144 panel.Add(wids['grouplist'], dcol=5) 

145 

146 add_text(' Absorbing Element: ') 

147 panel.Add(wids['elem']) 

148 

149 add_text(' Edge: ', newrow=False) 

150 panel.Add(wids['edge']) 

151 

152 add_text(' Material Formula: ') 

153 panel.Add(wids['formula'], dcol=3) 

154 

155 add_text(' Incident Angle (deg): ') 

156 panel.Add(wids['phi_in']) 

157 

158 add_text(' Exit Angle (deg): ') 

159 panel.Add(wids['phi_out']) 

160 

161 panel.Add(wids['correct'], newrow=True) 

162 # panel.Add(wids['apply'], dcol=2, newrow=True) 

163 

164 panel.Add(wids['save_as'], newrow=True) 

165 panel.Add(wids['save_as_name'], dcol=3) 

166 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone), 

167 newrow=True) 

168 panel.pack() 

169 fit_dialog_window(self, panel) 

170 self.plot_results(keep_limits=False) 

171 

172 

173 def onDone(self, event=None): 

174 self.Destroy() 

175 

176 def set_default_elem_edge(self, dgroup): 

177 elem, edge = guess_edge(dgroup.e0) 

178 self.wids['elem'].SetStringSelection(elem) 

179 self.wids['edge'].SetStringSelection(edge) 

180 

181 def on_groupchoice(self, event=None): 

182 fname = self.wids['grouplist'].GetStringSelection() 

183 self.dgroup = self.controller.get_group(fname) 

184 self.set_default_elem_edge(self.dgroup) 

185 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_abscorr') 

186 

187 

188 def on_correct(self, event=None): 

189 wids = self.wids 

190 dgroup = self.dgroup 

191 anginp = wids['phi_in'].GetValue() 

192 angout = wids['phi_out'].GetValue() 

193 elem = wids['elem'].GetStringSelection() 

194 edge = wids['edge'].GetStringSelection() 

195 formula = wids['formula'].GetValue() 

196 if len(formula) < 1: 

197 return 

198 

199 cmd = """fluo_corr(%s.energy, %s.mu, '%s', '%s', edge='%s', group=%s, 

200 anginp=%.1f, angout=%.1f)""" % (dgroup.groupname, dgroup.groupname, 

201 formula, elem, edge, dgroup.groupname, 

202 anginp, angout) 

203 self.cmd = cmd 

204 self.controller.larch.eval(cmd) 

205 self.plot_results() 

206 

207 def on_apply(self, event=None): 

208 xdat, ydat = self.data 

209 dgroup = self.dgroup 

210 dgroup.xdat = dgroup.energy = xdat 

211 self.parent.process_normalization(dgroup) 

212 dgroup.journal.add('fluor_corr_command', self.cmd) 

213 self.plot_results() 

214 

215 def on_saveas(self, event=None): 

216 wids = self.wids 

217 fname = self.wids['grouplist'].GetStringSelection() 

218 new_fname = wids['save_as_name'].GetValue() 

219 ngroup = self.controller.copy_group(fname, new_filename=new_fname) 

220 

221 if hasattr(self.dgroup, 'norm_corr' ): 

222 ngroup.mu = ngroup.norm_corr*1.0 

223 del ngroup.norm_corr 

224 

225 ogroup = self.controller.get_group(fname) 

226 self.parent.install_group(ngroup, journal=ogroup.journal) 

227 olddesc = ogroup.journal.get('source_desc').value 

228 ngroup.journal.add('source_desc', f"fluo_corrected({olddesc})") 

229 ngroup.journal.add('fluor_correction_command', self.cmd) 

230 

231 def plot_results(self, event=None, keep_limits=True): 

232 ppanel = self.controller.get_display(stacked=False).panel 

233 

234 dgroup = self.dgroup 

235 xlim, ylim = get_view_limits(ppanel) 

236 path, fname = os.path.split(dgroup.filename) 

237 

238 opts = dict(linewidth=3, ylabel=plotlabels.norm, 

239 xlabel=plotlabels.energy, delay_draw=True, 

240 show_legend=True) 

241 

242 if self.controller.plot_erange is not None: 

243 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0] 

244 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1] 

245 

246 if not hasattr(dgroup, 'norm_corr'): 

247 dgroup.norm_corr = dgroup.norm[:] 

248 

249 ppanel.plot(dgroup.energy, dgroup.norm_corr, zorder=10, marker=None, 

250 title='Over-absorption Correction:\n %s' % fname, 

251 label='corrected', **opts) 

252 

253 ppanel.oplot(dgroup.energy, dgroup.norm, zorder=10, marker='o', 

254 markersize=3, label='original', **opts) 

255 if keep_limits: 

256 set_view_limits(ppanel, xlim, ylim) 

257 ppanel.canvas.draw() 

258 ppanel.conf.draw_legend(show=True) 

259 

260 def GetResponse(self): 

261 raise AttributeError("use as non-modal dialog!") 

262 

263 

264class EnergyCalibrateDialog(wx.Dialog): 

265 """dialog for calibrating energy""" 

266 def __init__(self, parent, controller, **kws): 

267 

268 self.parent = parent 

269 self.controller = controller 

270 self.dgroup = self.controller.get_group() 

271 groupnames = list(self.controller.file_groups.keys()) 

272 

273 ensure_en_orig(self.dgroup) 

274 

275 self.data = [self.dgroup.energy_orig[:], self.dgroup.norm[:]] 

276 xmin = min(self.dgroup.energy_orig) 

277 xmax = max(self.dgroup.energy_orig) 

278 e0val = getattr(self.dgroup, 'e0', xmin) 

279 

280 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400), 

281 title="Calibrate / Align Energy") 

282 self.SetFont(Font(FONTSIZE)) 

283 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT) 

284 

285 self.wids = wids = {} 

286 wids['grouplist'] = Choice(panel, choices=groupnames, size=(275, -1), 

287 action=self.on_groupchoice) 

288 wids['grouplist'].SetStringSelection(self.dgroup.filename) 

289 

290 refgroups = ['None'] + groupnames 

291 wids['reflist'] = Choice(panel, choices=refgroups, size=(275, -1), 

292 action=self.on_align, default=0) 

293 

294 opts = dict(size=(90, -1), digits=3, increment=0.1) 

295 

296 opts['action'] = partial(self.on_calib, name='eshift') 

297 wids['eshift'] = FloatSpin(panel, value=0, **opts) 

298 

299 self.plottype = Choice(panel, choices=list(Plot_Choices.keys()), 

300 size=(275, -1), action=self.plot_results) 

301 wids['do_align'] = Button(panel, 'Auto Align', size=(100, -1), 

302 action=self.on_align) 

303 

304 wids['apply_one'] = Button(panel, 'Apply to Current Group', size=(200, -1), 

305 action=self.on_apply_one) 

306 

307 wids['apply_sel'] = Button(panel, 'Apply to Selected Groups', 

308 size=(250, -1), action=self.on_apply_sel) 

309 SetTip(wids['apply_sel'], 'Apply the Energy Shift to all Selected Groups') 

310 

311 wids['save_as'] = Button(panel, 'Save As New Group ', size=(200, -1), 

312 action=self.on_saveas) 

313 SetTip(wids['save_as'], 'Save shifted data as new group') 

314 

315 wids['save_as_name'] = wx.TextCtrl(panel, -1, 

316 self.dgroup.filename + '_eshift', 

317 size=(275, -1)) 

318 

319 wids['sharedref_msg'] = wx.StaticText(panel, label="1 groups share this energy reference") 

320 wids['select_sharedref'] = Button(panel, 'Select Groups with shared reference', 

321 size=(300, -1), action=self.on_select_sharedrefs) 

322 

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

324 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow) 

325 

326 add_text(' Current Group: ', newrow=False) 

327 panel.Add(wids['grouplist'], dcol=2) 

328 

329 add_text(' Auto-Align to : ') 

330 panel.Add(wids['reflist'], dcol=2) 

331 

332 add_text(' Plot Arrays as: ') 

333 panel.Add(self.plottype, dcol=2) 

334 

335 add_text(' Energy Shift (eV): ') 

336 panel.Add(wids['eshift'], dcol=1) 

337 panel.Add(wids['do_align'], dcol=1) 

338 panel.Add(HLine(panel, size=(500, 3)), dcol=4, newrow=True) 

339 # panel.Add(apply_one, newrow=True) 

340 

341 panel.Add(wids['sharedref_msg'], dcol=2, newrow=True) 

342 panel.Add(wids['select_sharedref'], dcol=2) 

343 panel.Add(wids['apply_one'], dcol=1, newrow=True) 

344 panel.Add(wids['apply_sel'], dcol=2) 

345 

346 panel.Add(HLine(panel, size=(500, 3)), dcol=4, newrow=True) 

347 

348 panel.Add(wids['save_as'], newrow=True) 

349 panel.Add(wids['save_as_name'], dcol=3) 

350 

351 panel.pack() 

352 

353 fit_dialog_window(self, panel) 

354 

355 self.plot_results(keep_limits=False) 

356 wx.CallAfter(self.get_groups_shared_energyrefs) 

357 

358 def on_select(self, event=None, opt=None): 

359 _x, _y = self.controller.get_cursor() 

360 if opt in self.wids: 

361 self.wids[opt].SetValue(_x) 

362 

363 def get_groups_shared_energyrefs(self, dgroup=None): 

364 if dgroup is None: 

365 dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection()) 

366 sharedrefs = [dgroup.filename] 

367 try: 

368 eref = dgroup.config.xasnorm.get('energy_ref', None) 

369 except: 

370 eref = None 

371 if eref is None: 

372 eref = dgroup.groupname 

373 

374 

375 for key, val in self.controller.file_groups.items(): 

376 if dgroup.groupname == val: 

377 continue 

378 g = self.controller.get_group(val) 

379 try: 

380 geref = g.config.xasnorm.get('energy_ref', None) 

381 except: 

382 geref = None 

383 # print(key, val, geref, geref == ref_filename) 

384 if geref == eref or geref == dgroup.filename: 

385 sharedrefs.append(key) 

386 self.wids['sharedref_msg'].SetLabel(f" {len(sharedrefs):d} groups share this energy reference") 

387 return sharedrefs 

388 

389 def on_select_sharedrefs(self, event=None): 

390 groups = self.get_groups_shared_energyrefs() 

391 self.controller.filelist.SetCheckedStrings(groups) 

392 

393 def on_groupchoice(self, event=None): 

394 dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection()) 

395 self.dgroup = dgroup 

396 others = self.get_groups_shared_energyrefs(dgroup) 

397 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_eshift') 

398 self.plot_results() 

399 

400 def on_align(self, event=None, name=None, value=None): 

401 ref = self.controller.get_group(self.wids['reflist'].GetStringSelection()) 

402 dat = self.dgroup 

403 ensure_en_orig(dat) 

404 ensure_en_orig(ref) 

405 

406 dat.xdat = dat.energy_orig[:] 

407 ref.xdat = ref.energy_orig[:] 

408 estep = find_energy_step(dat.xdat) 

409 i1 = index_of(ref.energy_orig, ref.e0-20) 

410 i2 = index_of(ref.energy_orig, ref.e0+20) 

411 

412 def resid(pars, ref, dat, i1, i2): 

413 "fit residual" 

414 newx = dat.xdat + pars['eshift'].value 

415 scale = pars['scale'].value 

416 y = interp(newx, dat.dmude, ref.xdat, kind='cubic') 

417 return smooth(ref.xdat, y*scale-ref.dmude, xstep=estep, sigma=0.50)[i1:i2] 

418 

419 params = Parameters() 

420 params.add('eshift', value=ref.e0-dat.e0, min=-50, max=50) 

421 params.add('scale', value=1, min=0, max=50) 

422 

423 result = minimize(resid, params, args=(ref, dat, i1, i2)) 

424 eshift = result.params['eshift'].value 

425 self.wids['eshift'].SetValue(eshift) 

426 

427 ensure_en_orig(self.dgroup) 

428 xnew = self.dgroup.energy_orig + eshift 

429 self.data = xnew, self.dgroup.norm[:] 

430 self.plot_results() 

431 

432 def on_calib(self, event=None, name=None): 

433 wids = self.wids 

434 eshift = wids['eshift'].GetValue() 

435 ensure_en_orig(self.dgroup) 

436 xnew = self.dgroup.energy_orig + eshift 

437 self.data = xnew, self.dgroup.norm[:] 

438 self.plot_results() 

439 

440 def on_apply_one(self, event=None): 

441 xdat, ydat = self.data 

442 dgroup = self.dgroup 

443 eshift = self.wids['eshift'].GetValue() 

444 

445 ensure_en_orig(dgroup) 

446 

447 idx, norm_page = self.parent.get_nbpage('norm') 

448 norm_page.wids['energy_shift'].SetValue(eshift) 

449 

450 dgroup.energy_shift = eshift 

451 dgroup.xdat = dgroup.energy = eshift + dgroup.energy_orig[:] 

452 dgroup.journal.add('energy_shift ', eshift) 

453 self.parent.process_normalization(dgroup) 

454 self.plot_results() 

455 

456 def on_apply_sel(self, event=None): 

457 eshift = self.wids['eshift'].GetValue() 

458 idx, norm_page = self.parent.get_nbpage('norm') 

459 for checked in self.controller.filelist.GetCheckedStrings(): 

460 fname = self.controller.file_groups[str(checked)] 

461 dgroup = self.controller.get_group(fname) 

462 ensure_en_orig(dgroup) 

463 dgroup.energy_shift = eshift 

464 norm_page.wids['energy_shift'].SetValue(eshift) 

465 

466 dgroup.xdat = dgroup.energy = eshift + dgroup.energy_orig[:] 

467 dgroup.journal.add('energy_shift ', eshift) 

468 self.parent.process_normalization(dgroup) 

469 

470 def on_saveas(self, event=None): 

471 wids = self.wids 

472 fname = wids['grouplist'].GetStringSelection() 

473 eshift = wids['eshift'].GetValue() 

474 new_fname = wids['save_as_name'].GetValue() 

475 ngroup = self.controller.copy_group(fname, new_filename=new_fname) 

476 

477 ensure_en_orig(ngroup) 

478 ngroup.xdat = ngroup.energy = eshift + ngroup.energy_orig[:] 

479 ngroup.energy_shift = 0 

480 ngroup.energy_ref = ngroup.groupname 

481 

482 ogroup = self.controller.get_group(fname) 

483 self.parent.install_group(ngroup, journal=ogroup.journal) 

484 olddesc = ogroup.journal.get('source_desc').value 

485 ngroup.journal.add('source_desc', f"energy_shifted({olddesc}, {eshift:.4f})") 

486 ngroup.journal.add('energy_shift ', 0.0) 

487 

488 def plot_results(self, event=None, keep_limits=True): 

489 ppanel = self.controller.get_display(stacked=False).panel 

490 ppanel.oplot 

491 xnew, ynew = self.data 

492 dgroup = self.dgroup 

493 

494 xlim, ylim = get_view_limits(ppanel) 

495 path, fname = os.path.split(dgroup.filename) 

496 

497 wids = self.wids 

498 eshift = wids['eshift'].GetValue() 

499 e0_old = dgroup.e0 

500 e0_new = dgroup.e0 + eshift 

501 

502 xmin = min(e0_old, e0_new) - 25 

503 xmax = max(e0_old, e0_new) + 50 

504 

505 use_deriv = self.plottype.GetStringSelection().lower().startswith('deriv') 

506 

507 ylabel = plotlabels.norm 

508 if use_deriv: 

509 ynew = np.gradient(ynew)/np.gradient(xnew) 

510 ylabel = plotlabels.dmude 

511 

512 opts = dict(xmin=xmin, xmax=xmax, ylabel=ylabel, delay_draw=True, 

513 xlabel=plotlabels.energy, show_legend=True) 

514 

515 if self.controller.plot_erange is not None: 

516 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0] 

517 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1] 

518 

519 xold, yold = self.dgroup.energy_orig, self.dgroup.norm 

520 if use_deriv: 

521 yold = np.gradient(yold)/np.gradient(xold) 

522 

523 ppanel.plot(xold, yold, zorder=10, marker='o', markersize=3, 

524 label='original', linewidth=2, color='#1f77b4', 

525 title=f'Energy Calibration:\n {fname}', **opts) 

526 

527 ppanel.oplot(xnew, ynew, zorder=15, marker='+', markersize=3, 

528 linewidth=2, label='shifted', 

529 color='#d62728', **opts) 

530 

531 if wids['reflist'].GetStringSelection() != 'None': 

532 refgroup = self.controller.get_group(wids['reflist'].GetStringSelection()) 

533 xref, yref = refgroup.energy, refgroup.norm 

534 if use_deriv: 

535 yref = np.gradient(yref)/np.gradient(xref) 

536 

537 ppanel.oplot(xref, yref, style='solid', zorder=5, color='#2ca02c', 

538 marker=None, label=refgroup.filename, **opts) 

539 if keep_limits: 

540 set_view_limits(ppanel, xlim, ylim) 

541 ppanel.canvas.draw() 

542 

543 

544 def GetResponse(self): 

545 raise AttributeError("use as non-modal dialog!") 

546 

547class RebinDataDialog(wx.Dialog): 

548 """dialog for rebinning data to standard XAFS grid""" 

549 def __init__(self, parent, controller, **kws): 

550 

551 self.parent = parent 

552 self.controller = controller 

553 self.dgroup = self.controller.get_group() 

554 groupnames = list(self.controller.file_groups.keys()) 

555 

556 xmin = min(self.dgroup.energy) 

557 xmax = max(self.dgroup.energy) 

558 e0val = getattr(self.dgroup, 'e0', xmin) 

559 xanes_step = 0.05 * (1 + max(1, int(e0val / 2000.0))) 

560 self.data = [self.dgroup.energy[:], self.dgroup.mu[:], 

561 self.dgroup.mu*0, e0val] 

562 

563 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400), 

564 title="Rebin mu(E) Data") 

565 self.SetFont(Font(FONTSIZE)) 

566 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

567 

568 self.wids = wids = {} 

569 

570 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1), 

571 action=self.on_groupchoice) 

572 

573 wids['grouplist'].SetStringSelection(self.dgroup.groupname) 

574 

575 opts = dict(size=(90, -1), precision=3, act_on_losefocus=True) 

576 

577 wids['e0'] = FloatCtrl(panel, value=e0val, minval=xmin, maxval=xmax, **opts) 

578 pre1 = 10.0*(1+int((xmin-e0val)/10.0)) 

579 wids['pre1'] = FloatCtrl(panel, value=pre1, **opts) 

580 wids['pre2'] = FloatCtrl(panel, value=-15, **opts) 

581 wids['xanes1'] = FloatCtrl(panel, value=-15, **opts) 

582 wids['xanes2'] = FloatCtrl(panel, value=15, **opts) 

583 wids['exafs1'] = FloatCtrl(panel, value=etok(15), **opts) 

584 wids['exafs2'] = FloatCtrl(panel, value=etok(xmax-e0val), **opts) 

585 

586 wids['pre_step'] = FloatCtrl(panel, value=2.0, **opts) 

587 wids['xanes_step'] = FloatCtrl(panel, value=xanes_step, **opts) 

588 wids['exafs_step'] = FloatCtrl(panel, value=0.05, **opts) 

589 

590 for wname, wid in wids.items(): 

591 if wname != 'grouplist': 

592 wid.SetAction(partial(self.on_rebin, name=wname)) 

593 

594 #wids['apply'] = Button(panel, 'Save / Overwrite', size=(150, -1), 

595 # action=self.on_apply) 

596 #SetTip(wids['apply'], 'Save rebinned data, overwrite current arrays') 

597 

598 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1), 

599 action=self.on_saveas) 

600 SetTip(wids['save_as'], 'Save corrected data as new group') 

601 

602 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_rebin', 

603 size=(250, -1)) 

604 

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

606 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow) 

607 

608 add_text('Rebin Data for Group: ', dcol=2, newrow=False) 

609 panel.Add(wids['grouplist'], dcol=3) 

610 

611 add_text('E0: ') 

612 panel.Add(wids['e0']) 

613 add_text(' eV', newrow=False) 

614 

615 add_text('Region ') 

616 add_text('Start ', newrow=False) 

617 add_text('Stop ', newrow=False) 

618 add_text('Step ', newrow=False) 

619 add_text('Units ', newrow=False) 

620 

621 add_text('Pre-Edge: ') 

622 panel.Add(wids['pre1']) 

623 panel.Add(wids['pre2']) 

624 panel.Add(wids['pre_step']) 

625 add_text(' eV', newrow=False) 

626 

627 add_text('XANES: ') 

628 panel.Add(wids['xanes1']) 

629 panel.Add(wids['xanes2']) 

630 panel.Add(wids['xanes_step']) 

631 add_text(' eV', newrow=False) 

632 

633 add_text('EXAFS: ') 

634 panel.Add(wids['exafs1']) 

635 panel.Add(wids['exafs2']) 

636 panel.Add(wids['exafs_step']) 

637 add_text('1/\u212B', newrow=False) 

638 

639 # panel.Add(wids['apply'], dcol=2, newrow=True) 

640 panel.Add(wids['save_as'], dcol=2, newrow=True) 

641 panel.Add(wids['save_as_name'], dcol=3) 

642 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone), 

643 newrow=True) 

644 panel.pack() 

645 

646 fit_dialog_window(self, panel) 

647 

648 self.on_rebin() 

649 self.plot_results(keep_limits=False) 

650 

651 def onDone(self, event=None): 

652 self.Destroy() 

653 

654 def on_groupchoice(self, event=None): 

655 self.dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection()) 

656 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_rebin') 

657 self.plot_results() 

658 

659 def on_rebin(self, event=None, name=None, value=None): 

660 wids = self.wids 

661 if name == 'pre2': 

662 val = wids['pre2'].GetValue() 

663 wids['xanes1'].SetValue(val, act=False) 

664 elif name == 'xanes1': 

665 val = wids['xanes1'].GetValue() 

666 wids['pre2'].SetValue(val, act=False) 

667 elif name == 'xanes2': 

668 val = wids['xanes2'].GetValue() 

669 wids['exafs1'].SetValue(etok(val), act=False) 

670 elif name == 'exafs1': 

671 val = wids['exafs1'].GetValue() 

672 wids['xanes2'].SetValue(ktoe(val), act=False) 

673 

674 e0 = wids['e0'].GetValue() 

675 args = dict(group=self.dgroup.groupname, e0=e0, 

676 pre1=wids['pre1'].GetValue(), 

677 pre2=wids['pre2'].GetValue(), 

678 pre_step=wids['pre_step'].GetValue(), 

679 exafs1=ktoe(wids['exafs1'].GetValue()), 

680 exafs2=ktoe(wids['exafs2'].GetValue()), 

681 exafs_kstep=wids['exafs_step'].GetValue(), 

682 xanes_step=wids['xanes_step'].GetValue()) 

683 

684 # do rebin: 

685 cmd = """rebin_xafs({group}, e0={e0:f}, pre1={pre1:f}, pre2={pre2:f}, 

686 pre_step={pre_step:f}, xanes_step={xanes_step:f}, exafs1={exafs1:f}, 

687 exafs2={exafs2:f}, exafs_kstep={exafs_kstep:f})""".format(**args) 

688 self.cmd = cmd 

689 self.controller.larch.eval(cmd) 

690 

691 if hasattr(self.dgroup, 'rebinned'): 

692 xnew = self.dgroup.rebinned.energy 

693 ynew = self.dgroup.rebinned.mu 

694 yerr = self.dgroup.rebinned.delta_mu 

695 self.data = xnew, ynew, yerr, e0 

696 self.plot_results() 

697 

698 def on_apply(self, event=None): 

699 xdat, ydat, yerr, e0 = self.data 

700 dgroup = self.dgroup 

701 dgroup.energy = dgroup.xdat = xdat 

702 dgroup.mu = dgroup.ydat = ydat 

703 dgroup.journal.add('rebin_command ', self.cmd) 

704 self.parent.process_normalization(dgroup) 

705 self.plot_results() 

706 

707 def on_saveas(self, event=None): 

708 wids = self.wids 

709 fname = wids['grouplist'].GetStringSelection() 

710 new_fname = wids['save_as_name'].GetValue() 

711 ngroup = self.controller.copy_group(fname, new_filename=new_fname) 

712 xdat, ydat, yerr, de0 = self.data 

713 ngroup.energy = ngroup.xdat = xdat 

714 ngroup.mu = ngroup.ydat = ydat 

715 

716 ogroup = self.controller.get_group(fname) 

717 olddesc = ogroup.journal.get('source_desc').value 

718 

719 ngroup.delta_mu = getattr(ngroup, 'yerr', 1.0) 

720 self.parent.process_normalization(ngroup) 

721 

722 self.parent.install_group(ngroup, journal=ogroup.journal) 

723 ngroup.journal.add('source_desc', f"rebinned({olddesc})") 

724 ngroup.journal.add('rebin_command ', self.cmd) 

725 

726 def on_done(self, event=None): 

727 self.Destroy() 

728 

729 def plot_results(self, event=None, keep_limits=True): 

730 ppanel = self.controller.get_display(stacked=False).panel 

731 xnew, ynew, yerr, e0 = self.data 

732 dgroup = self.dgroup 

733 xlim, ylim = get_view_limits(ppanel) 

734 path, fname = os.path.split(dgroup.filename) 

735 

736 opts = {'delay_draw': True} 

737 if self.controller.plot_erange is not None: 

738 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0] 

739 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1] 

740 

741 ppanel.plot(xnew, ynew, zorder=20, marker='square', 

742 linewidth=3, title='Enegy rebinning:\n %s' % fname, 

743 label='rebinned', xlabel=plotlabels.energy, 

744 ylabel=plotlabels.mu, **opts) 

745 

746 xold, yold = self.dgroup.energy, self.dgroup.mu 

747 ppanel.oplot(xold, yold, zorder=10, 

748 marker='o', markersize=4, linewidth=2.0, 

749 label='original', show_legend=True, **opts) 

750 if keep_limits: 

751 set_view_limits(ppanel, xlim, ylim) 

752 ppanel.canvas.draw() 

753 

754 def GetResponse(self): 

755 raise AttributeError("use as non-modal dialog!") 

756 

757class SmoothDataDialog(wx.Dialog): 

758 """dialog for smoothing data""" 

759 def __init__(self, parent, controller, **kws): 

760 

761 self.parent = parent 

762 self.controller = controller 

763 self.dgroup = self.controller.get_group() 

764 groupnames = list(self.controller.file_groups.keys()) 

765 

766 self.data = [self.dgroup.energy[:], self.dgroup.mu[:]] 

767 

768 

769 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400), 

770 title="Smooth mu(E) Data") 

771 self.SetFont(Font(FONTSIZE)) 

772 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT) 

773 

774 self.wids = wids = {} 

775 

776 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1), 

777 action=self.on_groupchoice) 

778 

779 wids['grouplist'].SetStringSelection(self.dgroup.filename) 

780 SetTip(wids['grouplist'], 'select a new group, clear undo history') 

781 

782 smooth_ops = ('None', 'Boxcar', 'Savitzky-Golay', 'Convolution') 

783 conv_ops = ('Lorenztian', 'Gaussian') 

784 

785 self.smooth_op = Choice(panel, choices=smooth_ops, size=(150, -1), 

786 action=self.on_smooth) 

787 self.smooth_op.SetSelection(0) 

788 

789 self.conv_op = Choice(panel, choices=conv_ops, size=(150, -1), 

790 action=self.on_smooth) 

791 self.conv_op.SetSelection(0) 

792 

793 opts = dict(size=(50, -1), act_on_losefocus=True, odd_only=False) 

794 

795 self.sigma = FloatSpin(panel, value=1, digits=2, min_val=0, increment=0.1, 

796 size=(60, -1), action=self.on_smooth) 

797 

798 self.par_n = FloatSpin(panel, value=2, digits=0, min_val=1, increment=1, 

799 size=(60, -1), action=self.on_smooth) 

800 

801 self.par_o = FloatSpin(panel, value=1, digits=0, min_val=1, increment=1, 

802 size=(60, -1), action=self.on_smooth) 

803 

804 # for fc in (self.sigma, self.par_n, self.par_o): 

805 # self.sigma.SetAction(self.on_smooth) 

806 

807 self.message = SimpleText(panel, label=' ', size=(200, -1)) 

808 

809 

810 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1), 

811 action=self.on_saveas) 

812 SetTip(wids['save_as'], 'Save corrected data as new group') 

813 

814 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_smooth', 

815 size=(250, -1)) 

816 

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

818 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow) 

819 

820 add_text('Smooth Data for Group: ', newrow=False) 

821 panel.Add(wids['grouplist'], dcol=5) 

822 

823 add_text('Smoothing Method: ') 

824 panel.Add(self.smooth_op) 

825 add_text(' n = ', newrow=False) 

826 panel.Add(self.par_n) 

827 add_text(' order= ', newrow=False) 

828 panel.Add(self.par_o) 

829 

830 add_text('Convolution Form: ') 

831 panel.Add(self.conv_op) 

832 add_text(' sigma: ', newrow=False) 

833 panel.Add(self.sigma) 

834 

835 panel.Add((10, 10), newrow=True) 

836 panel.Add(self.message, dcol=5) 

837 

838 # panel.Add(wids['apply'], newrow=True) 

839 

840 panel.Add(wids['save_as'], newrow=True) 

841 panel.Add(wids['save_as_name'], dcol=5) 

842 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone), 

843 newrow=True) 

844 panel.pack() 

845 fit_dialog_window(self, panel) 

846 

847 self.plot_results(keep_limits=False) 

848 

849 def onDone(self, event=None): 

850 self.Destroy() 

851 

852 

853 def on_groupchoice(self, event=None): 

854 self.dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection()) 

855 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_smooth') 

856 self.plot_results() 

857 

858 def on_smooth(self, event=None, value=None): 

859 smoothop = self.smooth_op.GetStringSelection().lower() 

860 

861 convop = self.conv_op.GetStringSelection() 

862 self.conv_op.Enable(smoothop.startswith('conv')) 

863 self.sigma.Enable(smoothop.startswith('conv')) 

864 self.message.SetLabel('') 

865 self.par_n.SetMin(1) 

866 self.par_n.odd_only = False 

867 par_n = int(self.par_n.GetValue()) 

868 par_o = int(self.par_o.GetValue()) 

869 sigma = self.sigma.GetValue() 

870 cmd = '{group:s}.mu' # No smoothing 

871 estep = find_energy_step(self.data[0]) 

872 if smoothop.startswith('box'): 

873 self.par_n.Enable() 

874 cmd = "boxcar({group:s}.mu, {par_n:d})" 

875 self.conv_op.Disable() 

876 elif smoothop.startswith('savi'): 

877 self.par_n.Enable() 

878 self.par_n.odd_only = True 

879 self.par_o.Enable() 

880 

881 x0 = max(par_o + 1, par_n) 

882 if x0 % 2 == 0: 

883 x0 += 1 

884 self.par_n.SetMin(par_o + 1) 

885 if par_n != x0: 

886 self.par_n.SetValue(x0) 

887 self.message.SetLabel('n must odd and > order+1') 

888 

889 cmd = "savitzky_golay({group:s}.mu, {par_n:d}, {par_o:d})" 

890 

891 elif smoothop.startswith('conv'): 

892 cmd = "smooth({group:s}.energy, {group:s}.mu, xstep={estep:f}, sigma={sigma:f}, form='{convop:s}')" 

893 self.cmd = cmd.format(group=self.dgroup.groupname, convop=convop, 

894 estep=estep, sigma=sigma, par_n=par_n, par_o=par_o) 

895 

896 self.controller.larch.eval("_tmpy = %s" % self.cmd) 

897 self.data = self.dgroup.energy[:], self.controller.symtable._tmpy 

898 self.plot_results() 

899 

900 def on_apply(self, event=None): 

901 xdat, ydat = self.data 

902 dgroup = self.dgroup 

903 dgroup.energy = xdat 

904 dgroup.mu = ydat 

905 dgroup.journal.add('smooth_command', self.cmd) 

906 self.parent.process_normalization(dgroup) 

907 self.plot_results() 

908 

909 def on_saveas(self, event=None): 

910 wids = self.wids 

911 fname = wids['grouplist'].GetStringSelection() 

912 new_fname = wids['save_as_name'].GetValue() 

913 ngroup = self.controller.copy_group(fname, new_filename=new_fname) 

914 

915 xdat, ydat = self.data 

916 ngroup.energy = ngroup.xdat = xdat 

917 ngroup.mu = ngroup.ydat = ydat 

918 

919 ogroup = self.controller.get_group(fname) 

920 olddesc = ogroup.journal.get('source_desc').value 

921 

922 self.parent.install_group(ngroup, journal=ogroup.journal) 

923 ngroup.journal.add('source_desc', f"smoothed({olddesc})") 

924 ngroup.journal.add('smooth_command', self.cmd) 

925 self.parent.process_normalization(ngroup) 

926 

927 def on_done(self, event=None): 

928 self.Destroy() 

929 

930 def plot_results(self, event=None, keep_limits=True): 

931 ppanel = self.controller.get_display(stacked=False).panel 

932 xnew, ynew = self.data 

933 dgroup = self.dgroup 

934 path, fname = os.path.split(dgroup.filename) 

935 opts = {'delay_draw': True} 

936 xlim, ylim = get_view_limits(ppanel) 

937 

938 if self.controller.plot_erange is not None: 

939 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0] 

940 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1] 

941 

942 ppanel.plot(xnew, ynew, zorder=20, marker=None, 

943 linewidth=3, title='Smoothing:\n %s' % fname, 

944 label='smoothed', xlabel=plotlabels.energy, 

945 ylabel=plotlabels.mu, **opts) 

946 

947 xold, yold = self.dgroup.energy, self.dgroup.mu 

948 ppanel.oplot(xold, yold, zorder=10, 

949 marker='o', markersize=4, linewidth=2.0, 

950 label='original', show_legend=True, **opts) 

951 if keep_limits: 

952 set_view_limits(ppanel, xlim, ylim) 

953 ppanel.canvas.draw() 

954 

955 def GetResponse(self): 

956 raise AttributeError("use as non-modal dialog!") 

957 

958class DeconvolutionDialog(wx.Dialog): 

959 """dialog for energy deconvolution""" 

960 def __init__(self, parent, controller, **kws): 

961 

962 self.parent = parent 

963 self.controller = controller 

964 self.dgroup = self.controller.get_group() 

965 groupnames = list(self.controller.file_groups.keys()) 

966 

967 self.data = [self.dgroup.energy[:], self.dgroup.norm[:]] 

968 

969 

970 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400), 

971 title="Deconvolve mu(E) Data") 

972 self.SetFont(Font(FONTSIZE)) 

973 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT) 

974 

975 self.wids = wids = {} 

976 

977 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1), 

978 action=self.on_groupchoice) 

979 

980 wids['grouplist'].SetStringSelection(self.dgroup.groupname) 

981 SetTip(wids['grouplist'], 'select a new group, clear undo history') 

982 

983 deconv_ops = ('Lorenztian', 'Gaussian') 

984 

985 wids['deconv_op'] = Choice(panel, choices=deconv_ops, size=(150, -1), 

986 action=self.on_deconvolve) 

987 

988 wids['esigma'] = FloatSpin(panel, value=0.5, digits=2, size=(90, -1), 

989 increment=0.1, action=self.on_deconvolve) 

990 

991 #wids['apply'] = Button(panel, 'Save / Overwrite', size=(150, -1), 

992 # action=self.on_apply) 

993 #SetTip(wids['apply'], 'Save corrected data, overwrite current arrays') 

994 

995 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1), 

996 action=self.on_saveas) 

997 SetTip(wids['save_as'], 'Save corrected data as new group') 

998 

999 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_deconv', 

1000 size=(250, -1)) 

1001 

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

1003 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow) 

1004 

1005 add_text('Deconvolve Data for Group: ', newrow=False) 

1006 panel.Add(wids['grouplist'], dcol=5) 

1007 

1008 add_text('Functional Form: ') 

1009 panel.Add(wids['deconv_op']) 

1010 

1011 add_text(' sigma= ') 

1012 panel.Add(wids['esigma']) 

1013 # panel.Add(wids['apply'], newrow=True) 

1014 panel.Add(wids['save_as'], newrow=True) 

1015 panel.Add(wids['save_as_name'], dcol=5) 

1016 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone), 

1017 newrow=True) 

1018 panel.pack() 

1019 

1020 fit_dialog_window(self, panel) 

1021 self.plot_results(keep_limits=False) 

1022 

1023 def onDone(self, event=None): 

1024 self.Destroy() 

1025 

1026 def on_saveas(self, event=None): 

1027 wids = self.wids 

1028 fname = wids['grouplist'].GetStringSelection() 

1029 new_fname = wids['save_as_name'].GetValue() 

1030 ngroup = self.controller.copy_group(fname, new_filename=new_fname) 

1031 xdat, ydat = self.data 

1032 ngroup.energy = ngroup.xdat = xdat 

1033 ngroup.mu = ngroup.ydat = ydat 

1034 

1035 ogroup = self.controller.get_group(fname) 

1036 olddesc = ogroup.journal.get('source_desc').value 

1037 

1038 self.parent.install_group(ngroup, journal=ogroup.journal) 

1039 ngroup.journal.add('source_desc', f"deconvolved({olddesc})") 

1040 ngroup.journal.add('deconvolve_command', self.cmd) 

1041 self.parent.process_normalization(ngroup) 

1042 

1043 

1044 def on_groupchoice(self, event=None): 

1045 self.dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection()) 

1046 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_deconv') 

1047 self.plot_results() 

1048 

1049 def on_deconvolve(self, event=None, value=None): 

1050 deconv_form = self.wids['deconv_op'].GetStringSelection() 

1051 

1052 esigma = self.wids['esigma'].GetValue() 

1053 

1054 dopts = [self.dgroup.groupname, 

1055 "form='%s'" % (deconv_form), 

1056 "esigma=%.4f" % (esigma)] 

1057 self.cmd = "xas_deconvolve(%s)" % (', '.join(dopts)) 

1058 self.controller.larch.eval(self.cmd) 

1059 

1060 self.data = self.dgroup.energy[:], self.dgroup.deconv[:] 

1061 self.plot_results() 

1062 

1063 def on_apply(self, event=None): 

1064 xdat, ydat = self.data 

1065 dgroup = self.dgroup 

1066 dgroup.energy = xdat 

1067 dgroup.mu = ydat 

1068 dgroup.journal.add('deconvolve_command ', self.cmd) 

1069 self.parent.process_normalization(dgroup) 

1070 self.plot_results() 

1071 

1072 def plot_results(self, event=None, keep_limits=True): 

1073 ppanel = self.controller.get_display(stacked=False).panel 

1074 xnew, ynew = self.data 

1075 dgroup = self.dgroup 

1076 xlim, ylim = get_view_limits(ppanel) 

1077 path, fname = os.path.split(dgroup.filename) 

1078 

1079 opts = {'delay_draw': True} 

1080 if self.controller.plot_erange is not None: 

1081 opts['xmin'] = dgroup.e0 + self.controller.plot_erange[0] 

1082 opts['xmax'] = dgroup.e0 + self.controller.plot_erange[1] 

1083 

1084 ppanel.plot(xnew, ynew, zorder=20, marker=None, 

1085 linewidth=3, title='Deconvolving:\n %s' % fname, 

1086 label='deconvolved', xlabel=plotlabels.energy, 

1087 ylabel=plotlabels.mu, **opts) 

1088 

1089 xold, yold = self.dgroup.energy, self.dgroup.norm 

1090 ppanel.oplot(xold, yold, zorder=10, 

1091 marker='o', markersize=4, linewidth=2.0, 

1092 label='original', show_legend=True, **opts) 

1093 if keep_limits: 

1094 set_view_limits(ppanel, xlim, ylim) 

1095 ppanel.canvas.draw() 

1096 

1097 def GetResponse(self): 

1098 raise AttributeError("use as non-modal dialog!") 

1099 

1100class DeglitchDialog(wx.Dialog): 

1101 """dialog for deglitching or removing unsightly data points""" 

1102 def __init__(self, parent, controller, **kws): 

1103 self.parent = parent 

1104 self.controller = controller 

1105 self.wids = {} 

1106 self.dgroup = self.controller.get_group() 

1107 groupnames = list(self.controller.file_groups.keys()) 

1108 

1109 self.reset_data_history() 

1110 xdat, ydat = self.data 

1111 

1112 xrange = (max(xdat) - min(xdat)) 

1113 xmax = int(max(xdat) + xrange/5.0) 

1114 xmin = int(min(xdat) - xrange/5.0) 

1115 

1116 lastx, lasty = self.controller.get_cursor() 

1117 if lastx is None: 

1118 lastx = max(xdat) 

1119 

1120 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400), 

1121 title="Select Points to Remove") 

1122 self.SetFont(Font(FONTSIZE)) 

1123 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT) 

1124 wids = self.wids 

1125 

1126 wids['grouplist'] = Choice(panel, choices=groupnames, size=(250, -1), 

1127 action=self.on_groupchoice) 

1128 

1129 wids['grouplist'].SetStringSelection(self.dgroup.filename) 

1130 SetTip(wids['grouplist'], 'select a new group, clear undo history') 

1131 

1132 br_xlast = Button(panel, 'Remove point', size=(125, -1), 

1133 action=partial(self.on_remove, opt='x')) 

1134 

1135 br_range = Button(panel, 'Remove range', size=(125, -1), 

1136 action=partial(self.on_remove, opt='range')) 

1137 

1138 undo = Button(panel, 'Undo remove', size=(125, -1), 

1139 action=self.on_undo) 

1140 #wids['apply'] = Button(panel, 'Save / Overwrite', size=(150, -1), 

1141 # action=self.on_apply) 

1142 #SetTip(wids['apply'], '''Save deglitched, overwrite current arrays, 

1143#clear undo history''') 

1144 

1145 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1), 

1146 action=self.on_saveas) 

1147 SetTip(wids['save_as'], 'Save deglitched data as new group') 

1148 

1149 wids['save_as_name'] = wx.TextCtrl(panel, -1, self.dgroup.filename + '_clean', 

1150 size=(250, -1)) 

1151 

1152 self.history_message = SimpleText(panel, '') 

1153 

1154 opts = dict(size=(125, -1), digits=2, increment=0.1, action=None) 

1155 for wname in ('xlast', 'range1', 'range2'): 

1156 if wname == 'range2': lastx += 1 

1157 pin_callback = partial(self.on_pinvalue, opt=wname) 

1158 fspin, pinbtn = add_floatspin(wname, lastx, panel, 

1159 with_pin=True, xasmain=self.parent, 

1160 callback=pin_callback, **opts) 

1161 wids[wname] = fspin 

1162 wids[wname+'_pin'] = pinbtn 

1163 

1164 self.choice_range = Choice(panel, choices=('above', 'below', 'between'), 

1165 size=(90, -1), action=self.on_rangechoice) 

1166 

1167 self.choice_range.SetStringSelection('above') 

1168 wids['range2'].Disable() 

1169 

1170 wids['plotopts'] = Choice(panel, choices=list(DEGLITCH_PLOTS.keys()), 

1171 size=(175, -1), 

1172 action=self.on_plotchoice) 

1173 

1174 wids['plotopts'].SetStringSelection(NORM_MU) 

1175 

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

1177 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow) 

1178 

1179 add_text('Deglitch Data for Group: ', dcol=2, newrow=False) 

1180 panel.Add(wids['grouplist'], dcol=5) 

1181 

1182 add_text('Single Energy : ', dcol=2) 

1183 panel.Add(wids['xlast']) 

1184 panel.Add(wids['xlast_pin']) 

1185 panel.Add(br_xlast) 

1186 

1187 add_text('Plot Data as: ', dcol=2) 

1188 panel.Add(wids['plotopts'], dcol=5) 

1189 

1190 add_text('Energy Range : ') 

1191 panel.Add(self.choice_range) 

1192 panel.Add(wids['range1']) 

1193 panel.Add(wids['range1_pin']) 

1194 panel.Add(br_range) 

1195 

1196 panel.Add((10, 10), dcol=2, newrow=True) 

1197 panel.Add(wids['range2']) 

1198 panel.Add(wids['range2_pin']) 

1199 

1200 # panel.Add(wids['apply'], dcol=2, newrow=True) 

1201 

1202 panel.Add(wids['save_as'], dcol=2, newrow=True) 

1203 panel.Add(wids['save_as_name'], dcol=4) 

1204 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone), 

1205 dcol=2, newrow=True) 

1206 panel.Add(self.history_message, dcol=2) 

1207 panel.Add(undo) 

1208 

1209 panel.pack() 

1210 

1211 fit_dialog_window(self, panel) 

1212 self.plot_results(keep_limits=False) 

1213 

1214 def onDone(self, event=None): 

1215 self.Destroy() 

1216 

1217 def reset_data_history(self): 

1218 plottype = 'norm' 

1219 if 'plotopts' in self.wids: 

1220 plotstr = self.wids['plotopts'].GetStringSelection() 

1221 plottype = DEGLITCH_PLOTS[plotstr] 

1222 self.data = self.get_xydata(datatype=plottype) 

1223 self.xmasks = [np.ones(len(self.data[0]), dtype=bool)] 

1224 

1225 def get_xydata(self, datatype='mu'): 

1226 if hasattr(self.dgroup, 'energy'): 

1227 xdat = self.dgroup.energy[:] 

1228 else: 

1229 xdat = self.dgroup.xdat[:] 

1230 ydat = self.dgroup.ydat[:] 

1231 if datatype == 'mu' and hasattr(self.dgroup, 'mu'): 

1232 ydat = self.dgroup.mu[:] 

1233 elif datatype == 'norm': 

1234 if not hasattr(self.dgroup, 'norm'): 

1235 self.parent.process_normalization(dgroup) 

1236 ydat = self.dgroup.norm[:] 

1237 elif datatype in ('chie', 'chiew'): 

1238 if not hasattr(self.dgroup, 'chie'): 

1239 self.parent.process_exafs(self.dgroup) 

1240 ydat = self.dgroup.chie[:] 

1241 if datatype == 'chiew': 

1242 ydat = self.dgroup.chie[:] * (xdat-self.dgroup.e0) 

1243 return (xdat, ydat) 

1244 

1245 def on_groupchoice(self, event=None): 

1246 self.dgroup = self.controller.get_group(self.wids['grouplist'].GetStringSelection()) 

1247 self.wids['save_as_name'].SetValue(self.dgroup.filename + '_clean') 

1248 self.reset_data_history() 

1249 self.plot_results(use_zoom=True) 

1250 

1251 def on_rangechoice(self, event=None): 

1252 sel = self.choice_range.GetStringSelection() 

1253 self.wids['range2'].Enable(sel == 'between') 

1254 

1255 

1256 def on_plotchoice(self, event=None): 

1257 plotstr = self.wids['plotopts'].GetStringSelection() 

1258 plottype = DEGLITCH_PLOTS[plotstr] 

1259 self.data = self.get_xydata(datatype=plottype) 

1260 self.plot_results() 

1261 

1262 def on_pinvalue(self, opt='__', xsel=None, **kws): 

1263 if xsel is not None and opt in self.wids: 

1264 self.wids[opt].SetValue(xsel) 

1265 

1266 def on_remove(self, event=None, opt=None): 

1267 xwork, ywork = self.data 

1268 mask = copy.deepcopy(self.xmasks[-1]) 

1269 if opt == 'x': 

1270 bad = index_nearest(xwork, self.wids['xlast'].GetValue()) 

1271 mask[bad] = False 

1272 elif opt == 'range': 

1273 rchoice = self.choice_range.GetStringSelection().lower() 

1274 x1 = index_nearest(xwork, self.wids['range1'].GetValue()) 

1275 x2 = None 

1276 if rchoice == 'below': 

1277 x2, x1 = x1, x2 

1278 elif rchoice == 'between': 

1279 x2 = index_nearest(xwork, self.wids['range2'].GetValue()) 

1280 if x1 > x2: 

1281 x1, x2 = x2, x1 

1282 mask[x1:x2] = False 

1283 self.xmasks.append(mask) 

1284 self.plot_results() 

1285 

1286 def on_undo(self, event=None): 

1287 if len(self.xmasks) == 1: 

1288 self.xmasks = [np.ones(len(self.data[0]), dtype=bool)] 

1289 else: 

1290 self.xmasks.pop() 

1291 self.plot_results() 

1292 

1293 def on_apply(self, event=None): 

1294 xdat, ydat = self.get_xydata(datatype='raw') 

1295 mask = self.xmasks[-1] 

1296 dgroup = self.dgroup 

1297 energies_removed = xdat[np.where(~mask)].tolist() 

1298 dgroup.energy = dgroup.xdat = xdat[mask] 

1299 dgroup.mu = dgroup.ydat = ydat[mask] 

1300 self.reset_data_history() 

1301 dgroup.journal.add('deglitch_removed_energies', energies_removed) 

1302 self.parent.process_normalization(dgroup) 

1303 self.plot_results() 

1304 

1305 def on_saveas(self, event=None): 

1306 fname = self.wids['grouplist'].GetStringSelection() 

1307 new_fname = self.wids['save_as_name'].GetValue() 

1308 ngroup = self.controller.copy_group(fname, new_filename=new_fname) 

1309 xdat, ydat = self.get_xydata(datatype='mu') 

1310 mask = self.xmasks[-1] 

1311 energies_removed = xdat[np.where(~mask)].tolist() 

1312 

1313 ngroup.energy = ngroup.xdat = xdat[mask] 

1314 ngroup.mu = ngroup.ydat = ydat[mask] 

1315 ngroup.energy_orig = 1.0*ngroup.energy 

1316 

1317 ogroup = self.controller.get_group(fname) 

1318 olddesc = ogroup.journal.get('source_desc').value 

1319 

1320 self.parent.install_group(ngroup, journal=ogroup.journal) 

1321 ngroup.journal.add('source_desc', f"deglitched({olddesc})") 

1322 ngroup.journal.add('deglitch_removed_energies', energies_removed) 

1323 

1324 self.parent.process_normalization(ngroup) 

1325 

1326 def plot_results(self, event=None, keep_limits=True): 

1327 ppanel = self.controller.get_display(stacked=False).panel 

1328 

1329 xdat, ydat = self.data 

1330 

1331 xmin = min(xdat) - 0.025*(max(xdat) - min(xdat)) 

1332 xmax = max(xdat) + 0.025*(max(xdat) - min(xdat)) 

1333 ymin = min(ydat) - 0.025*(max(ydat) - min(ydat)) 

1334 ymax = max(ydat) + 0.025*(max(ydat) - min(ydat)) 

1335 

1336 dgroup = self.dgroup 

1337 

1338 path, fname = os.path.split(dgroup.filename) 

1339 

1340 plotstr = self.wids['plotopts'].GetStringSelection() 

1341 plottype = DEGLITCH_PLOTS[plotstr] 

1342 

1343 xlabel=plotlabels.energy 

1344 if plottype in ('chie', 'chiew'): 

1345 xmin = self.dgroup.e0 

1346 xlabel = xlabel=plotlabels.ewithk 

1347 

1348 opts = dict(xlabel=xlabel, title='De-glitching:\n %s' % fname, 

1349 delay_draw=True) 

1350 

1351 ylabel = {'mu': plotlabels.mu, 

1352 'norm': plotlabels.norm, 

1353 'chie': plotlabels.chie, 

1354 'chiew': plotlabels.chiew.format(1), 

1355 }.get(plottype, plotlabels.norm) 

1356 

1357 dgroup.plot_xlabel = xlabel 

1358 dgroup.plot_ylabel = ylabel 

1359 

1360 xlim, ylim = get_view_limits(ppanel) 

1361 

1362 ppanel.plot(xdat, ydat, zorder=10, marker=None, linewidth=3, 

1363 label='original', ylabel=ylabel, **opts) 

1364 

1365 if len(self.xmasks) > 1: 

1366 mask = self.xmasks[-1] 

1367 ppanel.oplot(xdat[mask], ydat[mask], zorder=15, 

1368 marker='o', markersize=3, linewidth=2.0, 

1369 label='current', show_legend=True, **opts) 

1370 

1371 def ek_formatter(x, pos): 

1372 ex = float(x) - self.dgroup.e0 

1373 s = '' if ex < 0 else '\n[%.1f]' % (etok(ex)) 

1374 return r"%1.4g%s" % (x, s) 

1375 

1376 if keep_limits: 

1377 set_view_limits(ppanel, xlim, ylim) 

1378 if plottype in ('chie', 'chiew'): 

1379 ppanel.axes.xaxis.set_major_formatter(FuncFormatter(ek_formatter)) 

1380 

1381 ppanel.canvas.draw() 

1382 

1383 self.history_message.SetLabel('%i items in history' % (len(self.xmasks)-1)) 

1384 

1385 def GetResponse(self): 

1386 raise AttributeError("use as non-modal dialog!") 

1387 

1388 

1389SPECCALC_SETUP = """#From SpectraCalc dialog: 

1390_x = {group:s}.{xname:s} 

1391a = {group:s}.{yname:s} 

1392b = c = d = e = f = g = None 

1393""" 

1394 

1395SPECCALC_INTERP = "{key:s} = interp({group:s}.{xname:s}, {group:s}.{yname:s}, _x)" 

1396SPECCALC_PLOT = """plot(_x, ({expr:s}), label='{expr:s}', new=True, 

1397 show_legend=True, xlabel='{xname:s}', title='Spectral Calculation')""" 

1398 

1399SPECCALC_SAVE = """{new:s} = copy_xafs_group({group:s}) 

1400{new:s}.groupname = '{new:s}' 

1401{new:s}.mu = ({expr:s}) 

1402{new:s}.filename = '{fname:s}' 

1403{new:s}.journal.add('calc_groups', {group_map:s}) 

1404{new:s}.journal.add('calc_arrayname', '{yname:s}') 

1405{new:s}.journal.add('calc_expression', '{expr:s}') 

1406del _x, a, b, c, d, e, f, g""" 

1407 

1408 

1409class SpectraCalcDialog(wx.Dialog): 

1410 """dialog for adding and subtracting spectra""" 

1411 def __init__(self, parent, controller, **kws): 

1412 

1413 self.parent = parent 

1414 self.controller = controller 

1415 self.dgroup = self.controller.get_group() 

1416 self.group_a = None 

1417 groupnames = list(self.controller.file_groups.keys()) 

1418 

1419 self.data = [self.dgroup.energy[:], self.dgroup.norm[:]] 

1420 xmin = min(self.dgroup.energy) 

1421 xmax = max(self.dgroup.energy) 

1422 e0val = getattr(self.dgroup, 'e0', xmin) 

1423 

1424 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(475, 525), 

1425 title="Spectra Calculations: Add, Subtract Spectra") 

1426 self.SetFont(Font(FONTSIZE)) 

1427 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT) 

1428 

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

1430 panel.Add(SimpleText(panel, text), dcol=dcol, newrow=newrow) 

1431 

1432 self.wids = wids = {} 

1433 array_choices = ('Normalized \u03BC(E)', 'Raw \u03BC(E)') 

1434 

1435 wids['array'] = Choice(panel, choices=array_choices, size=(250, -1)) 

1436 

1437 add_text('Array to use: ', newrow=True) 

1438 panel.Add(wids['array'], dcol=2) 

1439 

1440 # group 'a' cannot be none, and defaults to current group 

1441 gname = 'a' 

1442 wname = 'group_%s' % gname 

1443 wids[wname] = Choice(panel, choices=groupnames, size=(250, -1)) 

1444 wids[wname].SetStringSelection(self.dgroup.filename) 

1445 add_text(' %s = ' % gname, newrow=True) 

1446 panel.Add(wids[wname], dcol=2) 

1447 

1448 groupnames.insert(0, 'None') 

1449 for gname in ('b', 'c', 'd', 'e', 'f', 'g'): 

1450 wname = 'group_%s' % gname 

1451 wids[wname] = Choice(panel, choices=groupnames, size=(250, -1)) 

1452 wids[wname].SetSelection(0) 

1453 add_text(' %s = ' % gname, newrow=True) 

1454 panel.Add(wids[wname], dcol=2) 

1455 

1456 wids['formula'] = wx.TextCtrl(panel, -1, 'a-b', size=(250, -1)) 

1457 add_text('Expression = ', newrow=True) 

1458 panel.Add(wids['formula'], dcol=2) 

1459 

1460 wids['docalc'] = Button(panel, 'Calculate', 

1461 size=(150, -1), action=self.on_docalc) 

1462 

1463 panel.Add(wids['docalc'], dcol=2, newrow=True) 

1464 

1465 wids['save_as'] = Button(panel, 'Save As New Group: ', size=(150, -1), 

1466 action=self.on_saveas) 

1467 SetTip(wids['save_as'], 'Save as new group') 

1468 

1469 wids['save_as_name'] = wx.TextCtrl(panel, -1, 

1470 self.dgroup.filename + '_calc', 

1471 size=(250, -1)) 

1472 panel.Add(wids['save_as'], newrow=True) 

1473 panel.Add(wids['save_as_name'], dcol=2) 

1474 wids['save_as'].Disable() 

1475 panel.Add(Button(panel, 'Done', size=(150, -1), action=self.onDone), 

1476 newrow=True) 

1477 panel.pack() 

1478 fit_dialog_window(self, panel) 

1479 

1480 def onDone(self, event=None): 

1481 self.Destroy() 

1482 

1483 def on_docalc(self, event=None): 

1484 self.expr = self.wids['formula'].GetValue() 

1485 

1486 self.yname = 'mu' 

1487 if self.wids['array'].GetStringSelection().lower().startswith('norm'): 

1488 self.yname = 'norm' 

1489 

1490 groups = {} 

1491 for aname in ('a', 'b', 'c', 'd', 'e', 'f', 'g'): 

1492 fname = self.wids['group_%s' % aname].GetStringSelection() 

1493 if fname not in (None, 'None'): 

1494 grp = self.controller.get_group(fname) 

1495 groups[aname] = grp 

1496 

1497 self.group_map = {key: group.groupname for key, group in groups.items()} 

1498 # note: 'a' cannot be None, all others can be None 

1499 group_a = self.group_a = groups.pop('a') 

1500 xname = 'energy' 

1501 if not hasattr(group_a, xname): 

1502 xname = 'xdat' 

1503 

1504 cmds = [SPECCALC_SETUP.format(group=group_a.groupname, 

1505 xname=xname, yname=self.yname)] 

1506 

1507 for key, group in groups.items(): 

1508 cmds.append(SPECCALC_INTERP.format(key=key, group=group.groupname, 

1509 xname=xname, yname=self.yname)) 

1510 

1511 cmds.append(SPECCALC_PLOT.format(expr=self.expr, xname=xname)) 

1512 self.controller.larch.eval('\n'.join(cmds)) 

1513 self.wids['save_as'].Enable() 

1514 

1515 def on_saveas(self, event=None): 

1516 wids = self.wids 

1517 _larch = self.controller.larch 

1518 fname = wids['group_a'].GetStringSelection() 

1519 new_fname =self.wids['save_as_name'].GetValue() 

1520 new_gname = file2groupname(new_fname, slen=5, symtable=_larch.symtable) 

1521 

1522 gmap = [] 

1523 for k, v in self.group_map.items(): 

1524 gmap.append(f'"{k}": "{v}"') 

1525 gmap = '{%s}' % (', '.join(gmap)) 

1526 

1527 _larch.eval(SPECCALC_SAVE.format(new=new_gname, fname=new_fname, 

1528 group=self.group_a.groupname, 

1529 group_map=gmap, 

1530 yname=self.yname, expr=self.expr)) 

1531 

1532 

1533 journal={'source_desc': f"{new_fname}: calc({self.expr})", 

1534 'calc_groups': gmap, 'calc_expression': self.expr} 

1535 

1536 ngroup = getattr(_larch.symtable, new_gname, None) 

1537 if ngroup is not None: 

1538 self.parent.install_group(ngroup, source=journal['source_desc'], 

1539 journal=journal) 

1540 

1541 def GetResponse(self): 

1542 raise AttributeError("use as non-modal dialog!") 

1543 

1544class EnergyUnitsDialog(wx.Dialog): 

1545 """dialog for selecting, changing energy units, forcing data to eV""" 

1546 unit_choices = ['eV', 'keV', 'deg', 'steps'] 

1547 

1548 def __init__(self, parent, energy_array, unitname='eV',dspace=1, **kws): 

1549 

1550 self.parent = parent 

1551 self.energy = 1.0*energy_array 

1552 

1553 title = "Select Energy Units to convert to 'eV'" 

1554 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title) 

1555 self.SetFont(Font(FONTSIZE)) 

1556 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

1557 

1558 self.en_units = Choice(panel, choices=self.unit_choices, size=(125, -1), 

1559 action=self.onUnits) 

1560 self.en_units.SetStringSelection(unitname) 

1561 self.mono_dspace = FloatCtrl(panel, value=dspace, minval=0, maxval=100.0, 

1562 precision=6, size=(125, -1)) 

1563 self.steps2deg = FloatCtrl(panel, value=1.0, minval=0, 

1564 precision=1, size=(125, -1)) 

1565 

1566 self.mono_dspace.Disable() 

1567 self.steps2deg.Disable() 

1568 

1569 panel.Add(SimpleText(panel, 'Energy Units : '), newrow=True) 

1570 panel.Add(self.en_units) 

1571 

1572 panel.Add(SimpleText(panel, 'Mono D spacing : '), newrow=True) 

1573 panel.Add(self.mono_dspace) 

1574 

1575 panel.Add(SimpleText(panel, 'Mono Steps per Degree : '), newrow=True) 

1576 panel.Add(self.steps2deg) 

1577 panel.Add((5, 5)) 

1578 

1579 panel.Add(OkCancel(panel), dcol=2, newrow=True) 

1580 panel.pack() 

1581 

1582 fit_dialog_window(self, panel) 

1583 

1584 

1585 def onUnits(self, event=None): 

1586 units = self.en_units.GetStringSelection() 

1587 self.steps2deg.Enable(units == 'steps') 

1588 self.mono_dspace.Enable(units in ('steps', 'deg')) 

1589 

1590 def GetResponse(self, master=None, gname=None, ynorm=True): 

1591 self.Raise() 

1592 response = namedtuple('EnergyUnitsResponse', 

1593 ('ok', 'units', 'energy', 'dspace')) 

1594 ok, units, en, dspace = False, 'eV', None, -1 

1595 

1596 if self.ShowModal() == wx.ID_OK: 

1597 units = self.en_units.GetStringSelection() 

1598 if units == 'eV': 

1599 en = self.energy 

1600 elif units == 'keV': 

1601 en = self.energy * 1000.0 

1602 elif units in ('steps', 'deg'): 

1603 dspace = float(self.mono_dspace.GetValue()) 

1604 if units == 'steps': 

1605 self.energy /= self.steps2deg.GetValue() 

1606 en = PLANCK_HC/(2*dspace*np.sin(self.energy * DEG2RAD)) 

1607 ok = True 

1608 return response(ok, units, en, dspace) 

1609 

1610class MergeDialog(wx.Dialog): 

1611 """dialog for merging groups""" 

1612 ychoices = ['raw mu(E)', 'normalized mu(E)'] 

1613 

1614 def __init__(self, parent, groupnames, outgroup='merge', **kws): 

1615 title = "Merge %i Selected Groups" % (len(groupnames)) 

1616 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title) 

1617 

1618 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

1619 

1620 self.master_group = Choice(panel, choices=groupnames, size=(250, -1)) 

1621 self.yarray_name = Choice(panel, choices=self.ychoices, size=(250, -1)) 

1622 self.group_name = wx.TextCtrl(panel, -1, outgroup, size=(250, -1)) 

1623 

1624 panel.Add(SimpleText(panel, 'Match Energy to : '), newrow=True) 

1625 panel.Add(self.master_group) 

1626 

1627 panel.Add(SimpleText(panel, 'Array to merge : '), newrow=True) 

1628 panel.Add(self.yarray_name) 

1629 

1630 panel.Add(SimpleText(panel, 'New group name : '), newrow=True) 

1631 panel.Add(self.group_name) 

1632 

1633 panel.Add(OkCancel(panel), dcol=2, newrow=True) 

1634 

1635 panel.pack() 

1636 fit_dialog_window(self, panel) 

1637 

1638 

1639 def GetResponse(self, master=None, gname=None, ynorm=True): 

1640 self.Raise() 

1641 response = namedtuple('MergeResponse', ('ok', 'master', 'ynorm', 'group')) 

1642 ok = False 

1643 if self.ShowModal() == wx.ID_OK: 

1644 master= self.master_group.GetStringSelection() 

1645 ynorm = 'norm' in self.yarray_name.GetStringSelection().lower() 

1646 gname = self.group_name.GetValue() 

1647 ok = True 

1648 return response(ok, master, ynorm, gname) 

1649 

1650 

1651class ExportCSVDialog(wx.Dialog): 

1652 """dialog for exporting groups to CSV file""" 

1653 

1654 def __init__(self, parent, groupnames, **kws): 

1655 title = "Export Selected Groups" 

1656 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title) 

1657 self.SetFont(Font(FONTSIZE)) 

1658 self.xchoices = {'Energy': 'energy', 

1659 'k': 'k', 

1660 'R': 'r', 

1661 'q': 'q'} 

1662 

1663 self.ychoices = {'normalized mu(E)': 'norm', 

1664 'raw mu(E)': 'mu', 

1665 'flattened mu(E)': 'flat', 

1666 'd mu(E) / dE': 'dmude', 

1667 'chi(k)': 'chi', 

1668 'chi(E)': 'chie', 

1669 'chi(q)': 'chiq', 

1670 '|chi(R)|': 'chir_mag', 

1671 'Re[chi(R)]': 'chir_re'} 

1672 

1673 self.delchoices = {'comma': ',', 

1674 'space': ' ', 

1675 'tab': '\t'} 

1676 

1677 

1678 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

1679 

1680 self.save_individual_files = Check(panel, default=False, label='Save individual files', action=self.onSaveIndividualFiles) 

1681 self.master_group = Choice(panel, choices=groupnames, size=(200, -1)) 

1682 self.xarray_name = Choice(panel, choices=list(self.xchoices.keys()),size=(200, -1)) 

1683 self.yarray_name = Choice(panel, choices=list(self.ychoices.keys()), size=(200, -1)) 

1684 self.del_name = Choice(panel, choices=list(self.delchoices.keys()), size=(200, -1)) 

1685 

1686 

1687 panel.Add(self.save_individual_files, newrow=True) 

1688 

1689 panel.Add(SimpleText(panel, 'Group for Energy Array: '), newrow=True) 

1690 panel.Add(self.master_group) 

1691 

1692 panel.Add(SimpleText(panel, 'X Array to Export: '), newrow=True) 

1693 panel.Add(self.xarray_name) 

1694 

1695 panel.Add(SimpleText(panel, 'Y Array to Export: '), newrow=True) 

1696 panel.Add(self.yarray_name) 

1697 

1698 panel.Add(SimpleText(panel, 'Delimeter for File: '), newrow=True) 

1699 panel.Add(self.del_name) 

1700 panel.Add(OkCancel(panel), dcol=2, newrow=True) 

1701 panel.pack() 

1702 fit_dialog_window(self, panel) 

1703 

1704 def onSaveIndividualFiles(self, event=None): 

1705 save_individual = self.save_individual_files.IsChecked() 

1706 self.master_group.Enable(not save_individual) 

1707 

1708 def GetResponse(self, master=None, gname=None, ynorm=True): 

1709 self.Raise() 

1710 response = namedtuple('ExportCSVResponse', 

1711 ('ok', 'individual', 'master', 'xarray', 'yarray', 'delim')) 

1712 ok = False 

1713 if self.ShowModal() == wx.ID_OK: 

1714 individual = self.save_individual_files.IsChecked() 

1715 master = self.master_group.GetStringSelection() 

1716 xarray = self.xchoices[self.xarray_name.GetStringSelection()] 

1717 yarray = self.ychoices[self.yarray_name.GetStringSelection()] 

1718 delim = self.delchoices[self.del_name.GetStringSelection()] 

1719 ok = True 

1720 return response(ok, individual, master, xarray, yarray, delim) 

1721 

1722class QuitDialog(wx.Dialog): 

1723 """dialog for quitting, prompting to save project""" 

1724 

1725 def __init__(self, parent, message, **kws): 

1726 title = "Quit Larch XAS Viewer?" 

1727 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title, size=(500, 150)) 

1728 self.SetFont(Font(FONTSIZE)) 

1729 self.needs_save = True 

1730 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

1731 

1732 status, filename, stime = message 

1733 warn_msg = 'All work in this session will be lost!' 

1734 

1735 panel.Add((5, 5)) 

1736 if len(stime) > 2: 

1737 status = f"{status} at {stime} to file" 

1738 warn_msg = 'Changes made after that will be lost!' 

1739 

1740 panel.Add(wx.StaticText(panel, label=status), dcol=2) 

1741 

1742 if len(filename) > 0: 

1743 if filename.startswith("'") and filename.endswith("'"): 

1744 filename = filename[1:-1] 

1745 panel.Add((15, 5), newrow=True) 

1746 panel.Add(wx.StaticText(panel, label=filename), dcol=2) 

1747 

1748 panel.Add((5, 5), newrow=True) 

1749 panel.Add(wx.StaticText(panel, label=warn_msg), dcol=2) 

1750 panel.Add(HLine(panel, size=(500, 3)), dcol=3, newrow=True) 

1751 panel.Add((5, 5), newrow=True) 

1752 panel.Add(OkCancel(panel), dcol=2, newrow=True) 

1753 panel.pack() 

1754 

1755 fit_dialog_window(self, panel) 

1756 

1757 def GetResponse(self): 

1758 self.Raise() 

1759 response = namedtuple('QuitResponse', ('ok',)) 

1760 ok = (self.ShowModal() == wx.ID_OK) 

1761 return response(ok,) 

1762 

1763class RenameDialog(wx.Dialog): 

1764 """dialog for renaming group""" 

1765 def __init__(self, parent, oldname, **kws): 

1766 title = "Rename Group %s" % (oldname) 

1767 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title) 

1768 self.SetFont(Font(FONTSIZE)) 

1769 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

1770 

1771 self.newname = wx.TextCtrl(panel, -1, oldname, size=(250, -1)) 

1772 

1773 panel.Add(SimpleText(panel, 'Old Name : '), newrow=True) 

1774 panel.Add(SimpleText(panel, oldname)) 

1775 panel.Add(SimpleText(panel, 'New Name : '), newrow=True) 

1776 panel.Add(self.newname) 

1777 panel.Add(OkCancel(panel), dcol=2, newrow=True) 

1778 

1779 panel.pack() 

1780 fit_dialog_window(self, panel) 

1781 

1782 

1783 def GetResponse(self, newname=None): 

1784 self.Raise() 

1785 response = namedtuple('RenameResponse', ('ok', 'newname')) 

1786 ok = False 

1787 if self.ShowModal() == wx.ID_OK: 

1788 newname = self.newname.GetValue() 

1789 ok = True 

1790 return response(ok, newname) 

1791 

1792class RemoveDialog(wx.Dialog): 

1793 """dialog for removing groups""" 

1794 def __init__(self, parent, grouplist, **kws): 

1795 title = "Remove %i Selected Group" % len(grouplist) 

1796 self.grouplist = grouplist 

1797 wx.Dialog.__init__(self, parent, wx.ID_ANY, title=title) 

1798 self.SetFont(Font(FONTSIZE)) 

1799 panel = GridPanel(self, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

1800 

1801 panel.Add(SimpleText(panel, 'Remove %i Selected Groups?' % (len(grouplist))), 

1802 newrow=True, dcol=2) 

1803 

1804 panel.Add(OkCancel(panel), dcol=2, newrow=True) 

1805 panel.pack() 

1806 fit_dialog_window(self, panel) 

1807 

1808 def GetResponse(self, ngroups=None): 

1809 self.Raise() 

1810 response = namedtuple('RemoveResponse', ('ok','ngroups')) 

1811 ok = False 

1812 if self.ShowModal() == wx.ID_OK: 

1813 ngroups = len(self.grouplist) 

1814 ok = True 

1815 return response(ok, ngroups) 

1816 

1817 

1818class LoadSessionDialog(wx.Frame): 

1819 """Read, show data from saved larch session""" 

1820 

1821 xasgroups_name = '_xasgroups' 

1822 feffgroups_name = ['_feffpaths', '_feffcache'] 

1823 

1824 def __init__(self, parent, session, filename, controller, **kws): 

1825 self.parent = parent 

1826 self.session = session 

1827 self.filename = filename 

1828 self.controller = controller 

1829 title = f"Read Larch Session from '{filename}'" 

1830 wx.Frame.__init__(self, parent, wx.ID_ANY, title=title) 

1831 

1832 x0, y0 = parent.GetPosition() 

1833 self.SetPosition((x0+450, y0+75)) 

1834 

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

1836 splitter.SetMinimumPaneSize(250) 

1837 

1838 leftpanel = wx.Panel(splitter) 

1839 rightpanel = wx.Panel(splitter) 

1840 

1841 ltop = wx.Panel(leftpanel) 

1842 

1843 sel_none = Button(ltop, 'Select None', size=(100, 30), action=self.onSelNone) 

1844 sel_all = Button(ltop, 'Select All', size=(100, 30), action=self.onSelAll) 

1845 sel_imp = Button(ltop, 'Import Selected Data', size=(200, 30), 

1846 action=self.onImport) 

1847 

1848 self.select_imported = sel_imp 

1849 self.grouplist = FileCheckList(leftpanel, select_action=self.onShowGroup) 

1850 set_color(self.grouplist, 'list_fg', bg='list_bg') 

1851 

1852 tsizer = wx.GridBagSizer(2, 2) 

1853 tsizer.Add(sel_all, (0, 0), (1, 1), LEFT, 0) 

1854 tsizer.Add(sel_none, (0, 1), (1, 1), LEFT, 0) 

1855 tsizer.Add(sel_imp, (1, 0), (1, 2), LEFT, 0) 

1856 

1857 pack(ltop, tsizer) 

1858 

1859 sizer = wx.BoxSizer(wx.VERTICAL) 

1860 sizer.Add(ltop, 0, LEFT|wx.GROW, 1) 

1861 sizer.Add(self.grouplist, 1, LEFT|wx.GROW|wx.ALL, 1) 

1862 pack(leftpanel, sizer) 

1863 

1864 

1865 panel = GridPanel(rightpanel, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

1866 self.wids = wids = {} 

1867 

1868 top_message = 'Larch Session File: No XAFS Groups' 

1869 symtable = controller.symtable 

1870 

1871 self.allgroups = session.symbols.get(self.xasgroups_name, {}) 

1872 self.extra_groups = [] 

1873 for key, val in session.symbols.items(): 

1874 if key == self.xasgroups_name or key in self.feffgroups_name: 

1875 continue 

1876 if key in self.allgroups: 

1877 continue 

1878 if hasattr(val, 'energy') and hasattr(val, 'mu'): 

1879 if key in self.allgroups.keys() or key in self.allgroups.values(): 

1880 continue 

1881 self.allgroups[key] = key 

1882 self.extra_groups.append(key) 

1883 

1884 

1885 checked = [] 

1886 for fname, gname in self.allgroups.items(): 

1887 self.grouplist.Append(fname) 

1888 checked.append(fname) 

1889 

1890 self.grouplist.SetCheckedStrings(checked) 

1891 

1892 group_names = list(self.allgroups.values()) 

1893 group_names.append(self.xasgroups_name) 

1894 group_names.extend(self.feffgroups_name) 

1895 

1896 wids['view_conf'] = Button(panel, 'Show Session Configuration', 

1897 size=(200, 30), action=self.onShowConfig) 

1898 wids['view_cmds'] = Button(panel, 'Show Session Commands', 

1899 size=(200, 30), action=self.onShowCommands) 

1900 

1901 wids['plotopt'] = Choice(panel, choices=list(SESSION_PLOTS.keys()), 

1902 action=self.onPlotChoice, size=(175, -1)) 

1903 

1904 panel.Add(wids['view_conf'], dcol=1) 

1905 panel.Add(wids['view_cmds'], dcol=1, newrow=False) 

1906 panel.Add(HLine(panel, size=(450, 2)), dcol=3, newrow=True) 

1907 

1908 over_msg = 'Importing these Groups/Data will overwrite values in the current session:' 

1909 panel.Add(SimpleText(panel, over_msg), dcol=2, newrow=True) 

1910 panel.Add(SimpleText(panel, "Symbol Name"), dcol=1, newrow=True) 

1911 panel.Add(SimpleText(panel, "Import/Overwrite?"), dcol=1) 

1912 i = 0 

1913 self.overwrite_checkboxes = {} 

1914 for g in self.session.symbols: 

1915 if g not in group_names and hasattr(symtable, g): 

1916 chbox = Check(panel, default=True) 

1917 panel.Add(SimpleText(panel, g), dcol=1, newrow=True) 

1918 panel.Add(chbox, dcol=1) 

1919 self.overwrite_checkboxes[g] = chbox 

1920 

1921 i += 1 

1922 

1923 panel.Add((5, 5), newrow=True) 

1924 panel.Add(HLine(panel, size=(450, 2)), dcol=3, newrow=True) 

1925 panel.Add(SimpleText(panel, 'Plot Type:'), newrow=True) 

1926 panel.Add(wids['plotopt'], dcol=2, newrow=False) 

1927 panel.pack() 

1928 

1929 self.plotpanel = PlotPanel(rightpanel, messenger=self.plot_messages) 

1930 self.plotpanel.SetSize((475, 450)) 

1931 plotconf = self.controller.get_config('plot') 

1932 self.plotpanel.conf.set_theme(plotconf['theme']) 

1933 self.plotpanel.conf.enable_grid(plotconf['show_grid']) 

1934 

1935 sizer = wx.BoxSizer(wx.VERTICAL) 

1936 sizer.Add(panel, 0, LEFT, 2) 

1937 sizer.Add(self.plotpanel, 1, LEFT, 2) 

1938 

1939 pack(rightpanel, sizer) 

1940 

1941 splitter.SplitVertically(leftpanel, rightpanel, 1) 

1942 self.SetSize((750, 725)) 

1943 

1944 self.Show() 

1945 self.Raise() 

1946 

1947 def plot_messages(self, msg, panel=1): 

1948 pass 

1949 

1950 def onSelAll(self, event=None): 

1951 self.grouplist.SetCheckedStrings(list(self.allgroups.keys())) 

1952 

1953 def onSelNone(self, event=None): 

1954 self.grouplist.SetCheckedStrings([]) 

1955 

1956 def onShowGroup(self, event=None): 

1957 """column selections changed calc xdat and ydat""" 

1958 fname = event.GetString() 

1959 gname = self.allgroups.get(fname, None) 

1960 if gname in self.session.symbols: 

1961 self.plot_group(gname, fname) 

1962 

1963 def onPlotChoice(self, event=None): 

1964 fname = self.grouplist.GetStringSelection() 

1965 gname = self.allgroups.get(fname, None) 

1966 self.plot_group(gname, fname) 

1967 

1968 def plot_group(self, gname, fname): 

1969 grp = self.session.symbols[gname] 

1970 plottype = SESSION_PLOTS.get(self.wids['plotopt'].GetStringSelection(), 'norm') 

1971 xdef = np.zeros(1) 

1972 xdat = getattr(grp, 'energy', xdef) 

1973 ydat = getattr(grp, 'mu', xdef) 

1974 xlabel = plotlabels.energy 

1975 ylabel = plotlabels.mu 

1976 if plottype == 'norm' and hasattr(grp, 'norm'): 

1977 ydat = getattr(grp, 'norm', xdef) 

1978 ylabel = plotlabels.norm 

1979 elif plottype == 'chikw' and hasattr(grp, 'chi'): 

1980 xdat = getattr(grp, 'k', xdef) 

1981 ydat = getattr(grp, 'chi', xdef) 

1982 ydat = ydat*xdat*xdat 

1983 xlabel = plotlabels.chikw.format(2) 

1984 

1985 if len(ydat) > 1: 

1986 self.plotpanel.plot(xdat, ydat, xlabel=xlabel, 

1987 ylabel=ylabel, title=fname) 

1988 

1989 

1990 def onShowConfig(self, event=None): 

1991 DictFrame(parent=self.parent, 

1992 data=self.session.config, 

1993 title=f"Session Configuration for '{self.filename}'") 

1994 

1995 def onShowCommands(self, event=None): 

1996 oname = self.filename.replace('.larix', '.lar') 

1997 wildcard='Larch Command Files (*.lar)|*.lar' 

1998 text = '\n'.join(self.session.command_history) 

1999 ReportFrame(parent=self.parent, 

2000 text=text, 

2001 title=f"Session Commands from '{self.filename}'", 

2002 default_filename=oname, 

2003 wildcard=wildcard) 

2004 

2005 def onClose(self, event=None): 

2006 self.Destroy() 

2007 

2008 def onImport(self, event=None): 

2009 ignore = [] 

2010 for gname, chbox in self.overwrite_checkboxes.items(): 

2011 if not chbox.IsChecked(): 

2012 ignore.append(gname) 

2013 

2014 sel_groups = self.grouplist.GetCheckedStrings() 

2015 for fname, gname in self.allgroups.items(): 

2016 if fname not in sel_groups: 

2017 ignore.append(gname) 

2018 

2019 fname = self.filename.replace('\\','/') 

2020 if fname.endswith('/'): 

2021 fname = fname[:-1] 

2022 lcmd = [f"load_session('{fname}'"] 

2023 if len(ignore) > 0: 

2024 ignore = repr(ignore) 

2025 lcmd.append(f", ignore_groups={ignore}") 

2026 if len(self.extra_groups) > 0: 

2027 extra = repr(self.extra_groups) 

2028 lcmd.append(f", include_xasgroups={extra}") 

2029 

2030 lcmd = ''.join(lcmd) + ')' 

2031 

2032 cmds = ["# Loading Larch Session with ", lcmd, '######'] 

2033 

2034 self.controller.larch.eval('\n'.join(cmds)) 

2035 last_fname = None 

2036 xasgroups = getattr(self.controller.symtable, self.xasgroups_name, {}) 

2037 for key, val in xasgroups.items(): 

2038 if key not in self.controller.filelist.GetItems(): 

2039 self.controller.filelist.Append(key) 

2040 last_fname = key 

2041 

2042 self.controller.recentfiles.append((time.time(), self.filename)) 

2043 

2044 wx.CallAfter(self.Destroy) 

2045 if last_fname is not None: 

2046 self.parent.ShowFile(filename=last_fname)