Coverage for larch/wxxas/xasgui.py: 0%

1346 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-10-16 21:04 +0000

1#!/usr/bin/env python 

2""" 

3XANES Data Viewer and Analysis Tool 

4""" 

5import os 

6import sys 

7import time 

8import copy 

9import platform 

10from pathlib import Path 

11from importlib import import_module 

12from threading import Thread 

13import numpy as np 

14np.seterr(all='ignore') 

15 

16from functools import partial 

17 

18import wx 

19import wx.lib.scrolledpanel as scrolled 

20import wx.lib.agw.flatnotebook as flat_nb 

21from wx.adv import AboutBox, AboutDialogInfo 

22 

23from wx.richtext import RichTextCtrl 

24 

25import larch 

26from larch import Group, Journal, Entry 

27from larch.io import save_session, read_session 

28from larch.math import index_of 

29from larch.utils import (isotime, time_ago, get_cwd, 

30 is_gzip, uname, path_split) 

31from larch.utils.strutils import (file2groupname, unique_name, 

32 common_startstring, asfloat, fix_varname) 

33 

34from larch.larchlib import read_workdir, save_workdir, read_config, save_config 

35 

36from larch.wxlib import (LarchFrame, ColumnDataFileFrame, AthenaImporter, 

37 SpecfileImporter, XasImporter, FileCheckList, FloatCtrl, 

38 FloatSpin, SetTip, get_icon, SimpleText, TextCtrl, 

39 pack, Button, Popup, HLine, FileSave, FileOpen, 

40 Choice, Check, MenuItem, HyperText, set_color, COLORS, 

41 CEN, LEFT, FRAMESTYLE, Font, FONTSIZE, 

42 flatnotebook, LarchUpdaterDialog, GridPanel, 

43 CIFFrame, Structure2FeffFrame, FeffResultsFrame, LarchWxApp, OkCancel, 

44 ExceptionPopup, set_color) 

45 

46 

47from larch.wxlib.plotter import get_display 

48 

49from larch.fitting import fit_report 

50from larch.site_config import icondir, home_dir, user_larchdir 

51from larch.version import check_larchversion 

52 

53from .xas_controller import XASController 

54from .taskpanel import GroupJournalFrame 

55from .config import (FULLCONF, CONF_SECTIONS, CVar, ATHENA_CLAMPNAMES, 

56 LARIX_PANELS, LARIX_MODES) 

57 

58from .xas_dialogs import (MergeDialog, RenameDialog, RemoveDialog, 

59 DeglitchDialog, ExportCSVDialog, RebinDataDialog, 

60 EnergyCalibrateDialog, SmoothDataDialog, 

61 OverAbsorptionDialog, DeconvolutionDialog, 

62 SpectraCalcDialog, QuitDialog, LoadSessionDialog, 

63 fit_dialog_window) 

64 

65from larch.io import (read_ascii, read_xdi, read_gsexdi, gsescan_group, 

66 groups2csv, is_athena_project, 

67 is_larch_session_file, 

68 AthenaProject, make_hashkey, is_specfile, open_specfile) 

69from larch.io.xas_data_source import open_xas_source 

70 

71from larch.xafs import pre_edge, pre_edge_baseline 

72 

73# FNB_STYLE = flat_nb.FNB_NO_X_BUTTON 

74FNB_STYLE = flat_nb.FNB_X_ON_TAB 

75FNB_STYLE |= flat_nb.FNB_SMART_TABS|flat_nb.FNB_NO_NAV_BUTTONS 

76 

77LEFT = wx.ALIGN_LEFT 

78CEN |= wx.ALL 

79FILE_WILDCARDS = "Data Files|*.0*;*.dat;*.DAT;*.xdi;*.prj;*.sp*c;*.h*5;*.larix|All files (*.*)|*.*" 

80 

81ICON_FILE = 'onecone.ico' 

82LARIX_SIZE = (1050, 850) 

83LARIX_MINSIZE = (500, 250) 

84PLOTWIN_SIZE = (550, 550) 

85 

86QUIT_MESSAGE = '''Really Quit? You may want to save your project before quitting. 

87 This is not done automatically!''' 

88 

89LARIX_TITLE = "Larix: XAS Visualization and Analysis" 

90 

91def assign_gsescan_groups(group): 

92 labels = group.array_labels 

93 labels = [] 

94 for i, name in enumerate(group.pos_desc): 

95 name = fix_varname(name.lower()) 

96 labels.append(name) 

97 setattr(group, name, group.pos[i, :]) 

98 

99 for i, name in enumerate(group.sums_names): 

100 name = fix_varname(name.lower()) 

101 labels.append(name) 

102 setattr(group, name, group.sums_corr[i, :]) 

103 

104 for i, name in enumerate(group.det_desc): 

105 name = fix_varname(name.lower()) 

106 labels.append(name) 

107 setattr(group, name, group.det_corr[i, :]) 

108 

109 group.array_labels = labels 

110 

111 

112class PreferencesFrame(wx.Frame): 

113 """ edit preferences""" 

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

115 self.controller = controller 

116 wx.Frame.__init__(self, None, -1, 'Larix Preferences', 

117 style=FRAMESTYLE, size=(700, 725)) 

118 

119 sizer = wx.BoxSizer(wx.VERTICAL) 

120 tpanel = wx.Panel(self) 

121 

122 self.title = SimpleText(tpanel, 'Edit Preference and Defaults', 

123 size=(500, 25), 

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

125 colour=COLORS['nb_text']) 

126 

127 self.save_btn = Button(tpanel, 'Save for Future sessions', 

128 size=(200, -1), action=self.onSave) 

129 

130 self.nb = flatnotebook(tpanel, {}) 

131 self.wids = {} 

132 conf = self.controller.config 

133 

134 def text(panel, label, size): 

135 return SimpleText(panel, label, size=(size, -1), style=LEFT) 

136 

137 for name, data in FULLCONF.items(): 

138 self.wids[name] = {} 

139 

140 panel = GridPanel(self.nb, ncols=3, nrows=8, pad=3, itemstyle=LEFT) 

141 panel.SetFont(Font(FONTSIZE)) 

142 title = CONF_SECTIONS.get(name, name) 

