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

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 

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.utils import path_split 

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 

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) 

68 

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) 

73 

74 

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

82 

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

84 

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 = {} 

90 

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

92 action=self.on_groupchoice) 

93 

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

95 

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

97 minval=-90, maxval=180) 

98 

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) 

102 

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

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

105 

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

107 

108 self.set_default_elem_edge(self.dgroup) 

109 

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

113 

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

117 

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

123 

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

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

126 

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

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

129 

130 add_text(' Absorbing Element: ') 

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

132 

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

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

135 

136 add_text(' Material Formula: ') 

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

138 

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

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

141 

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

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

144 

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

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

147 

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) 

155 

156 

157 def onDone(self, event=None): 

158 self.Destroy() 

159 

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) 

164 

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

170 

171 

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 

182 

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

190 

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

198 

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) 

204 

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

206 ngroup.mu = ngroup.norm_corr*1.0 

207 del ngroup.norm_corr 

208 

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) 

214 

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

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

217 

218 dgroup = self.dgroup 

219 xlim, ylim = get_view_limits(ppanel) 

220 path, fname = path_split(dgroup.filename) 

221 

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

223 xlabel=plotlabels.energy, delay_draw=True, 

224 show_legend=True) 

225 

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] 

229 

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

231 dgroup.norm_corr = dgroup.norm[:] 

232 

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

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

235 label='corrected', **opts) 

236 

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) 

243 

244 def GetResponse(self): 

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

246 

247 

248class EnergyCalibrateDialog(wx.Dialog): 

249 """dialog for calibrating energy""" 

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

251 

252 self.parent = parent 

253 self.controller = controller 

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

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

256 

257 ensure_en_orig(self.dgroup) 

258 

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) 

263 

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) 

268 

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) 

273 

274 refgroups = ['None'] + groupnames 

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

276 action=self.on_align, default=0) 

277 

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

279 

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

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

282 

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) 

287 

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

289 action=self.on_apply_one) 

290 

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

294 

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

298 

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

300 self.dgroup.filename + '_eshift', 

301 size=(275, -1)) 

302 

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) 

306 

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

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

309 

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

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

312 

313 add_text(' Auto-Align to : ') 

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

315 

316 add_text(' Plot Arrays as: ') 

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

318 

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) 

324 

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) 

329 

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

331 

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

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

334 

335 panel.pack() 

336 

337 fit_dialog_window(self, panel) 

338 

339 self.plot_results(keep_limits=False) 

340 wx.CallAfter(self.get_groups_shared_energyrefs) 

341 

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) 

346 

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 

357 

358 

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 

372 

373 def on_select_sharedrefs(self, event=None): 

374 groups = self.get_groups_shared_energyrefs() 

375 self.controller.filelist.SetCheckedStrings(groups) 

376 

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

383 

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) 

389 

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) 

395 

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] 

402 

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) 

419 

420 ensure_en_orig(self.dgroup) 

421 xnew = self.dgroup.energy_orig + eshift 

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

423 self.plot_results() 

424 

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

432 

433 def on_apply_one(self, event=None): 

434 xplot, yplot = self.data 

435 dgroup = self.dgroup 

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

437 

438 ensure_en_orig(dgroup) 

439 

440 idx, norm_page = self.parent.get_nbpage('xasnorm') 

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

442 

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

448 

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) 

458 

459 dgroup.xplot = dgroup.energy = eshift + dgroup.energy_orig[:] 

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

461 self.parent.process_normalization(dgroup) 

462 

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) 

469 

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 

474 

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) 

480 

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 

486 

487 xlim, ylim = get_view_limits(ppanel) 

488 path, fname = path_split(dgroup.filename) 

489 

490 wids = self.wids 

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

492 e0_old = dgroup.e0 

493 e0_new = dgroup.e0 + eshift 

494 

495 xmin = min(e0_old, e0_new) - 25 

496 xmax = max(e0_old, e0_new) + 50 

497 

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

499 

500 ylabel = plotlabels.norm 

501 if use_deriv: 

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

503 ylabel = plotlabels.dmude 

504 

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

506 xlabel=plotlabels.energy, show_legend=True) 

507 

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] 

511 

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

513 if use_deriv: 

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

515 

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) 

519 

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

521 linewidth=2, label='shifted', 

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

523 

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) 

529 

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

535 

536 

537 def GetResponse(self): 

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

539 

540class RebinDataDialog(wx.Dialog): 

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

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

543 

544 self.parent = parent 

545 self.controller = controller 

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

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

548 

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] 

555 

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) 

560 

561 self.wids = wids = {} 

562 

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

564 action=self.on_groupchoice) 

565 

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

567 

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

569 

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) 

578 

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) 

582 

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

584 if wname != 'grouplist': 

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