143 title = SimpleText(panel, f" {title}", 

144 size=(550, -1), font=Font(FONTSIZE+2), 

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

146 

147 self.wids[name]['_key_'] = SimpleText(panel, " <name> ", 

148 size=(150, -1), style=LEFT) 

149 self.wids[name]['_help_'] = SimpleText(panel, " <click on name for description> ", 

150 size=(525, 30), style=LEFT) 

151 

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

153 panel.Add(title, dcol=4) 

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

155 panel.Add(self.wids[name]['_key_']) 

156 panel.Add(self.wids[name]['_help_'], dcol=3) 

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

158 panel.Add(HLine(panel, (625, 3)), dcol=4) 

159 

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

161 panel.Add(text(panel, 'Name', 150)) 

162 

163 panel.Add(text(panel, 'Value', 250)) 

164 panel.Add(text(panel, 'Factory Default Value', 225)) 

165 

166 for key, cvar in data.items(): 

167 val = conf[name][key] 

168 cb = partial(self.update_value, section=name, option=key) 

169 helpcb = partial(self.update_help, section=name, option=key) 

170 wid = None 

171 if cvar.dtype == 'choice': 

172 wid = Choice(panel, size=(250, -1), choices=cvar.choices, action=cb) 

173 if not isinstance(val, str): val = str(val) 

174 wid.SetStringSelection(val) 

175 elif cvar.dtype == 'bool': 

176 wid = Choice(panel, size=(250, -1), choices=['True', 'False'], action=cb) 

177 wid.SetStringSelection('True' if val else 'False') 

178 elif cvar.dtype in ('int', 'float'): 

179 digits = 2 if cvar.dtype == 'float' else 0 

180 wid = FloatSpin(panel, value=val, min_val=cvar.min, max_val=cvar.max, 

181 digits=digits, increment=cvar.step, size=(250, -1), action=cb) 

182 else: 

183 wid = TextCtrl(panel, size=(250, -1), value=val, action=cb) 

184 

185 label = HyperText(panel, key, action=helpcb, size=(150, -1)) 

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

187 panel.Add(label) 

188 panel.Add(wid) 

189 panel.Add(text(panel, f'{cvar.value}', 225)) 

190 SetTip(wid, cvar.desc) 

191 self.wids[name][key] = wid 

192 

193 panel.pack() 

194 self.nb.AddPage(panel, name, True) 

195 

196 self.nb.SetSelection(0) 

197 

198 sizer.Add(self.title, 0, LEFT, 3) 

199 sizer.Add(self.save_btn, 0, LEFT, 5) 

200 sizer.Add((5, 5), 0, LEFT, 5) 

201 sizer.Add(self.nb, 1, LEFT|wx.EXPAND, 5) 

202 pack(tpanel, sizer) 

203 w0, h0 = self.GetSize() 

204 w1, h1 = self.GetBestSize() 

205 self.SetSize((max(w0, w1)+25, max(h0, h1)+25)) 

206 

207 self.Show() 

208 self.Raise() 

209 

210 def update_help(self, label=None, event=None, section='main', option=None): 

211 cvar = FULLCONF[section][option] 

212 self.wids[section]['_key_'].SetLabel("%s : " % option) 

213 self.wids[section]['_help_'].SetLabel(cvar.desc) 

214 

215 def update_value(self, event=None, section='main', option=None): 

216 cvar = FULLCONF[section][option] 

217 wid = self.wids[section][option] 

218 value = cvar.value 

219 if cvar.dtype == 'bool': 

220 value = wid.GetStringSelection().lower().startswith('t') 

221 elif cvar.dtype == 'choice': 

222 value = wid.GetStringSelection() 

223 elif cvar.dtype == 'int': 

224 value = int(wid.GetValue()) 

225 elif cvar.dtype == 'float': 

226 value = float(wid.GetValue()) 

227 else: 

228 value = wid.GetValue() 

229 self.controller.config[section][option] = value 

230 

231 def onSave(self, event=None): 

232 self.controller.save_config() 

233 

234class PanelSelectionFrame(wx.Frame): 

235 """select analysis panels to display""" 

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

237 self.controller = controller 

238 self.parent = parent 

239 wx.Frame.__init__(self, None, -1, 'Larix Configuration: Analysis Panels', 

240 style=FRAMESTYLE, size=(650, 500)) 

241 self.wids = {} 

242 

243 style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL 

244 labstyle = wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ALL 

245 rlabstyle = wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL|wx.ALL 

246 tstyle = wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL 

247 

248 font = parent.GetFont() 

249 

250 titlefont = self.GetFont() 

251 titlefont.PointSize += 2 

252 titlefont.SetWeight(wx.BOLD) 

253 

254 sizer = wx.GridBagSizer(5, 5) 

255 panel = scrolled.ScrolledPanel(self, size=(700, 750), 

256 style=wx.GROW|wx.TAB_TRAVERSAL) 

257 

258 title = SimpleText(panel, 'Select Larix Modes and Analysis Panels', 

259 size=(500, 25), 

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

261 colour=COLORS['nb_text']) 

262 modetitle = SimpleText(panel, 'Analysis Mode: ') 

263 panetitle = SimpleText(panel, 'Select Individual Analysis Panels: ') 

264 

265 self.wids = wids = {} 

266 self.current_mode = self.parent.mode 

267 modenames = [m[0] for m in LARIX_MODES.values()] 

268 

269 wids['modechoice'] = Choice(panel, choices=modenames, size=(350, -1), 

270 action=self.on_modechoice) 

271 wids['modechoice'].SetStringSelection(LARIX_MODES[self.current_mode][0]) 

272 

273 page_map = self.parent.get_panels() 

274 irow = 0 

275 sizer.Add(title, (irow, 0), (1, 2), labstyle|wx.ALL, 3) 

276 irow = 1 

277 sizer.Add(modetitle, (irow, 0), (1, 1), labstyle|wx.ALL, 3) 

278 sizer.Add(wids['modechoice'], (irow, 1), (1, 1), labstyle|wx.ALL, 3) 

279 irow += 1 

280 sizer.Add(HLine(panel, (325, 3)), (irow, 0), (1, 2), labstyle|wx.ALL, 3) 

281 irow += 1 

282 sizer.Add(panetitle, (irow, 0), (1, 2), labstyle|wx.ALL, 3) 

283 

284 self.selections = {} 

285 strlen = 30 

286 for pagename in page_map: 

287 strlen = max(strlen, len(pagename)) 

288 

289 for key, atab in LARIX_PANELS.items(): 

290 iname = (atab.title + ' '*strlen)[:strlen] 

291 cb = wx.CheckBox(panel, -1, iname)#, style=wx.ALIGN_RIGHT) 

292 cb.SetValue(atab.title in page_map) 

293 desc = SimpleText(panel, LARIX_PANELS[key].desc) 

294 self.selections[key] = cb 

295 irow += 1 

296 sizer.Add(cb, (irow, 0), (1, 1), labstyle, 5) 

297 sizer.Add(desc, (irow, 1), (1, 1), labstyle, 5) 

298 

299 irow += 1 

300 sizer.Add(HLine(panel, (325, 3)), (irow, 0), (1, 2), labstyle|wx.ALL, 3) 

301 

302 btn_ok = Button(panel, 'Apply', size=(70, -1), action=self.OnOK) 

303 btn_cancel = Button(panel, 'Done', size=(70, -1), action=self.OnCancel) 

304 

305 irow += 1 

306 sizer.Add(btn_ok, (irow, 0), (1, 1), labstyle|wx.ALL, 3) 

307 sizer.Add(btn_cancel, (irow, 1), (1, 1), labstyle|wx.ALL, 3) 

308 

309 pack(panel, sizer) 

310 panel.SetupScrolling() 

311 mainsizer = wx.BoxSizer(wx.VERTICAL) 

312 mainsizer.Add(panel, 1, wx.GROW|wx.ALL, 1) 

313 

314 self.SetMinSize((750, 450)) 

315 pack(self, mainsizer) 

316 self.Show() 

317 self.Raise() 

318 

319 def on_modechoice(self, event=None): 

320 modename = event.GetString() 

321 self.current_mode = 'xas' 

322 for key, dat in LARIX_MODES.items(): 

323 if dat[0] == modename: 

324 self.current_mode = key 

325 panels = LARIX_MODES[self.current_mode][1] 

326 self.Freeze() 

327 for name in self.selections: 

328 self.selections[name].SetValue(False) 

329 

330 for name in panels: 

331 if name in self.selections: 

332 self.selections[name].SetValue(True) 

333 self.Thaw() 

334 

335 def OnOK(self, event=None): 

336 self.parent.Hide() 

337 self.parent.Freeze() 

338 for i in range(self.parent.nb.GetPageCount()): 

339 self.parent.nb.DeletePage(0) 

340 

341 for name, cb in self.selections.items(): 

342 if cb.IsChecked(): 

343 self.parent.add_analysis_panel(name) 

344 self.parent.nb.SetSelection(0) 

345 self.parent.mode = self.current_mode 

346 self.parent.Thaw() 

347 self.parent.Show() 

348 

349 def OnCancel(self, event=None): 

350 self.Destroy() 

351 

352class LarixFrame(wx.Frame): 

353 _about = f"""{LARIX_TITLE} 

354 Matt Newville <newville @ cars.uchicago.edu> 

355 """ 

356 def __init__(self, parent=None, _larch=None, filename=None, 

357 with_wx_inspect=False, mode='xas', check_version=True, **kws): 

358 wx.Frame.__init__(self, parent, -1, size=LARIX_SIZE, style=FRAMESTYLE) 

359 

360 if check_version: 

361 def version_checker(): 

362 self.vinfo = check_larchversion() 

363 version_thread = Thread(target=version_checker) 

364 version_thread.start() 

365 

366 self.with_wx_inspect = with_wx_inspect 

367 self.mode = mode 

368 if self.mode not in LARIX_MODES: 

369 self.mode = 'xas' 

370 self.last_col_config = {} 

371 self.last_spec_config = {} 

372 self.last_session_file = None 

373 self.last_session_read = None 

374 self.last_athena_file = None 

375 self.paths2read = [] 

376 self.current_filename = filename 

377 title = LARIX_TITLE 

378 

379 self.larch_buffer = parent 

380 if not isinstance(parent, LarchFrame): 

381 self.larch_buffer = LarchFrame(_larch=_larch, 

382 parent=self, 

383 is_standalone=False, 

384 with_raise=False, 

385 exit_on_close=False) 

386 

387 self.larch = self.larch_buffer.larchshell 

388 

389 self.controller = XASController(wxparent=self, _larch=self.larch) 

390 iconfile = Path(icondir, ICON_FILE).as_posix() 

391 self.SetIcon(wx.Icon(iconfile, wx.BITMAP_TYPE_ICO)) 

392 

393 self.last_autosave = 0 

394 self.last_save_message = ('Session has not been saved', '', '') 

395 

396 

397 self.timers = {'pin': wx.Timer(self), 

398 'autosave': wx.Timer(self)} 

399 self.Bind(wx.EVT_TIMER, self.onPinTimer, self.timers['pin']) 

400 self.Bind(wx.EVT_TIMER, self.onAutoSaveTimer, self.timers['autosave']) 

401 self.cursor_dat = {} 

402 

403 self.subframes = {} 

404 self.plotframe = None 

405 self.SetTitle(title) 

406 self.SetSize(LARIX_SIZE) 

407 self.SetMinSize(LARIX_MINSIZE) 

408 self.SetFont(Font(FONTSIZE)) 

409 self.createMainPanel() 

410 self.createMenus() 

411 self.statusbar = self.CreateStatusBar(2, style=wx.STB_DEFAULT_STYLE) 

412 self.statusbar.SetStatusWidths([-3, -1]) 

413 statusbar_fields = [" ", "ready"] 

414 for i in range(len(statusbar_fields)): 

415 self.statusbar.SetStatusText(statusbar_fields[i], i) 

416 self.Show() 

417 

418 self.Raise() 

419 self.statusbar.SetStatusText('ready', 1) 

420 self.timers['autosave'].Start(30_000) 

421 

422 if self.current_filename is not None: 

423 wx.CallAfter(self.onRead, self.current_filename) 

424 

425 # show_wxsizes(self) 

426 if check_version: 

427 version_thread.join() 

428 if self.vinfo is not None: 

429 if self.vinfo.update_available: 

430 self.statusbar.SetStatusText(f'Larch Version {self.vinfo.remote_version} is available!', 0) 

431 self.statusbar.SetStatusText(f'Larch Version {self.vinfo.local_version}', 1) 

432 else: 

433 self.statusbar.SetStatusText(f'Larch Version {self.vinfo.local_version} (latest)', 1) 

434 xpos, ypos = self.GetPosition() 

435 xsiz, ysiz = self.GetSize() 

436 plotpos = (xpos+xsiz+2, ypos) 

437 self.controller.get_display(stacked=False, position=plotpos) 

438 self.Raise() 

439 

440 

441 def createMainPanel(self): 

442 display0 = wx.Display(0) 

443 client_area = display0.ClientArea 

444 xmin, ymin, xmax, ymax = client_area 

445 xpos = int((xmax-xmin)*0.02) + xmin 

446 ypos = int((ymax-ymin)*0.04) + ymin 

447 self.SetPosition((xpos, ypos)) 

448 

449 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE, 

450 size=(700, 700)) 

451 splitter.SetMinimumPaneSize(250) 

452 

453 leftpanel = wx.Panel(splitter) 

454 ltop = wx.Panel(leftpanel) 

455 

456 def Btn(msg, x, act): 

457 b = Button(ltop, msg, size=(x, 30), action=act) 

458 b.SetFont(Font(FONTSIZE)) 

459 return b 

460 

461 sel_none = Btn('Select None', 120, self.onSelNone) 

462 sel_all = Btn('Select All', 120, self.onSelAll) 

463 

464 file_actions = [('Show Group Journal', self.onGroupJournal), 

465 ('Copy Group', self.onCopyGroup), 

466 ('Rename Group', self.onRenameGroup), 

467 ('Remove Group', self.onRemoveGroup)] 

468 

469 self.controller.filelist = FileCheckList(leftpanel, main=self, 

470 pre_actions=file_actions, 

471 select_action=self.ShowFile, 

472 remove_action=self.RemoveFile) 

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

474 # self.controller.filelist.check_event = self.filelist_check_event 

475 self.controller.filelist.Bind(wx.EVT_CHECKLISTBOX, self.filelist_check_event) 

476 

477 tsizer = wx.BoxSizer(wx.HORIZONTAL) 

478 tsizer.Add(sel_all, 1, LEFT|wx.GROW, 1) 

479 tsizer.Add(sel_none, 1, LEFT|wx.GROW, 1) 

480 pack(ltop, tsizer) 

481 

482 sizer = wx.BoxSizer(wx.VERTICAL) 

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

484 sizer.Add(self.controller.filelist, 1, LEFT|wx.GROW|wx.ALL, 1) 

485 

486 pack(leftpanel, sizer) 

487 

488 # right hand side 

489 panel = scrolled.ScrolledPanel(splitter) 

490 panel.SetSize((650, 650)) 

491 panel.SetMinSize((450, 550)) 

492 sizer = wx.BoxSizer(wx.VERTICAL) 

493 self.title = SimpleText(panel, ' ', size=(500, 25), 

494 font=Font(FONTSIZE+3), style=LEFT, 

495 colour=COLORS['nb_text']) 

496 

497 ir = 0 

498 sizer.Add(self.title, 0, CEN, 3) 

499 

500 self.nb = flatnotebook(panel, {}, 

501 panelkws={'controller': self.controller}, 

502 on_change=self.onNBChanged, 

503 style=FNB_STYLE, 

504 size=(700, 700)) 

505 panels = LARIX_MODES[self.mode][1] 

506 for key, atab in LARIX_PANELS.items(): 

507 if key in panels: 

508 self.add_analysis_panel(key) 

509 self.nb.SetSelection(0) 

510 

511 sizer.Add(self.nb, 1, LEFT|wx.EXPAND, 2) 

512 panel.SetupScrolling() 

513 

514 pack(panel, sizer) 

515 splitter.SplitVertically(leftpanel, panel, 1) 

516 

517 def get_panels(self): 

518 "return current mapping of displayed panels" 

519 out = {} 

520 for i in range(self.nb.GetPageCount()): 

521 out[self.nb.GetPageText(i)] = i 

522 return out 

523 

524 def add_analysis_panel(self, name): 

525 """make sure an analysis panel is displayed , 

526 and return its current index in the set of panels 

527 or None to signal "unknonwn panel name" 

528 """ 

529 atab = LARIX_PANELS.get(name, None) 

530 if atab is None: 

531 return None 

532 

533 current_panels = self.get_panels() 

534 #print("Add panel , current = ", name, atab.title) 

535 if atab.title in current_panels: 

536 return current_panels[atab.title] 

537 # not in current tabs, so add it 

538 cons = atab.constructor.split('.') 

539 clsname = cons.pop() 

540 module = '.'.join(cons) 

541 try: 

542 cls = getattr(import_module(module), clsname) 

543 except (ImportError, KeyError): 

544 cls = None 

545 print(f"cannot find analysis panel {atab}") 

546 if cls is not None: 

547 # print("Add panel , current = ", name, atab) 

548 nbpanel = cls(parent=self, controller=self.controller) 

549 self.nb.AddPage(nbpanel, atab.title, True) 

550 current_panels = self.get_panels() 

551 return current_panels.get(atab.title, None) 

552 

553 def process_normalization(self, dgroup, force=True, use_form=True, force_mback=False): 

554 self.get_nbpage('xasnorm')[1].process(dgroup, force=force, 

555 force_mback=False, use_form=use_form) 

556 

557 def process_exafs(self, dgroup, force=True): 

558 self.get_nbpage('exafs')[1].process(dgroup, force=force) 

559 

560 def get_nbpage(self, name): 

561 "get nb page by name" 

562 name = name.lower() 

563 out = 0 

564 if name not in LARIX_PANELS: 

565 print("unknown panel : ", name, LARIX_PANELS) 

566 return 0, self.nb.GetPage(0) 

567 # print("GET NB PAGE ", name, LARIX_PANELS.get(name, 'gg')) 

568 

569 atab = LARIX_PANELS[name] 

570 current_panels = self.get_panels() 

571 if atab.title in current_panels: 

572 ipage = current_panels[atab.title] 

573 else: 

574 ipage = self.add_analysis_panel(name) 

575 # print("GET NB PAGE ", ipage, self.nb.GetPage(ipage)) 

576 return ipage, self.nb.GetPage(ipage) 

577 

578 def onNBChanged(self, event=None): 

579 callback = getattr(self.nb.GetCurrentPage(), 'onPanelExposed', None) 

580 if callable(callback): 

581 callback() 

582 

583 def onSelAll(self, event=None): 

584 self.controller.filelist.select_all() 

585 

586 def onSelNone(self, event=None): 

587 self.controller.filelist.select_none() 

588 

589 def write_message(self, msg, panel=0): 

590 """write a message to the Status Bar""" 

591 self.statusbar.SetStatusText(msg, panel) 

592 

593 def RemoveFile(self, fname=None, **kws): 

594 if fname is not None: 

595 s = str(fname) 

596 if s in self.controller.file_groups: 

597 group = self.controller.file_groups.pop(s) 

598 self.controller.sync_xasgroups() 

599 

600 def filelist_check_event(self, evt=None): 

601 """MN 2024-Feb this is included to better 'swallow' the checked event, 

602 so that it does in fact run ShowFile(). 

603 This could be removed eventually, as wxutils will also no longer run 

604 filelist.SetSelection()""" 

605 index = evt.GetSelection() 

606 label = evt.GetString() 

607 pass 

608 

609 def ShowFile(self, evt=None, groupname=None, process=True, 

610 filename=None, plot=True, **kws): 

611 if filename is None and evt is not None: 

612 filename = str(evt.GetString()) 

613 

614 if groupname is None and filename is not None: 

615 groupname = self.controller.file_groups[filename] 

616 

617 if not hasattr(self.larch.symtable, groupname): 

618 return 

619 

620 dgroup = self.controller.get_group(groupname) 

621 if dgroup is None: 

622 return 

623 

624 # print("This ShowFile ", groupname, getattr(dgroup, 'datatype', 'xydata')) 

625 

626 if (getattr(dgroup, 'datatype', 'xydata').startswith('xa') and not 

627 (hasattr(dgroup, 'norm') and hasattr(dgroup, 'e0'))): 

628 self.process_normalization(dgroup, force=True, use_form=False) 

629 if filename is None: 

630 filename = dgroup.filename 

631 self.current_filename = filename 

632 journal = getattr(dgroup, 'journal', Journal(source_desc=filename)) 

633 if isinstance(journal, Journal): 

634 sdesc = journal.get('source_desc', latest=True) 

635 else: 

636 sdesc = journal.get('source_desc', '?') 

637 

638 if isinstance(sdesc, Entry): 

639 sdesc = sdesc.value 

640 if not isinstance(sdesc, str): 

641 sdesc = repr(sdesc) 

642 self.title.SetLabel(sdesc) 

643 

644 self.controller.group = dgroup 

645 self.controller.groupname = groupname 

646 cur_panel = self.nb.GetCurrentPage() 

647 # print("Got CUR PANEL ", cur_panel) 

648 if process: 

649 cur_panel.fill_form(dgroup) 

650 cur_panel.skip_process = False 

651 cur_panel.process(dgroup=dgroup) 

652 if plot and hasattr(cur_panel, 'plot'): 

653 cur_panel.plot(dgroup=dgroup) 

654 cur_panel.skip_process = False 

655 

656 self.controller.filelist.SetStringSelection(filename) 

657 

658 

659 def createMenus(self): 

660 # ppnl = self.plotpanel 

661 self.menubar = wx.MenuBar() 

662 file_menu = wx.Menu() 

663 session_menu = wx.Menu() 

664 pref_menu = wx.Menu() 

665 group_menu = wx.Menu() 

666 xasdata_menu = wx.Menu() 

667 feff_menu = wx.Menu() 

668 m = {} 

669 

670 MenuItem(self, file_menu, "&Open Data File\tCtrl+O", 

671 "Open Data File", self.onReadDialog) 

672 

673 file_menu.AppendSeparator() 

674 

675 MenuItem(self, file_menu, "&Read Larch Session\tCtrl+R", 

676 "Read Previously Saved Session", self.onLoadSession) 

677 

678 MenuItem(self, file_menu, "&Save Larch Session\tCtrl+S", 

679 "Save Session to a File", self.onSaveSession) 

680 

681 MenuItem(self, file_menu, "&Save Larch Session As ...\tCtrl+A", 

682 "Save Session to a File", self.onSaveSessionAs) 

683 

684 

685 file_menu.AppendSeparator() 

686 MenuItem(self, file_menu, "Save Selected Groups to Athena Project File", 

687 "Save Selected Groups to an Athena Project File", 

688 self.onExportAthenaProject) 

689 

690 MenuItem(self, file_menu, "Save Selected Groups to CSV File", 

691 "Save Selected Groups to a CSV File", 

692 self.onExportCSV) 

693 

694 MenuItem(self, file_menu, "&Quit\tCtrl+Q", "Quit program", self.onClose) 

695 

696 

697 MenuItem(self, session_menu, "&Read Larch Session", 

698 "Read Previously Saved Session", self.onLoadSession) 

699 

700 MenuItem(self, session_menu, "&Save Larch Session", 

701 "Save Session to a File", self.onSaveSession) 

702 

703 MenuItem(self, session_menu, "&Save Larch Session As ...", 

704 "Save Session to a File", self.onSaveSessionAs) 

705 

706 MenuItem(self, session_menu, "Clear Larch Session", 

707 "Clear all data from this Session", self.onClearSession) 

708 

709 # autosaved session 

710 conf = self.controller.get_config('autosave', 

711 {'fileroot': 'autosave'}) 

712 froot= conf['fileroot'] 

713 

714 self.recent_menu = wx.Menu() 

715 self.get_recent_session_menu() 

716 session_menu.Append(-1, 'Recent Session Files', self.recent_menu) 

717 

718 

719 MenuItem(self, session_menu, "&Auto-Save Larch Session", 

720 f"Save Session now", self.autosave_session) 

721 

722 session_menu.AppendSeparator() 

723 

724 MenuItem(self, session_menu, 'Show Larch Buffer\tCtrl+L', 

725 'Show Larch Programming Buffer', 

726 self.onShowLarchBuffer) 

727 

728 MenuItem(self, session_menu, 'Save Larch History as Script\tCtrl+H', 

729 'Save Session History as Larch Script', 

730 self.onSaveLarchHistory) 

731 

732 if self.with_wx_inspect: 

733 MenuItem(self, session_menu, 'wx inspect\tCtrl+I', 

734 'Show wx inspection window', self.onwxInspect) 

735 

736 MenuItem(self, pref_menu, 'Select Analysis Panels and Modes', 

737 'Select Analysis Panels and Modes', 

738 self.onPanelSelect) 

739 

740 MenuItem(self, pref_menu, 'Edit Preferences\tCtrl+E', 'Customize Preferences', 

741 self.onPreferences) 

742 

743 MenuItem(self, group_menu, "Copy This Group", 

744 "Copy This Group", self.onCopyGroup) 

745 

746 MenuItem(self, group_menu, "Rename This Group", 

747 "Rename This Group", self.onRenameGroup) 

748 

749 MenuItem(self, group_menu, "Show Journal for This Group", 

750 "Show Processing Journal for This Group", self.onGroupJournal) 

751 

752 

753 group_menu.AppendSeparator() 

754 

755 MenuItem(self, group_menu, "Remove Selected Groups", 

756 "Remove Selected Group", self.onRemoveGroups) 

757 

758 group_menu.AppendSeparator() 

759 

760 MenuItem(self, group_menu, "Merge Selected Groups", 

761 "Merge Selected Groups", self.onMergeData) 

762 

763 group_menu.AppendSeparator() 

764 