586 

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

590 

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

594 

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

596 size=(250, -1)) 

597 

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

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

600 

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

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

603 

604 add_text('E0: ') 

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

606 add_text(' eV', newrow=False) 

607 

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) 

613 

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) 

619 

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) 

625 

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) 

631 

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

638 

639 fit_dialog_window(self, panel) 

640 

641 self.on_rebin() 

642 self.plot_results(keep_limits=False) 

643 

644 def onDone(self, event=None): 

645 self.Destroy() 

646 

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

651 

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) 

666 

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

676 

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) 

683 

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

690 

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

699 

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 

708 

709 ogroup = self.controller.get_group(fname) 

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

711 

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

713 self.parent.process_normalization(ngroup) 

714 

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) 

718 

719 def on_done(self, event=None): 

720 self.Destroy() 

721 

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) 

728 

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] 

733 

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) 

738 

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

746 

747 def GetResponse(self): 

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

749 

750class SmoothDataDialog(wx.Dialog): 

751 """dialog for smoothing data""" 

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

753 

754 self.parent = parent 

755 self.controller = controller 

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

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

758 

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

760 

761 

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) 

766 

767 self.wids = wids = {} 

768 

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

770 action=self.on_groupchoice) 

771 

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

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

774 

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

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

777 

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

779 action=self.on_smooth) 

780 self.smooth_op.SetSelection(0) 

781 

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

783 action=self.on_smooth) 

784 self.conv_op.SetSelection(0) 

785 

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

787 

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

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

790 

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

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

793 

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

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

796 

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

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

799 

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

801 

802 

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

806 

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

808 size=(250, -1)) 

809 

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

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

812 

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

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

815 

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) 

822 

823 add_text('Convolution Form: ') 

824 panel.Add(self.conv_op) 

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

826 panel.Add(self.sigma) 

827 

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

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

830 

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

832 

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) 

839 

840 self.plot_results(keep_limits=False) 

841 

842 def onDone(self, event=None): 

843 self.Destroy() 

844 

845 

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

850 

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

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

853 

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

873 

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

881 

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

883 

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) 

888 

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

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

891 self.plot_results() 

892 

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

901 

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) 

907 

908 xplot, yplot = self.data 

909 ngroup.energy = ngroup.xplot = xplot 

910 ngroup.mu = ngroup.yplot = yplot 

911 

912 ogroup = self.controller.get_group(fname) 

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

914 

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) 

919 

920 def on_done(self, event=None): 

921 self.Destroy() 

922 

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) 

930 

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] 

934 

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) 

939 

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

947 

948 def GetResponse(self): 

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

950 

951class DeconvolutionDialog(wx.Dialog): 

952 """dialog for energy deconvolution""" 

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

954 

955 self.parent = parent 

956 self.controller = controller 

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

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

959 

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

961 

962 

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) 

967 

968 self.wids = wids = {} 

969 

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

971 action=self.on_groupchoice) 

972 

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

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

975 

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

977 

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

979 action=self.on_deconvolve) 

980 

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

982 increment=0.1, action=self.on_deconvolve) 

983 

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

987 

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

991 

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

993 size=(250, -1)) 

994 

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

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

997 

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

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

1000 

1001 add_text('Functional Form: ') 

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

1003 

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

1012 

1013 fit_dialog_window(self, panel) 

1014 self.plot_results(keep_limits=False) 

1015 

1016 def onDone(self, event=None): 

1017 self.Destroy() 

1018 

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 

1027 

1028 ogroup = self.controller.get_group(fname) 

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

1030 

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) 

1035 

1036 

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

1041 

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

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

1044 

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

1046 

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) 

1052 

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

1054 self.plot_results() 

1055 

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

1064 

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) 

1071 

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] 

1076 

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) 

1081 

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

1089 

1090 def GetResponse(self): 

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

1092 

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

1102 

1103 self.reset_data_history() 

1104 xplot, yplot = self.data 

1105 

1106 xrange = (max(xplot) - min(xplot)) 

1107 xmax = int(max(xplot) + xrange/5.0) 

1108 xmin = int(min(xplot) - xrange/5.0) 

1109 

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

1111 if lastx is None: 

1112 lastx = max(xplot) 

1113 

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 

1119 

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

1121 action=self.on_groupchoice) 

1122 

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

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

1125 

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

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

1128 

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

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

1131 

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

1133 action=self.on_undo) 

1134 

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

1138 

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

1140 size=(250, -1)) 

1141 

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

1143 

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) 

1151 

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 

1158 

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

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

1161 

1162 self.choice_range.SetStringSelection('above') 

1163 wids['range2'].Disable() 

1164 

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

1166 size=(175, -1), 

1167 action=self.on_plotchoice) 