765 MenuItem(self, group_menu, "Freeze Selected Groups", 

766 "Freeze Selected Groups", self.onFreezeGroups) 

767 

768 MenuItem(self, group_menu, "UnFreeze Selected Groups", 

769 "UnFreeze Selected Groups", self.onUnFreezeGroups) 

770 

771 MenuItem(self, xasdata_menu, "Deglitch Data", "Deglitch Data", 

772 self.onDeglitchData) 

773 

774 MenuItem(self, xasdata_menu, "Calibrate Energy", 

775 "Calibrate Energy", 

776 self.onEnergyCalibrateData) 

777 

778 MenuItem(self, xasdata_menu, "Smooth Data", "Smooth Data", 

779 self.onSmoothData) 

780 

781 MenuItem(self, xasdata_menu, "Deconvolve Data", 

782 "Deconvolution of Data", self.onDeconvolveData) 

783 

784 MenuItem(self, xasdata_menu, "Rebin Data", "Rebin Data", 

785 self.onRebinData) 

786 

787 MenuItem(self, xasdata_menu, "Correct Over-absorption", 

788 "Correct Over-absorption", 

789 self.onCorrectOverAbsorptionData) 

790 

791 MenuItem(self, xasdata_menu, "Add and Subtract Spectra", 

792 "Calculations of Spectra", self.onSpectraCalc) 

793 

794 

795 self.menubar.Append(file_menu, "&File") 

796 self.menubar.Append(session_menu, "Sessions") 

797 self.menubar.Append(pref_menu, "Preferences") 

798 self.menubar.Append(group_menu, "Groups") 

799 self.menubar.Append(xasdata_menu, "XAS Data") 

800 

801 MenuItem(self, feff_menu, "Browse CIF Structures, Run Feff", 

802 "Browse CIF Structure, run Feff", self.onCIFBrowse) 

803 MenuItem(self, feff_menu, "Generate Feff input from general structures, Run Feff", 

804 "Generate Feff input from general structures, run Feff", self.onStructureBrowse) 

805 MenuItem(self, feff_menu, "Browse Feff Calculations", 

806 "Browse Feff Calculations, Get Feff Paths", self.onFeffBrowse) 

807 

808 self.menubar.Append(feff_menu, "Feff") 

809 

810 hmenu = wx.Menu() 

811 MenuItem(self, hmenu, 'About Larix', 'About Larix', 

812 self.onAbout) 

813 MenuItem(self, hmenu, 'Check for Updates', 'Check for Updates', 

814 self.onCheckforUpdates) 

815 

816 self.menubar.Append(hmenu, '&Help') 

817 self.SetMenuBar(self.menubar) 

818 self.Bind(wx.EVT_CLOSE, self.onClose) 

819 

820 def onwxInspect(self, evt=None): 

821 wx.GetApp().ShowInspectionTool() 

822 

823 def onShowLarchBuffer(self, evt=None): 

824 if self.larch_buffer is None: 

825 self.larch_buffer = LarchFrame(_larch=self.larch, is_standalone=False) 

826 self.larch_buffer.Show() 

827 self.larch_buffer.Raise() 

828 

829 def onSaveLarchHistory(self, evt=None): 

830 wildcard = 'Larch file (*.lar)|*.lar|All files (*.*)|*.*' 

831 path = FileSave(self, message='Save Session History as Larch Script', 

832 wildcard=wildcard, 

833 default_file='larix_history.lar') 

834 if path is not None: 

835 self.larch._larch.input.history.save(path, session_only=True) 

836 self.write_message("Wrote history %s" % path, 0) 

837 

838 def onExportCSV(self, evt=None): 

839 filenames = self.controller.filelist.GetCheckedStrings() 

840 if len(filenames) < 1: 

841 Popup(self, "No files selected to export to CSV", 

842 "No files selected") 

843 return 

844 

845 deffile = "%s_%i.csv" % (filenames[0], len(filenames)) 

846 

847 dlg = ExportCSVDialog(self, filenames) 

848 res = dlg.GetResponse() 

849 

850 dlg.Destroy() 

851 if not res.ok: 

852 return 

853 

854 deffile = f"{filenames[0]:s}_{len(filenames):d}.csv" 

855 wcards = 'CSV Files (*.csv)|*.csv|All files (*.*)|*.*' 

856 

857 outfile = FileSave(self, 'Save Groups to CSV File', 

858 default_file=deffile, wildcard=wcards) 

859 

860 if outfile is None: 

861 return 

862 if Path(outfile).exists() and uname != 'darwin': # darwin prompts in FileSave! 