1168 

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

1170 

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

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

1173 

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

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

1176 

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

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

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

1180 panel.Add(br_xlast) 

1181 

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

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

1184 

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) 

1190 

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

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

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

1194 

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

1196 

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) 

1203 

1204 panel.pack() 

1205 

1206 fit_dialog_window(self, panel) 

1207 self.plot_results(keep_limits=False) 

1208 

1209 def onDone(self, event=None): 

1210 self.Destroy() 

1211 

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 

1220 

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) 

1240 

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) 

1246 

1247 def on_rangechoice(self, event=None): 

1248 sel = self.choice_range.GetStringSelection() 

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

1250 

1251 

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

1257 

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

1263 

1264 def on_floatvalue(self, val=None, opt='_', **kws): 

1265 self.plot_markers = opt 

1266 self.plot_results() 

1267 

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

1287 

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

1294 

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

1306 

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

1314 

1315 ngroup.energy = ngroup.xplot = xplot[mask] 

1316 ngroup.mu = ngroup.yplot = yplot[mask] 

1317 ngroup.energy_orig = 1.0*ngroup.energy 

1318 

1319 ogroup = self.controller.get_group(fname) 

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

1321 

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) 

1325 

1326 self.parent.process_normalization(ngroup) 

1327 

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

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

1330 

1331 xplot, yplot = self.data 

1332 

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

1337 

1338 dgroup = self.dgroup 

1339 

1340 path, fname = path_split(dgroup.filename) 

1341 

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

1343 plottype = DEGLITCH_PLOTS[plotstr] 

1344 

1345 xlabel=plotlabels.energy 

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

1347 xmin = self.dgroup.e0 

1348 xlabel = xlabel=plotlabels.ewithk 

1349 

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

1351 delay_draw=True) 

1352 

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

1354 'norm': plotlabels.norm, 

1355 'chie': plotlabels.chie, 

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

1357 }.get(plottype, plotlabels.norm) 

1358 

1359 dgroup.plot_xlabel = xlabel 

1360 dgroup.plot_ylabel = ylabel 

1361 

1362 xlim, ylim = get_view_limits(ppanel) 

1363 

1364 ppanel.plot(xplot, yplot, zorder=10, marker=None, linewidth=3, 

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

1366 

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) 

1372 

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) 

1377 

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

1382 

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) 

1401 

1402 

1403 ppanel.canvas.draw() 

1404 

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

1406 

1407 def GetResponse(self): 

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

1409 

1410 

1411SPECCALC_SETUP = """#From SpectraCalc dialog: 

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

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

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

1415""" 

1416 

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

1420 

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

1429 

1430 

1431class SpectraCalcDialog(wx.Dialog): 

1432 """dialog for adding and subtracting spectra""" 

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

1434 

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

1440 

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) 

1445 

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) 

1450 

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

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

1453 

1454 self.wids = wids = {} 

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

1456 

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

1458 

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

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

1461 

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) 

1469 

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) 

1477 

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) 

1481 

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

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

1484 

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

1486 

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

1490 

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) 

1501 

1502 def onDone(self, event=None): 

1503 self.Destroy() 

1504 

1505 def on_docalc(self, event=None): 

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

1507 

1508 self.yname = 'mu' 

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

1510 self.yname = 'norm' 

1511 

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 

1518 

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' 

1525 

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

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

1528 

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

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

1531 xname=xname, yname=self.yname)) 

1532 

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

1536 

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) 

1543 

1544 gmap = [] 

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

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

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

1548 

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

1553 

1554 

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

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

1557 

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) 

1562 

1563 def GetResponse(self): 

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

1565 

1566class EnergyUnitsDialog(wx.Dialog): 

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

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

1569 

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

1571 

1572 self.parent = parent 

1573 self.energy = 1.0*energy_array 

1574 

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) 

1579 

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

1587 

1588 self.mono_dspace.Disable() 

1589 self.steps2deg.Disable() 

1590 

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

1592 panel.Add(self.en_units) 

1593 

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

1595 panel.Add(self.mono_dspace) 

1596 

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

1598 panel.Add(self.steps2deg) 

1599 panel.Add((5, 5)) 

1600 

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

1602 panel.pack() 

1603 

1604 fit_dialog_window(self, panel) 

1605 

1606 

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

1611 

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 

1617 

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) 

1631 

1632class MergeDialog(wx.Dialog): 

1633 """dialog for merging groups""" 

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

1635 

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) 

1639 

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

1641 

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

1645 

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

1647 panel.Add(self.master_group) 

1648 

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

1650 panel.Add(self.yarray_name) 

1651 

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

1653 panel.Add(self.group_name) 

1654 

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

1656 

1657 panel.pack() 

1658 fit_dialog_window(self, panel) 

1659 

1660 

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) 

1671 

1672 

1673class ExportCSVDialog(wx.Dialog): 

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

1675 

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'} 

1684 

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'} 

1694 

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

1696 'space': ' ', 

1697 'tab': '\t'} 

1698 

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

1705 

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

1707 

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

1709 panel.Add(self.master_group) 

1710 

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

1712 panel.Add(self.xarray_name) 

1713 

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

1715 panel.Add(self.yarray_name) 

1716 

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) 

1722 

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) 

1732 

1733 def onSaveIndividualFiles(self, event=None): 

1734 save_individual = self.save_individual_files.IsChecked() 

1735 self.master_group.Enable(not save_individual) 

1736 

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) 

1752 

1753class QuitDialog(wx.Dialog): 

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

1755 

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) 

1762 

1763 status, filename, stime = message 

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

1765 

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!' 

1770 

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

1772 

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) 

1778 

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

1785 

1786 fit_dialog_window(self, panel) 

1787 

1788 def GetResponse(self): 

1789 self.Raise() 

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

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

1792 return response(ok,) 

1793 

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) 

1801 

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

1803 

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) 

1809 

1810 panel.pack() 

1811 fit_dialog_window(self, panel) 

1812 

1813 

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) 

1822 

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) 

1831 

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

1833 newrow=True, dcol=2) 

1834 

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

1836 panel.pack() 

1837 fit_dialog_window(self, panel) 

1838 

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) 

1847 

1848 

1849class LoadSessionDialog(wx.Frame): 

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

1851 

1852 xasgroups_name = '_xasgroups' 

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

1854 

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) 

1862 

1863 x0, y0 = parent.GetPosition() 

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

1865 

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

1867 splitter.SetMinimumPaneSize(250) 

1868 

1869 leftpanel = wx.Panel(splitter) 

1870 rightpanel = wx.Panel(splitter) 

1871 

1872 ltop = wx.Panel(leftpanel) 

1873 

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) 

1878 

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

1882 

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) 

1887 

1888 pack(ltop, tsizer) 

1889 

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) 

1894 

1895 

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

1897 self.wids = wids = {} 

1898 

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

1900 symtable = controller.symtable 

1901 

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) 

1914 

1915 

1916 checked = [] 

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

1918 self.grouplist.Append(fname) 

1919 checked.append(fname) 

1920 

1921 self.grouplist.SetCheckedStrings(checked) 

1922 

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

1924 group_names.append(self.xasgroups_name) 

1925 group_names.extend(self.feffgroups_name) 

1926 

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) 

1931 

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

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

1934 

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) 

1938 

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 

1951 

1952 i += 1 

1953 

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

1959 

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

1965 

1966 sizer = wx.BoxSizer(wx.VERTICAL) 

1967 sizer.Add(panel, 0, LEFT, 2) 

1968 sizer.Add(self.plotpanel, 1, LEFT, 2) 

1969 

1970 pack(rightpanel, sizer) 

1971 

1972 splitter.SplitVertically(leftpanel, rightpanel, 1) 

1973 self.SetSize((750, 725)) 

1974 

1975 self.Show() 

1976 self.Raise() 

1977 

1978 def plot_messages(self, msg, panel=1): 

1979 pass 

1980 

1981 def onSelAll(self, event=None): 

1982 self.grouplist.SetCheckedStrings(list(self.allgroups.keys())) 

1983 

1984 def onSelNone(self, event=None): 

1985 self.grouplist.SetCheckedStrings([]) 

1986 

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) 

1993 

1994 def onPlotChoice(self, event=None): 

1995 fname = self.grouplist.GetStringSelection() 

1996 gname = self.allgroups.get(fname, None) 

1997 self.plot_group(gname, fname) 

1998 

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) 

2015 

2016 if len(yplot) > 1: 

2017 self.plotpanel.plot(xplot, yplot, xlabel=xlabel, 

2018 ylabel=ylabel, title=fname) 

2019 

2020 

2021 def onShowConfig(self, event=None): 

2022 DictFrame(parent=self.parent, 

2023 data=self.session.config, 

2024 title=f"Session Configuration for '{self.filename}'") 

2025 

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) 

2035 

2036 def onClose(self, event=None): 

2037 self.Destroy() 

2038 

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) 

2044 

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) 

2049 

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

2060 

2061 lcmd = ''.join(lcmd) + ')' 

2062 

2063 cmds = ["# Loading Larch Session with ", lcmd, '######'] 

2064 

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 

2072 

2073 self.controller.recentfiles.append((time.time(), self.filename)) 

2074 

2075 wx.CallAfter(self.Destroy) 

2076 if last_fname is not None: 

2077 self.parent.ShowFile(filename=last_fname)