863 if wx.ID_YES != Popup(self, 

864 "Overwrite existing CSV File?", 

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

866 return 

867 

868 savegroups = [self.controller.filename2group(res.master)] 

869 for fname in filenames: 

870 dgroup = self.controller.filename2group(fname) 

871 if dgroup not in savegroups: 

872 savegroups.append(dgroup) 

873 

874 try: 

875 groups2csv(savegroups, outfile, x=res.xarray, y=res.yarray, 

876 delim=res.delim, individual=res.individual) 

877 self.write_message(f"Exported CSV file {outfile:s}") 

878 except: 

879 title = "Could not export CSV File" 

880 message = [f"Could not export CSV File {outfile}"] 

881 ExceptionPopup(self, title, message) 

882 

883 # Athena 

884 def onExportAthenaProject(self, evt=None): 

885 groups = [] 

886 self.controller.sync_xasgroups() 

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

888 groups.append(self.controller.file_groups[str(checked)]) 

889 

890 if len(groups) < 1: 

891 Popup(self, "No files selected to export to Project", 

892 "No files selected") 

893 return 

894 prompt, prjfile = self.get_athena_project() 

895 self.save_athena_project(prjfile, groups) 

896 

897 def get_athena_project(self): 

898 prjfile = self.last_athena_file 

899 prompt = False 

900 if prjfile is None: 

901 tstamp = isotime(filename=True)[:15] 

902 prjfile = f"{tstamp:s}.prj" 

903 prompt = True 

904 return prompt, prjfile 

905 

906 def onSaveAthenaProject(self, evt=None): 

907 groups = self.controller.filelist.GetItems() 

908 if len(groups) < 1: 

909 Popup(self, "No files to export to Project", "No files to export") 

910 return 

911 

912 prompt, prjfile = self.get_athenaproject() 

913 self.save_athena_project(prjfile, groups, prompt=prompt, 

914 warn_overwrite=False) 

915 

916 def onSaveAsAthenaProject(self, evt=None): 

917 groups = self.controller.filelist.GetItems() 

918 if len(groups) < 1: 

919 Popup(self, "No files to export to Project", "No files to export") 

920 return 

921 

922 prompt, prjfile = self.get_athena_project() 

923 self.save_athena_project(prjfile, groups) 

924 

925 def save_athena_project(self, filename, grouplist, prompt=True, 

926 warn_overwrite=True): 

927 if len(grouplist) < 1: 

928 return 

929 savegroups = [self.controller.get_group(gname) for gname in grouplist] 

930 if prompt: 

931 filename = Path(filename).name 

932 wcards = 'Project Files (*.prj)|*.prj|All files (*.*)|*.*' 

933 filename = FileSave(self, 'Save Groups to Project File', 

934 default_file=filename, wildcard=wcards) 

935 if filename is None: 

936 return 

937 

938 if (Path(filename).exists() and warn_overwrite and 

939 uname != 'darwin'): # darwin prompts in FileSave! 

940 if wx.ID_YES != Popup(self, 

941 "Overwrite existing Project File?", 

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

943 return 

944 

945 aprj = AthenaProject(filename=filename) 

946 for label, grp in zip(grouplist, savegroups): 

947 aprj.add_group(grp) 

948 aprj.save(use_gzip=True) 

949 self.write_message("Saved project file %s" % (filename)) 

950 self.last_athena_file = filename 

951 

952 

953 def onPreferences(self, evt=None): 

954 self.show_subframe('preferences', PreferencesFrame, 

955 controller=self.controller) 

956 

957 def onPanelSelect(self, evt=None): 

958 self.show_subframe('panel_select', PanelSelectionFrame, 

959 controller=self.controller) 

960 

961 def onLoadSession(self, evt=None, path=None): 

962 if path is None: 

963 wildcard = 'Larch Session File (*.larix)|*.larix|All files (*.*)|*.*' 

964 path = FileOpen(self, message="Load Larch Session", 

965 wildcard=wildcard, default_file='larch.larix') 

966 if path is None: 

967 return 

968 

969 if is_athena_project(path): 

970 self.show_subframe('athena_import', AthenaImporter, 

971 controller=self.controller, filename=path, 

972 read_ok_cb=self.onReadAthenaProject_OK) 

973 return 

974 

975 try: 

976 _session = read_session(path) 

977 except: 

978 title = "Invalid Path for Larch Session" 

979 message = [f"{path} is not a valid Larch Session File"] 

980 ExceptionPopup(self, title, message) 

981 return 

982 

983 LoadSessionDialog(self, _session, path, self.controller).Show() 

984 self.last_session_read = path 

985 fpath = Path(path).absolute() 

986 fname = fpath.name 

987 fdir = fpath.parent.as_posix() 

988 if self.controller.chdir_on_fileopen() and len(fdir) > 0: 

989 os.chdir(fdir) 

990 self.controller.set_workdir() 

991 

992 def onSaveSessionAs(self, evt=None): 

993 groups = self.controller.filelist.GetItems() 

994 if len(groups) < 1: 

995 return 

996 self.last_session_file = None 

997 self.onSaveSession() 

998 

999 

1000 def onSaveSession(self, evt=None): 

1001 groups = self.controller.filelist.GetItems() 

1002 if len(groups) < 1: 

1003 return 

1004 

1005 fname = self.last_session_file 

1006 if fname is None: 

1007 fname = self.last_session_read 

1008 if fname is None: 

1009 fname = time.strftime('%Y%b%d_%H%M') + '.larix' 

1010 

1011 fname = Path(fname).name 

1012 wcards = 'Larch Project Files (*.larix)|*.larix|All files (*.*)|*.*' 

1013 fname = FileSave(self, 'Save Larch Session File', 

1014 default_file=fname, wildcard=wcards) 

1015 if fname is None: 

1016 return 

1017 

1018 if Path(fname).exists() and uname != 'darwin': # darwin prompts in FileSave! 

1019 if wx.ID_YES != Popup(self, "Overwrite existing Project File?", 

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

1021 return 

1022 

1023 save_session(fname=fname, _larch=self.larch._larch) 

1024 stime = time.strftime("%H:%M") 

1025 self.last_save_message = ("Session last saved", f"'{fname}'", f"{stime}") 

1026 self.write_message(f"Saved session to '{fname}' at {stime}") 

1027 self.last_session_file = self.last_session_read = fname 

1028 

1029 def onClearSession(self, evt=None): 

1030 conf = self.controller.get_config('autosave', 

1031 {'fileroot': 'autosave'}) 

1032 afile = Path(self.controller.larix_folder, 

1033 conf['fileroot']+'.larix').as_posix() 

1034 

1035 msg = f"""Session will be saved to 

1036 '{afile:s}' 

1037before clearing""" 

1038 

1039 dlg = wx.Dialog(None, -1, title="Clear all Session data?", size=(550, 300)) 

1040 dlg.SetFont(Font(FONTSIZE)) 

1041 panel = GridPanel(dlg, ncols=3, nrows=4, pad=2, itemstyle=LEFT) 

1042 

1043 panel.Add(wx.StaticText(panel, label="Clear all Session Data?"), dcol=2) 

1044 panel.Add(wx.StaticText(panel, label=msg), dcol=4, newrow=True) 

1045 

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

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

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

1049 panel.pack() 

1050 

1051 fit_dialog_window(dlg, panel) 

1052 

1053 

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

1055 self.autosave_session() 

1056 self.controller.clear_session() 

1057 dlg.Destroy() 

1058 

1059 

1060 def onConfigDataProcessing(self, event=None): 

1061 pass 

1062 

1063 

1064 def onCopyGroup(self, event=None, journal=None): 

1065 fname = self.current_filename 

1066 if fname is None: 

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

1068 ogroup = self.controller.get_group(fname) 

1069 ngroup = self.controller.copy_group(fname) 

1070 self.install_group(ngroup, journal=ogroup.journal) 

1071 

1072 def onGroupJournal(self, event=None): 

1073 dgroup = self.controller.get_group() 

1074 if dgroup is not None: 

1075 self.show_subframe('group_journal', GroupJournalFrame) 

1076 self.subframes['group_journal'].set_group(dgroup) 

1077 

1078 

1079 def onRenameGroup(self, event=None): 

1080 fname = self.current_filename = self.controller.filelist.GetStringSelection() 

1081 if fname is None: 

1082 return 

1083 dlg = RenameDialog(self, fname) 

1084 res = dlg.GetResponse() 

1085 dlg.Destroy() 

1086 

1087 if res.ok: 

1088 selected = [] 

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

1090 selected.append(str(checked)) 

1091 if self.current_filename in selected: 

1092 selected.remove(self.current_filename) 

1093 selected.append(res.newname) 

1094 

1095 groupname = self.controller.file_groups.pop(fname) 

1096 self.controller.sync_xasgroups() 

1097 self.controller.file_groups[res.newname] = groupname 

1098 self.controller.filelist.rename_item(self.current_filename, res.newname) 

1099 dgroup = self.controller.get_group(groupname) 

1100 dgroup.filename = self.current_filename = res.newname 

1101 

1102 self.controller.filelist.SetCheckedStrings(selected) 

1103 self.controller.filelist.SetStringSelection(res.newname) 

1104 

1105 def onRemoveGroup(self, event=None): 

1106 n = int(self.controller.filelist.GetSelection()) 

1107 all_names = self.controller.filelist.GetItems() 

1108 fname = all_names[n] 

1109 

1110 do_remove = (wx.ID_YES == Popup(self, 

1111 f"Remove Group '{fname}'?", 

1112 'Remove Group? Cannot be undone!', 

1113 style=wx.YES_NO)) 

1114 if do_remove: 

1115 fname = all_names.pop(n) 

1116 self.controller.filelist.refresh(all_names) 

1117 self.RemoveFile(fname) 

1118 

1119 

1120 def onRemoveGroups(self, event=None): 

1121 groups = [] 

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

1123 groups.append(str(checked)) 

1124 if len(groups) < 1: 

1125 return 

1126 

1127 dlg = RemoveDialog(self, groups) 

1128 res = dlg.GetResponse() 

1129 dlg.Destroy() 

1130 

1131 if res.ok: 

1132 filelist = self.controller.filelist 

1133 all_fnames = filelist.GetItems() 

1134 for fname in groups: 

1135 gname = self.controller.file_groups.pop(fname) 

1136 delattr(self.controller.symtable, gname) 

1137 all_fnames.remove(fname) 

1138 

1139 filelist.Clear() 

1140 for name in all_fnames: 

1141 filelist.Append(name) 

1142 self.controller.sync_xasgroups() 

1143 

1144 def onFreezeGroups(self, event=None): 

1145 self._freeze_handler(True) 

1146 

1147 def onUnFreezeGroups(self, event=None): 

1148 self._freeze_handler(False) 

1149 

1150 def _freeze_handler(self, freeze): 

1151 current_filename = self.current_filename 

1152 reproc_group = None 

1153 for fname in self.controller.filelist.GetCheckedStrings(): 

1154 groupname = self.controller.file_groups[str(fname)] 

1155 dgroup = self.controller.get_group(groupname) 

1156 if fname == current_filename: 

1157 reproc_group = groupname 

1158 dgroup.is_frozen = freeze 

1159 if reproc_group is not None: 

1160 self.ShowFile(groupname=reproc_group, process=True) 

1161 

1162 def onMergeData(self, event=None): 

1163 groups = {} 

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

1165 cname = str(checked) 

1166 groups[cname] = self.controller.file_groups[cname] 

1167 if len(groups) < 1: 

1168 return 

1169 

1170 outgroup = common_startstring(list(groups.keys())) 

1171 if len(outgroup) < 2: outgroup = "data" 

1172 outgroup = "%s (merge %d)" % (outgroup, len(groups)) 

1173 outgroup = unique_name(outgroup, self.controller.file_groups) 

1174 dlg = MergeDialog(self, list(groups.keys()), outgroup=outgroup) 

1175 res = dlg.GetResponse() 

1176 dlg.Destroy() 

1177 if res.ok: 

1178 fname = res.group 

1179 gname = fix_varname(res.group.lower()) 

1180 master = self.controller.file_groups[res.master] 

1181 yname = 'norm' if res.ynorm else 'mu' 

1182 this = self.controller.merge_groups(list(groups.values()), 

1183 master=master, 

1184 yarray=yname, 

1185 outgroup=gname) 

1186 

1187 mfiles, mgroups = [], [] 

1188 for g in groups.values(): 

1189 mgroups.append(g) 

1190 mfiles.append(self.controller.get_group(g).filename) 

1191 mfiles = '[%s]' % (', '.join(mfiles)) 

1192 mgroups = '[%s]' % (', '.join(mgroups)) 

1193 desc = "%s: merge of %d groups" % (fname, len(groups)) 

1194 self.install_group(gname, fname, source=desc, 

1195 journal={'source_desc': desc, 

1196 'merged_groups': mgroups, 

1197 'merged_filenames': mfiles}) 

1198 

1199 def has_datagroup(self): 

1200 return hasattr(self.controller.get_group(), 'energy') 

1201 

1202 def onDeglitchData(self, event=None): 

1203 if self.has_datagroup(): 

1204 DeglitchDialog(self, self.controller).Show() 

1205 

1206 def onSmoothData(self, event=None): 

1207 if self.has_datagroup(): 

1208 SmoothDataDialog(self, self.controller).Show() 

1209 

1210 def onRebinData(self, event=None): 

1211 if self.has_datagroup(): 

1212 RebinDataDialog(self, self.controller).Show() 

1213 

1214 def onCorrectOverAbsorptionData(self, event=None): 

1215 if self.has_datagroup(): 

1216 OverAbsorptionDialog(self, self.controller).Show() 

1217 

1218 def onSpectraCalc(self, event=None): 

1219 

1220 if self.has_datagroup(): 

1221 SpectraCalcDialog(self, self.controller).Show() 

1222 

1223 def onEnergyCalibrateData(self, event=None): 

1224 if self.has_datagroup(): 

1225 EnergyCalibrateDialog(self, self.controller).Show() 

1226 

1227 def onDeconvolveData(self, event=None): 

1228 if self.has_datagroup(): 

1229 DeconvolutionDialog(self, self.controller).Show() 

1230 

1231 def onConfigDataFitting(self, event=None): 

1232 pass 

1233 

1234 def onAbout(self, event=None): 

1235 info = AboutDialogInfo() 

1236 info.SetName('Larix') 

1237 info.SetDescription('X-ray Absorption Visualization and Analysis') 

1238 info.SetVersion('Larch %s ' % larch.version.__version__) 

1239 info.AddDeveloper('Matthew Newville: newville at cars.uchicago.edu') 

1240 dlg = AboutBox(info) 

1241 

1242 def onCheckforUpdates(self, event=None): 

1243 dlg = LarchUpdaterDialog(self, caller='Larix') 

1244 dlg.Raise() 

1245 dlg.SetWindowStyle(wx.STAY_ON_TOP) 

1246 res = dlg.GetResponse() 

1247 dlg.Destroy() 

1248 if res.ok and res.run_updates: 

1249 from larch.apps import update_larch 

1250 update_larch() 

1251 self.onClose(event=event, prompt=False) 

1252 

1253 def onClose(self, event=None, prompt=True): 

1254 if prompt: 

1255 dlg = QuitDialog(self, self.last_save_message) 

1256 dlg.Raise() 

1257 dlg.SetWindowStyle(wx.STAY_ON_TOP) 

1258 res = dlg.GetResponse() 

1259 dlg.Destroy() 

1260 if not res.ok: 

1261 return 

1262 

1263 self.controller.save_workdir() 

1264 try: 

1265 self.controller.close_all_displays() 

1266 except Exception: 

1267 pass 

1268 

1269 if self.larch_buffer is not None: 

1270 try: 

1271 self.larch_buffer.Destroy() 

1272 except Exception: 

1273 pass 

1274 

1275 def destroy(wid): 

1276 if hasattr(wid, 'Destroy'): 

1277 try: 

1278 wid.Destroy() 

1279 except Exception: 

1280 pass 

1281 time.sleep(0.01) 

1282 

1283 for name, wid in self.subframes.items(): 

1284 destroy(wid) 

1285 

1286 for i in range(self.nb.GetPageCount()): 

1287 nbpage = self.nb.GetPage(i) 

1288 timers = getattr(nbpage, 'timers', None) 

1289 if timers is not None: 

1290 for t in timers.values(): 

1291 t.Stop() 

1292 

1293 if hasattr(nbpage, 'subframes'): 

1294 for name, wid in nbpage.subframes.items(): 

1295 destroy(wid) 

1296 for t in self.timers.values(): 

1297 t.Stop() 

1298 

1299 time.sleep(0.05) 

1300 self.Destroy() 

1301 

1302 def show_subframe(self, name, frameclass, **opts): 

1303 shown = False 

1304 if name in self.subframes: 

1305 try: 

1306 self.subframes[name].Raise() 

1307 shown = True 

1308 except: 

1309 del self.subframes[name] 

1310 if not shown: 

1311 self.subframes[name] = frameclass(self, **opts) 

1312 

1313 

1314 def onCIFBrowse(self, event=None): 

1315 self.show_subframe('cif_feff', CIFFrame, _larch=self.larch, 

1316 path_importer=self.get_nbpage('feffit')[1].add_path, 

1317 with_feff=True) 

1318 

1319 def onStructureBrowse(self, event=None): 

1320 self.show_subframe('structure_feff', Structure2FeffFrame, _larch=self.larch, 

1321 path_importer=self.get_nbpage('feffit')[1].add_path) 

1322 

1323 def onFeffBrowse(self, event=None): 

1324 self.show_subframe('feff_paths', FeffResultsFrame, _larch=self.larch, 

1325 path_importer=self.get_nbpage('feffit')[1].add_path) 

1326 

1327 def onLoadFitResult(self, event=None): 

1328 pass 

1329 # print("onLoadFitResult??") 

1330 # self.nb.SetSelection(1) 

1331 # self.nb_panels[1].onLoadFitResult(event=event) 

1332 

1333 def onReadDialog(self, event=None): 

1334 dlg = wx.FileDialog(self, message="Read Data File", 

1335 defaultDir=get_cwd(), 

1336 wildcard=FILE_WILDCARDS, 

1337 style=wx.FD_OPEN|wx.FD_MULTIPLE) 

1338 self.paths2read = [] 

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

1340 self.paths2read = dlg.GetPaths() 

1341 dlg.Destroy() 

1342 

1343 if len(self.paths2read) < 1: 

1344 return 

1345 

1346 def file_mtime(x): 

1347 return os.stat(x).st_mtime 

1348 

1349 self.paths2read = [Path(p).as_posix() for p in self.paths2read] 

1350 self.paths2read = sorted(self.paths2read, key=file_mtime) 

1351 

1352 path = self.paths2read.pop(0) 

1353 

1354 do_read = True 

1355 if path in self.controller.file_groups: 

1356 do_read = (wx.ID_YES == Popup(self, 

1357 "Re-read file '%s'?" % path, 

1358 'Re-read file?')) 

1359 if do_read: 

1360 self.onRead(path) 

1361 

1362 def onRead(self, path): 

1363 fpath = Path(path).absolute() 

1364 filedir = fpath.parent.as_posix() 

1365 filename = fpath.name 

1366 fullpath = fpath.as_posix() 

1367 if self.controller.chdir_on_fileopen() and len(filedir) > 0: 

1368 os.chdir(filedir) 

1369 self.controller.set_workdir() 

1370 

1371 # check for athena projects 

1372 if is_athena_project(fullpath): 

1373 self.show_subframe('athena_import', AthenaImporter, 

1374 controller=self.controller, filename=fullpath, 

1375 read_ok_cb=self.onReadAthenaProject_OK) 

1376 return 

1377 

1378 # check for Spec File 

1379 if is_specfile(fullpath): 

1380 self.show_subframe('spec_import', SpecfileImporter, 

1381 filename=fullpath, 

1382 _larch=self.larch_buffer.larchshell, 

1383 config=self.last_spec_config, 

1384 read_ok_cb=self.onReadSpecfile_OK) 

1385 return 

1386 

1387 # check for Larch Session File 

1388 if is_larch_session_file(fullpath): 

1389 self.onLoadSession(path=fullpath) 

1390 return 

1391 

1392 

1393 # default to Column File 

1394 self.show_subframe('readfile', ColumnDataFileFrame, filename=fullpath, 

1395 config=self.last_col_config, 

1396 _larch=self.larch_buffer.larchshell, 

1397 read_ok_cb=self.onRead_OK) 

1398 

1399 def onReadSpecfile_OK(self, script, path, scanlist, config=None): 

1400 """read groups from a list of scans from a specfile""" 

1401 self.larch.eval("_specfile = specfile('{path:s}')".format(path=path)) 

1402 dgroup = None 

1403 fname = Path(path).name 

1404 first_group = None 

1405 cur_panel = self.nb.GetCurrentPage() 

1406 cur_panel.skip_plotting = True 

1407 symtable = self.larch.symtable 

1408 if config is not None: 

1409 self.last_spec_config = config 

1410 

1411 array_desc = config.get('array_desc', {}) 

1412 

1413 multiconfig = config.get('multicol_config', {'channels':[], 'i0': config['iy2']}) 

1414 multi_i0 = multiconfig.get('i0', config['iy2']) 

1415 multi_chans = copy.copy(multiconfig.get('channels', [])) 

1416 

1417 if len(multi_chans) > 0: 

1418 if (multi_chans[0] == config['iy1'] and multi_i0 == config['iy2'] 

1419 and 'log' not in config['expressions']['yplot']): 

1420 yname = config['array_labels'][config['iy1']] 

1421 # filename = f"{spath}:{yname}" 

1422 multi_chans.pop(0) 

1423 

1424 for scan in scanlist: 

1425 gname = fix_varname("{:s}{:s}".format(fname[:6], scan)) 

1426 if hasattr(symtable, gname): 

1427 count, tname = 0, gname 

1428 while count < 1e7 and self.larch.symtable.has_group(tname): 

1429 tname = gname + make_hashkey(length=7) 

1430 count += 1 

1431 gname = tname 

1432 

1433 cur_panel.skip_plotting = (scan == scanlist[-1]) 

1434 yname = config['yarr1'] 

1435 if first_group is None: 

1436 first_group = gname 

1437 cmd = script.format(group=gname, specfile='_specfile', 

1438 path=path, scan=scan, **config) 

1439 

1440 self.larch.eval(cmd) 

1441 displayname = f"{fname} scan{scan} {yname}" 

1442 jrnl = {'source_desc': f"{fname}: scan{scan} {yname}"} 

1443 dgroup = self.install_group(gname, displayname, journal=jrnl) 

1444 if len(multi_chans) > 0: 

1445 yplotline = None 

1446 for line in script.split('\n'): 

1447 if line.startswith("{group}.yplot ="): 

1448 yplotline = line.replace("{group}", "{ngroup}") 

1449 mscript = '\n'.join(["{ngroup} = deepcopy({group})", 

1450 yplotline, 

1451 "{ngroup}.mu = {ngroup}.yplot", 

1452 "{ngroup}.plot_ylabel = '{ylabel}'"]) 

1453 i0 = '1.0' 

1454 if multi_i0 < len(config['array_labels']): 

1455 i0 = config['array_labels'][multi_i0] 

1456 

1457 for mchan in multi_chans: 

1458 yname = config['array_labels'][mchan] 

1459 ylabel = f"{yname}/{i0}" 

1460 dname = f"{fname} scan{scan} {yname}" 

1461 ngroup = file2groupname(dname, symtable=self.larch.symtable) 

1462 njournal = {'source': path, 

1463 'xplot': array_desc['xplot'].format(group=ngroup), 

1464 'yplot': ylabel, 

1465 'source_desc': f"{fname}: scan{scan} {yname}", 

1466 'yerr': array_desc['yerr'].format(group=ngroup)} 

1467 cmd = mscript.format(group=gname, ngroup=ngroup, 

1468 iy1=mchan, iy2=multi_i0, ylabel=ylabel) 

1469 self.larch.eval(cmd) 

1470 self.install_group(ngroup, dname, source=path, journal=njournal) 

1471 

1472 

1473 cur_panel.skip_plotting = False 

1474 

1475 if first_group is not None: 

1476 self.ShowFile(groupname=first_group, process=True, plot=True) 

1477 self.write_message("read %d datasets from %s" % (len(scanlist), path)) 

1478 self.larch.eval('del _specfile') 

1479 

1480 def onReadXasDataSource_OK(self, script, path, scanlist, array_sel=None, extra_sums=None): 

1481 """read groups from a list of scans from a xas data source""" 

1482 self.larch.eval("_data_source = open_xas_source('{path:s}')".format(path=path)) 

1483 dgroup = None 

1484 fname = Path(path).name 

1485 first_group = None 

1486 cur_panel = self.nb.GetCurrentPage() 

1487 cur_panel.skip_plotting = True 

1488 symtable = self.larch.symtable 

1489 if array_sel is not None: 

1490 self.last_array_sel_spec = array_sel 

1491 

1492 for scan in scanlist: 

1493 gname = fix_varname("{:s}{:s}".format(fname[:6], scan)) 

1494 if hasattr(symtable, gname): 

1495 count, tname = 0, gname 

1496 while count < 1e7 and self.larch.symtable.has_group(tname): 

1497 tname = gname + make_hashkey(length=7) 

1498 count += 1 

1499 gname = tname 

1500 

1501 cur_panel.skip_plotting = (scan == scanlist[-1]) 

1502 yname = self.last_array_sel_spec['yarr1'] 

1503 if first_group is None: 

1504 first_group = gname 

1505 self.larch.eval(script.format(group=gname, data_source='_data_source', 

1506 path=path, scan=scan)) 

1507 

1508 displayname = f"{fname:s} scan{scan:s} {yname:s}" 

1509 jrnl = {'source_desc': f"{fname:s}: scan{scan:s} {yname:s}"} 

1510 dgroup = self.install_group(gname, displayname, 

1511 process=True, plot=False, extra_sums=extra_sums, 

1512 source=displayname, 

1513 journal=jrnl) 

1514 cur_panel.skip_plotting = False 

1515 

1516 if first_group is not None: 

1517 self.ShowFile(groupname=first_group, process=True, plot=True) 

1518 self.write_message("read %d datasets from %s" % (len(scanlist), path)) 

1519 self.larch.eval('del _data_source') 

1520 

1521 def onReadAthenaProject_OK(self, path, namelist): 

1522 """read groups from a list of groups from an athena project file""" 

1523 self.larch.eval("_prj = read_athena('{path:s}', do_fft=False, do_bkg=False)".format(path=path)) 

1524 dgroup = None 

1525 script = "{group:s} = _prj.{prjgroup:s}" 

1526 cur_panel = self.nb.GetCurrentPage() 

1527 cur_panel.skip_plotting = True 

1528 parent, spath = path_split(path) 

1529 labels = [] 

1530 groups_added = [] 

1531 

1532 for ig, gname in enumerate(namelist): 

1533 cur_panel.skip_plotting = (gname == namelist[-1]) 

1534 this = getattr(self.larch.symtable._prj, gname) 

1535 gid = file2groupname(str(getattr(this, 'athena_id', gname)), 

1536 symtable=self.larch.symtable) 

1537 if self.larch.symtable.has_group(gid): 

1538 count, prefix = 0, gname[:3] 

1539 while count < 1e7 and self.larch.symtable.has_group(gid): 

1540 gid = prefix + make_hashkey(length=7) 

1541 count += 1 

1542 label = getattr(this, 'label', gname).strip() 

1543 labels.append(label) 

1544 

1545 jrnl = {'source_desc': f'{spath:s}: {gname:s}'} 

1546 self.larch.eval(script.format(group=gid, prjgroup=gname)) 

1547 

1548 dgroup = self.install_group(gid, label, process=False, 

1549 source=path, journal=jrnl) 

1550 groups_added.append(gid) 

1551 

1552 for gid in groups_added: 

1553 rgroup = gid 

1554 dgroup = self.larch.symtable.get_group(gid) 

1555 

1556 conf_xasnorm = dgroup.config.xasnorm 

1557 conf_exafs= dgroup.config.exafs 

1558 

1559 apars = getattr(dgroup, 'athena_params', {}) 

1560 abkg = getattr(apars, 'bkg', {}) 

1561 afft = getattr(apars, 'fft', {}) 

1562 

1563 # norm 

1564 for attr in ('e0', 'pre1', 'pre2', 'nnorm'): 

1565 if hasattr(abkg, attr): 

1566 conf_xasnorm[attr] = float(getattr(abkg, attr)) 

1567 

1568 for attr, alt in (('norm1', 'nor1'), ('norm2', 'nor2'), 

1569 ('edge_step', 'step')): 

1570 if hasattr(abkg, alt): 

1571 conf_xasnorm[attr] = float(getattr(abkg, alt)) 

1572 if hasattr(abkg, 'fixstep'): 

1573 a = float(getattr(abkg, 'fixstep', 0.0)) 

1574 conf_xasnorm['auto_step'] = (a < 0.5) 

1575 

1576 

1577 # bkg 

1578 for attr in ('e0', 'rbkg'): 

1579 if hasattr(abkg, attr): 

1580 conf_exafs[attr] = float(getattr(abkg, attr)) 

1581 

1582 for attr, alt in (('bkg_kmin', 'spl1'), ('bkg_kmax', 'spl2'), 

1583 ('bkg_kweight', 'kw'), ('bkg_clamplo', 'clamp1'), 

1584 ('bkg_clamphi', 'clamp2')): 

1585 if hasattr(abkg, alt): 

1586 val = getattr(abkg, alt) 

1587 try: 

1588 val = float(getattr(abkg, alt)) 

1589 except: 

1590 if alt.startswith('clamp') and isinstance(val, str): 

1591 val = ATHENA_CLAMPNAMES.get(val.lower(), 0) 

1592 conf_exafs[attr] = val 

1593 

1594 

1595 # fft 

1596 for attr in ('kmin', 'kmax', 'dk', 'kwindow', 'kw'): 

1597 if hasattr(afft, attr): 

1598 n = f'fft_{attr}' 

1599 if attr == 'kw': n = 'fft_kweight' 

1600 if attr == 'kwindow': 

1601 conf_exafs[n] = getattr(afft, attr) 

1602 else: 

1603 conf_exafs[n] = float(getattr(afft, attr)) 

1604 

1605 # reference 

1606 refgroup = getattr(apars, 'reference', '') 

1607 if refgroup in groups_added: 

1608 newname = None 

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

1610 if refgroup in (key, val): 

1611 newname = key 

1612 

1613 if newname is not None: 

1614 refgroup = newname 

1615 else: 

1616 refgroup = dgroup.filename 

1617 dgroup.energy_ref = refgroup 

1618 

1619 self.larch.eval("del _prj") 

1620 cur_panel.skip_plotting = False 

1621 

1622 plot_first = True 

1623 if len(labels) > 0: 

1624 gname = self.controller.file_groups[labels[0]] 

1625 self.ShowFile(groupname=gname, process=True, plot=plot_first) 

1626 plot_first = False 

1627 self.write_message("read %d datasets from %s" % (len(namelist), path)) 

1628 self.last_athena_file = path 

1629 self.controller.sync_xasgroups() 

1630 self.controller.recentfiles.append((time.time(), path)) 

1631 

1632 def onRead_OK(self, script, path, config): 

1633 #groupname=None, filename=None, 

1634 # ref_groupname=None, ref_filename=None, config=None, 

1635 # array_desc=None): 

1636 

1637 """ called when column data has been selected and is ready to be used 

1638 overwrite: whether to overwrite the current datagroup, as when 

1639 editing a datagroup 

1640 """ 

1641 filedir, spath = path_split(path) 

1642 filename = config.get('filename', spath) 

1643 groupname = config.get('groupname', None) 

1644 if groupname is None: 

1645 groupname = file2groupname(filename, 

1646 symtable=self.larch.symtable) 

1647 array_desc = config.get('array_desc', {}) 

1648 if 'xplot' not in array_desc and 'xdat' in array_desc: # back compat 

1649 array_desc['xplot'] = copy.copy(array_desc['xdat']) 

1650 if 'yplot' not in array_desc and 'ydat' in array_desc: # back compat 

1651 array_desc['yplot'] = copy.copy(array_desc['ydat']) 

1652 

1653 if hasattr(self.larch.symtable, groupname): 

1654 groupname = file2groupname(filename, 

1655 symtable=self.larch.symtable) 

1656 

1657 refgroup = config.get('refgroup', groupname + '_ref') 

1658 

1659 multiconfig = config.get('multicol_config', {'channels':[], 'i0': config['iy2']}) 

1660 multi_i0 = multiconfig.get('i0', config['iy2']) 

1661 multi_chans = copy.copy(multiconfig.get('channels', [])) 

1662 

1663 if len(multi_chans) > 0: 

1664 if (multi_chans[0] == config['iy1'] and multi_i0 == config['iy2'] 

1665 and 'log' not in config['expressions']['yplot']): 

1666 yname = config['array_labels'][config['iy1']] 

1667 filename = f"{spath}:{yname}" 

1668 multi_chans.pop(0) 

1669 

1670 config = copy.copy(config) 

1671 config['group'] = groupname 

1672 config['path'] = path 

1673 has_yref = config.get('has_yref', False) 

1674 

1675 

1676 self.larch.eval(script.format(**config)) 

1677 

1678 if config is not None: 

1679 self.last_col_config = config 

1680 

1681 journal = {'source': path} 

1682 refjournal = {} 

1683 

1684 if 'xplot' in array_desc: 

1685 journal['xplot'] = array_desc['xplot'].format(group=groupname) 

1686 if 'yplot' in array_desc: 

1687 journal['yplot'] = ylab = array_desc['yplot'].format(group=groupname) 

1688 journal['source_desc'] = f'{spath}: {ylab}' 

1689 if 'yerr' in array_desc: 

1690 journal['yerr'] = array_desc['yerr'].format(group=groupname) 

1691 

1692 self.install_group(groupname, filename, source=path, journal=journal) 

1693 

1694 dtype = getattr(config, 'datatype', 'xydata') 

1695 def install_multichans(config): 

1696 yplotline = None 

1697 yarray = 'mu' if dtype == 'xas' else 'y' 

1698 for line in script.split('\n'): 

1699 if line.startswith("{group}.yplot ="): 

1700 yplotline = line.replace("{group}", "{ngroup}") 

1701 mscript = ["{ngroup} = deepcopy({group})", 

1702 yplotline, 

1703 "{ngroup}.{yarray} = {ngroup}.yplot[:]", 

1704 "{ngroup}.plot_ylabel = '{ylabel}'" ] 

1705 if dtype == 'xydata': 

1706 mscript.append("{ngroup}.scale = ptp({ngroup}.y+1.e-15)") 

1707 

1708 i0 = '1.0' 

1709 if multi_i0 < len(config['array_labels']): 

1710 i0 = config['array_labels'][multi_i0] 

1711 

1712 for mchan in multi_chans: 

1713 yname = config['array_labels'][mchan] 

1714 ylabel = f"{yname}/{i0}" 

1715 fname = f"{spath}:{yname}" 

1716 ngroup = file2groupname(fname, symtable=self.larch.symtable) 

1717 njournal = {'source': path, 

1718 'xplot': array_desc['xplot'].format(group=ngroup), 

1719 'yplot': ylabel, 

1720 'source_desc': f"{spath}: {ylabel}", 

1721 'yerr': array_desc['yerr'].format(group=ngroup)} 

1722 cmd = '\n'.join(mscript).format(group=config['group'], 

1723 ngroup=ngroup, ylabel=ylabel, 

1724 iy1=mchan, iy2=multi_i0, 

1725 yarray=yarray) 

1726 self.larch.eval(cmd) 

1727 self.install_group(ngroup, fname, source=path, journal=njournal) 

1728 

1729 if len(multi_chans) > 0: 

1730 install_multichans(config) 

1731 

1732 if has_yref: 

1733 

1734 if 'xplot' in array_desc: 

1735 refjournal['xplot'] = array_desc['xplot'].format(group=refgroup) 

1736 if 'yref' in array_desc: 

1737 refjournal['yplot'] = ydx = array_desc['yref'].format(group=refgroup) 

1738 refjournal['source_desc'] = f'{spath:s}: {ydx:s}' 

1739 self.install_group(refgroup, config['reffile'], 

1740 source=path, journal=refjournal) 

1741 

1742 # check if rebin is needed 

1743 thisgroup = getattr(self.larch.symtable, groupname) 

1744 

1745 do_rebin = False 

1746 if thisgroup.datatype == 'xas': 

1747 try: 

1748 en = thisgroup.energy 

1749 except: 

1750 do_rebin = True 

1751 en = thisgroup.energy = thisgroup.xplot 

1752 # test for rebinning: 

1753 # too many data points 

1754 # unsorted energy data or data in angle 

1755 # too fine a step size at the end of the data range 

1756 if (len(en) > 1200 or 

1757 any(np.diff(en) < 0) or 

1758 ((max(en)-min(en)) > 300 and 

1759 (np.diff(en[-50:]).mean() < 0.75))): 

1760 msg = """This dataset may need to be rebinned. 

1761 Rebin now?""" 

1762 dlg = wx.MessageDialog(self, msg, 'Warning', 

1763 wx.YES | wx.NO ) 

1764 do_rebin = (wx.ID_YES == dlg.ShowModal()) 

1765 dlg.Destroy() 

1766 gname = None 

1767 

1768 for path in self.paths2read: 

1769 filedir, spath = path_split(path) 

1770 fname = spath 

1771 if len(multi_chans) > 0: 

1772 yname = config['array_labels'][config['iy1']] 

1773 fname = f"{spath}:{yname}" 

1774 

1775 gname = file2groupname(fname, symtable=self.larch.symtable) 

1776 refgroup = config['refgroup'] 

1777 if has_yref: 

1778 refgroup = gname + '_ref' 

1779 reffile = spath + '_ref' 

1780 config = copy.copy(config) 

1781 config['group'] = gname 

1782 config['refgroup'] = refgroup 

1783 config['path'] = path 

1784 

1785 self.larch.eval(script.format(**config)) 

1786 if has_yref: 

1787 self.larch.eval(f"{gname}.energy_ref = {refgroup}.energy_ref = '{refgroup}'\n") 

1788 

1789 if 'xplot' in array_desc: 

1790 journal['xplot'] = array_desc['xplot'].format(group=gname) 

1791 if 'yplot' in array_desc: 

1792 journal['yplot'] = ydx = array_desc['yplot'].format(group=gname) 

1793 journal['source_desc'] = f'{spath:s}: {ydx:s}' 

1794 if 'yerr' in array_desc: 

1795 journal['yerr'] = array_desc['yerr'].format(group=gname) 

1796 

1797 self.install_group(gname, fname, source=path, journal=journal, plot=False) 

1798 if len(multi_chans) > 0: 

1799 install_multichans(config) 

1800 

1801 if has_yref: 

1802 if 'xplot' in array_desc: 

1803 refjournal['xplot'] = array_desc['xplot'].format(group=refgroup) 

1804 if 'yref' in array_desc: 

1805 refjournal['yplot'] = ydx = array_desc['yref'].format(group=refgroup) 

1806 refjournal['source_desc'] = f'{spath:s}: {ydx:s}' 

1807 

1808 self.install_group(refgroup, reffile, source=path, journal=refjournal, plot=False) 

1809 

1810 

1811 if gname is not None: 

1812 self.ShowFile(groupname=gname) 

1813 

1814 self.write_message("read %s" % (spath)) 

1815 if do_rebin: 

1816 RebinDataDialog(self, self.controller).Show() 

1817 

1818 def install_group(self, groupname, filename=None, source=None, journal=None, 

1819 process=True, plot=True): 

1820 """add groupname / filename to list of available data groups""" 

1821 if isinstance(groupname, Group): 

1822 dgroup = groupname 

1823 groupname = groupname.groupname 

1824 else: 

1825 dgroup = self.controller.get_group(groupname) 

1826 

1827 if filename is None: 

1828 filename = dgroup.filename 

1829 

1830 self.controller.install_group(groupname, filename, 

1831 source=source, journal=journal) 

1832 dtype = getattr(dgroup, 'datatype', 'xydata') 

1833 startpage = 'xasnorm' if dtype == 'xas' else 'xydata' 

1834 ipage, pagepanel = self.get_nbpage(startpage) 

1835 self.nb.SetSelection(ipage) 

1836 self.ShowFile(groupname=groupname, filename=filename, 

1837 process=process, plot=plot) 

1838 

1839 ## 

1840 def get_recent_session_menu(self): 

1841 """ get recent sessions files for Menu list""" 

1842 for menu_item in self.recent_menu.MenuItems: 

1843 self.recent_menu.Remove(menu_item) 

1844 

1845 for tstamp, fname in self.controller.get_recentfiles(): 

1846 message = f"{fname} [{time_ago(tstamp)} ago]" 

1847 MenuItem(self, self.recent_menu, message, 

1848 f"file saved {isotime(tstamp)}", 

1849 partial(self.onLoadSession, path=fname)) 

1850 

1851 self.recent_menu.AppendSeparator() 

1852 for tstamp, fname in self.controller.recent_autosave_sessions(): 

1853 message = f"{fname} [{time_ago(tstamp)} ago]" 

1854 if abs(tstamp) < 5.0: 

1855 message = f"{fname} [most recent]" 

1856 MenuItem(self, self.recent_menu, message, 

1857 f"file saved {isotime(tstamp)}", 

1858 partial(self.onLoadSession, path=fname)) 

1859 

1860 def onAutoSaveTimer(self, event=None): 

1861 """autosave session periodically, using autosave_config settings 

1862 and avoiding saving sessions while program is inactive. 

1863 """ 

1864 conf = self.controller.get_config('autosave', {}) 

1865 savetime = conf.get('savetime', 600) 

1866 symtab = self.larch.symtable 

1867 

1868 if (time.time() > self.last_autosave + savetime and 

1869 symtab._sys.last_eval_time > (self.last_autosave+60) and 

1870 len(symtab._xasgroups) > 0): 

1871 self.autosave_session() 

1872 self.get_recent_session_menu() 

1873 

1874 def autosave_session(self, event=None): 

1875 """autosave session now""" 

1876 savefile = self.controller.autosave_session() 

1877 # save_session(savefile, _larch=self.larch._larch) 

1878 self.last_autosave = time.time() 

1879 stime = time.strftime("%H:%M") 

1880 self.last_save_message = ("Session last saved", f"'{savefile}'", f"{stime}") 

1881 self.write_message(f"Session saved to '{savefile}' at {stime}") 

1882 

1883 ## float-spin / pin timer events 

1884 def onPinTimer(self, event=None): 

1885 if 'start' not in self.cursor_dat: 

1886 self.cursor_dat['xsel'] = None 

1887 self.onPinTimerComplete(reason="bad") 

1888 pin_config = self.controller.get_config('pin', 

1889 {'style': 'pin_first', 

1890 'max_time':15.0, 

1891 'min_time': 2.0}) 

1892 min_time = float(pin_config['min_time']) 

1893 timeout = float(pin_config['max_time']) 

1894 

1895 curhist_name = self.cursor_dat['name'] 

1896 cursor_hist = getattr(self.larch.symtable._plotter, curhist_name, []) 

1897 if len(cursor_hist) > self.cursor_dat['nhist']: # got new data! 

1898 self.cursor_dat['xsel'] = cursor_hist[0][0] 

1899 self.cursor_dat['ysel'] = cursor_hist[0][1] 

1900 if time.time() > min_time + self.cursor_dat['start']: 

1901 self.timers['pin'].Stop() 

1902 self.onPinTimerComplete(reason="new") 

1903 elif time.time() > timeout + self.cursor_dat['start']: 

1904 self.onPinTimerComplete(reason="timeout") 

1905 

1906 if 'win' in self.cursor_dat and 'xsel' in self.cursor_dat: 

1907 time_remaining = timeout + self.cursor_dat['start'] - time.time() 

1908 msg = 'Select Point from Plot #%d' % (self.cursor_dat['win']) 

1909 if self.cursor_dat['xsel'] is not None: 

1910 msg = '%s, [current value=%.1f]' % (msg, self.cursor_dat['xsel']) 

1911 msg = '%s, expiring in %.0f sec' % (msg, time_remaining) 

1912 self.write_message(msg) 

1913 

1914 def onPinTimerComplete(self, reason=None, **kws): 

1915 self.timers['pin'].Stop() 

1916 if reason != "bad": 

1917 msg = 'Selected Point at %.1f' % self.cursor_dat['xsel'] 

1918 if reason == 'timeout': 

1919 msg = msg + '(timed-out)' 

1920 self.write_message(msg) 

1921 if (self.cursor_dat['xsel'] is not None and 

1922 callable(self.cursor_dat['callback'])): 

1923 self.cursor_dat['callback'](**self.cursor_dat) 

1924 time.sleep(0.05) 

1925 else: 

1926 self.write_message('Select Point Error') 

1927 self.cursor_dat = {} 

1928 

1929 

1930 def onSelPoint(self, evt=None, opt='__', relative_e0=True, callback=None, 

1931 win=None): 

1932 """ 

1933 get last selected point from a specified plot window 

1934 and fill in the value for the widget defined by `opt`. 

1935 

1936 start Pin Timer to get last selected point from a specified plot window 

1937 and fill in the value for the widget defined by `opt`. 

1938 """ 

1939 if win is None: 

1940 win = 1 

1941 

1942 display = get_display(win=win, _larch=self.larch) 

1943 display.Raise() 

1944 msg = 'Select Point from Plot #%d' % win 

1945 self.write_message(msg) 

1946 

1947 now = time.time() 

1948 curhist_name = 'plot%d_cursor_hist' % win 

1949 cursor_hist = getattr(self.larch.symtable._plotter, curhist_name, []) 

1950 

1951 self.cursor_dat = dict(relative_e0=relative_e0, opt=opt, 

1952 callback=callback, 

1953 start=now, xsel=None, ysel=None, 

1954 win=win, name=curhist_name, 

1955 nhist=len(cursor_hist)) 

1956 

1957 pin_config = self.controller.get_config('pin', 

1958 {'style': 'pin first', 

1959 'max_time':15.0, 

1960 'min_time': 2.0}) 

1961 if pin_config['style'].startswith('plot'): 

1962 if len(cursor_hist) > 0: 

1963 x, y, t = cursor_hist[0] 

1964 if now < (t + 60.0): 

1965 self.cursor_dat['xsel'] = x 

1966 self.cursor_dat['ysel'] = y 

1967 msg = 'Selected Point at x=%.3f' % self.cursor_dat['xsel'] 

1968 self.cursor_dat['callback'](**self.cursor_dat) 

1969 else: 

1970 self.write_message('No Points selected from plot window!') 

1971 else: # "pin first" mode 

1972 if len(cursor_hist) > 2: # purge old cursor history 

1973 setattr(self.larch.symtable._plotter, curhist_name, cursor_hist[:2]) 

1974 

1975 if len(cursor_hist) > 0: 

1976 x, y, t = cursor_hist[0] 

1977 if now < (t + 30.0): 

1978 self.cursor_dat['xsel'] = x 

1979 self.cursor_dat['ysel'] = y 

1980 self.timers['pin'].Start(250) 

1981 

1982 

1983class LarixApp(LarchWxApp): 

1984 def __init__(self, filename=None, check_version=True, mode='xas', 

1985 with_wx_inspect=False, **kws): 

1986 self.filename = filename 

1987 self.mode = mode 

1988 self.with_wx_inspect = with_wx_inspect 

1989 self.check_version = check_version 

1990 LarchWxApp.__init__(self,**kws) 

1991 

1992 def createApp(self): 

1993 self.frame = LarixFrame(filename=self.filename, 

1994 mode=self.mode, 

1995 with_wx_inspect=self.with_wx_inspect, 

1996 check_version=self.check_version) 

1997 self.SetTopWindow(self.frame) 

1998 # if self.with_wx_inspect: 

1999 # wx.GetApp().ShowInspectionTool() 

2000 return True 

2001 

2002def larix(**kws): 

2003 LarixApp(**kws) 

2004 

2005if __name__ == "__main__": 

2006 import argparse 

2007 parser = argparse.ArgumentParser(description=LARIX_TITLE) 

2008 parser.add_argument( 

2009 '-f', '--filename', 

2010 dest='filename', 

2011 help='data file to load') 

2012 parser.add_argument( 

2013 '-m', '--mode', 

2014 dest='mode', 

2015 help='mode to start larix') 

2016 parser.add_argument( 

2017 '-w', '--wx_inspect', 

2018 dest='wx_inspect', 

2019 help='wx debugging mode') 

2020 args = parser.parse_args() 

2021 LarixApp(**vars(args)).MainLoop()