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
« 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')
16from functools import partial
18import wx
19import wx.lib.scrolledpanel as scrolled
20import wx.lib.agw.flatnotebook as flat_nb
21from wx.adv import AboutBox, AboutDialogInfo
23from wx.richtext import RichTextCtrl
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)
34from larch.larchlib import read_workdir, save_workdir, read_config, save_config
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)
47from larch.wxlib.plotter import get_display
49from larch.fitting import fit_report
50from larch.site_config import icondir, home_dir, user_larchdir
51from larch.version import check_larchversion
53from .xas_controller import XASController
54from .taskpanel import GroupJournalFrame
55from .config import (FULLCONF, CONF_SECTIONS, CVar, ATHENA_CLAMPNAMES,
56 LARIX_PANELS, LARIX_MODES)
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)
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
71from larch.xafs import pre_edge, pre_edge_baseline
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
77LEFT = wx.ALIGN_LEFT
78CEN |= wx.ALL
79FILE_WILDCARDS = "Data Files|*.0*;*.dat;*.DAT;*.xdi;*.prj;*.sp*c;*.h*5;*.larix|All files (*.*)|*.*"
81ICON_FILE = 'onecone.ico'
82LARIX_SIZE = (1050, 850)
83LARIX_MINSIZE = (500, 250)
84PLOTWIN_SIZE = (550, 550)
86QUIT_MESSAGE = '''Really Quit? You may want to save your project before quitting.
87 This is not done automatically!'''
89LARIX_TITLE = "Larix: XAS Visualization and Analysis"
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, :])
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, :])
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, :])
109 group.array_labels = labels
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))
119 sizer = wx.BoxSizer(wx.VERTICAL)
120 tpanel = wx.Panel(self)
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'])
127 self.save_btn = Button(tpanel, 'Save for Future sessions',
128 size=(200, -1), action=self.onSave)
130 self.nb = flatnotebook(tpanel, {})
131 self.wids = {}
132 conf = self.controller.config
134 def text(panel, label, size):
135 return SimpleText(panel, label, size=(size, -1), style=LEFT)
137 for name, data in FULLCONF.items():
138 self.wids[name] = {}
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)
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)
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)
160 panel.Add((5, 5), newrow=True)
161 panel.Add(text(panel, 'Name', 150))
163 panel.Add(text(panel, 'Value', 250))
164 panel.Add(text(panel, 'Factory Default Value', 225))
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)
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
193 panel.pack()
194 self.nb.AddPage(panel, name, True)
196 self.nb.SetSelection(0)
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))
207 self.Show()
208 self.Raise()
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)
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
231 def onSave(self, event=None):
232 self.controller.save_config()
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 = {}
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
248 font = parent.GetFont()
250 titlefont = self.GetFont()
251 titlefont.PointSize += 2
252 titlefont.SetWeight(wx.BOLD)
254 sizer = wx.GridBagSizer(5, 5)
255 panel = scrolled.ScrolledPanel(self, size=(700, 750),
256 style=wx.GROW|wx.TAB_TRAVERSAL)
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: ')
265 self.wids = wids = {}
266 self.current_mode = self.parent.mode
267 modenames = [m[0] for m in LARIX_MODES.values()]
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])
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)
284 self.selections = {}
285 strlen = 30
286 for pagename in page_map:
287 strlen = max(strlen, len(pagename))
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)
299 irow += 1
300 sizer.Add(HLine(panel, (325, 3)), (irow, 0), (1, 2), labstyle|wx.ALL, 3)
302 btn_ok = Button(panel, 'Apply', size=(70, -1), action=self.OnOK)
303 btn_cancel = Button(panel, 'Done', size=(70, -1), action=self.OnCancel)
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)
309 pack(panel, sizer)
310 panel.SetupScrolling()
311 mainsizer = wx.BoxSizer(wx.VERTICAL)
312 mainsizer.Add(panel, 1, wx.GROW|wx.ALL, 1)
314 self.SetMinSize((750, 450))
315 pack(self, mainsizer)
316 self.Show()
317 self.Raise()
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)
330 for name in panels:
331 if name in self.selections:
332 self.selections[name].SetValue(True)
333 self.Thaw()
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)
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()
349 def OnCancel(self, event=None):
350 self.Destroy()
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)
360 if check_version:
361 def version_checker():
362 self.vinfo = check_larchversion()
363 version_thread = Thread(target=version_checker)
364 version_thread.start()
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
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)
387 self.larch = self.larch_buffer.larchshell
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))
393 self.last_autosave = 0
394 self.last_save_message = ('Session has not been saved', '', '')
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 = {}
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()
418 self.Raise()
419 self.statusbar.SetStatusText('ready', 1)
420 self.timers['autosave'].Start(30_000)
422 if self.current_filename is not None:
423 wx.CallAfter(self.onRead, self.current_filename)
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()
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))
449 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE,
450 size=(700, 700))
451 splitter.SetMinimumPaneSize(250)
453 leftpanel = wx.Panel(splitter)
454 ltop = wx.Panel(leftpanel)
456 def Btn(msg, x, act):
457 b = Button(ltop, msg, size=(x, 30), action=act)
458 b.SetFont(Font(FONTSIZE))
459 return b
461 sel_none = Btn('Select None', 120, self.onSelNone)
462 sel_all = Btn('Select All', 120, self.onSelAll)
464 file_actions = [('Show Group Journal', self.onGroupJournal),
465 ('Copy Group', self.onCopyGroup),
466 ('Rename Group', self.onRenameGroup),
467 ('Remove Group', self.onRemoveGroup)]
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)
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)
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)
486 pack(leftpanel, sizer)
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'])
497 ir = 0
498 sizer.Add(self.title, 0, CEN, 3)
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)
511 sizer.Add(self.nb, 1, LEFT|wx.EXPAND, 2)
512 panel.SetupScrolling()
514 pack(panel, sizer)
515 splitter.SplitVertically(leftpanel, panel, 1)
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
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
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)
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)
557 def process_exafs(self, dgroup, force=True):
558 self.get_nbpage('exafs')[1].process(dgroup, force=force)
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'))
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)
578 def onNBChanged(self, event=None):
579 callback = getattr(self.nb.GetCurrentPage(), 'onPanelExposed', None)
580 if callable(callback):
581 callback()
583 def onSelAll(self, event=None):
584 self.controller.filelist.select_all()
586 def onSelNone(self, event=None):
587 self.controller.filelist.select_none()
589 def write_message(self, msg, panel=0):
590 """write a message to the Status Bar"""
591 self.statusbar.SetStatusText(msg, panel)
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()
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
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())
614 if groupname is None and filename is not None:
615 groupname = self.controller.file_groups[filename]
617 if not hasattr(self.larch.symtable, groupname):
618 return
620 dgroup = self.controller.get_group(groupname)
621 if dgroup is None:
622 return
624 # print("This ShowFile ", groupname, getattr(dgroup, 'datatype', 'xydata'))
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', '?')
638 if isinstance(sdesc, Entry):
639 sdesc = sdesc.value
640 if not isinstance(sdesc, str):
641 sdesc = repr(sdesc)
642 self.title.SetLabel(sdesc)
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
656 self.controller.filelist.SetStringSelection(filename)
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 = {}
670 MenuItem(self, file_menu, "&Open Data File\tCtrl+O",
671 "Open Data File", self.onReadDialog)
673 file_menu.AppendSeparator()
675 MenuItem(self, file_menu, "&Read Larch Session\tCtrl+R",
676 "Read Previously Saved Session", self.onLoadSession)
678 MenuItem(self, file_menu, "&Save Larch Session\tCtrl+S",
679 "Save Session to a File", self.onSaveSession)
681 MenuItem(self, file_menu, "&Save Larch Session As ...\tCtrl+A",
682 "Save Session to a File", self.onSaveSessionAs)
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)
690 MenuItem(self, file_menu, "Save Selected Groups to CSV File",
691 "Save Selected Groups to a CSV File",
692 self.onExportCSV)
694 MenuItem(self, file_menu, "&Quit\tCtrl+Q", "Quit program", self.onClose)
697 MenuItem(self, session_menu, "&Read Larch Session",
698 "Read Previously Saved Session", self.onLoadSession)
700 MenuItem(self, session_menu, "&Save Larch Session",
701 "Save Session to a File", self.onSaveSession)
703 MenuItem(self, session_menu, "&Save Larch Session As ...",
704 "Save Session to a File", self.onSaveSessionAs)
706 MenuItem(self, session_menu, "Clear Larch Session",
707 "Clear all data from this Session", self.onClearSession)
709 # autosaved session
710 conf = self.controller.get_config('autosave',
711 {'fileroot': 'autosave'})
712 froot= conf['fileroot']
714 self.recent_menu = wx.Menu()
715 self.get_recent_session_menu()
716 session_menu.Append(-1, 'Recent Session Files', self.recent_menu)
719 MenuItem(self, session_menu, "&Auto-Save Larch Session",
720 f"Save Session now", self.autosave_session)
722 session_menu.AppendSeparator()
724 MenuItem(self, session_menu, 'Show Larch Buffer\tCtrl+L',
725 'Show Larch Programming Buffer',
726 self.onShowLarchBuffer)
728 MenuItem(self, session_menu, 'Save Larch History as Script\tCtrl+H',
729 'Save Session History as Larch Script',
730 self.onSaveLarchHistory)
732 if self.with_wx_inspect:
733 MenuItem(self, session_menu, 'wx inspect\tCtrl+I',
734 'Show wx inspection window', self.onwxInspect)
736 MenuItem(self, pref_menu, 'Select Analysis Panels and Modes',
737 'Select Analysis Panels and Modes',
738 self.onPanelSelect)
740 MenuItem(self, pref_menu, 'Edit Preferences\tCtrl+E', 'Customize Preferences',
741 self.onPreferences)
743 MenuItem(self, group_menu, "Copy This Group",
744 "Copy This Group", self.onCopyGroup)
746 MenuItem(self, group_menu, "Rename This Group",
747 "Rename This Group", self.onRenameGroup)
749 MenuItem(self, group_menu, "Show Journal for This Group",
750 "Show Processing Journal for This Group", self.onGroupJournal)
753 group_menu.AppendSeparator()
755 MenuItem(self, group_menu, "Remove Selected Groups",
756 "Remove Selected Group", self.onRemoveGroups)
758 group_menu.AppendSeparator()
760 MenuItem(self, group_menu, "Merge Selected Groups",
761 "Merge Selected Groups", self.onMergeData)
763 group_menu.AppendSeparator()
765 MenuItem(self, group_menu, "Freeze Selected Groups",
766 "Freeze Selected Groups", self.onFreezeGroups)
768 MenuItem(self, group_menu, "UnFreeze Selected Groups",
769 "UnFreeze Selected Groups", self.onUnFreezeGroups)
771 MenuItem(self, xasdata_menu, "Deglitch Data", "Deglitch Data",
772 self.onDeglitchData)
774 MenuItem(self, xasdata_menu, "Calibrate Energy",
775 "Calibrate Energy",
776 self.onEnergyCalibrateData)
778 MenuItem(self, xasdata_menu, "Smooth Data", "Smooth Data",
779 self.onSmoothData)
781 MenuItem(self, xasdata_menu, "Deconvolve Data",
782 "Deconvolution of Data", self.onDeconvolveData)
784 MenuItem(self, xasdata_menu, "Rebin Data", "Rebin Data",
785 self.onRebinData)
787 MenuItem(self, xasdata_menu, "Correct Over-absorption",
788 "Correct Over-absorption",
789 self.onCorrectOverAbsorptionData)
791 MenuItem(self, xasdata_menu, "Add and Subtract Spectra",
792 "Calculations of Spectra", self.onSpectraCalc)
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")
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)
808 self.menubar.Append(feff_menu, "Feff")
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)
816 self.menubar.Append(hmenu, '&Help')
817 self.SetMenuBar(self.menubar)
818 self.Bind(wx.EVT_CLOSE, self.onClose)
820 def onwxInspect(self, evt=None):
821 wx.GetApp().ShowInspectionTool()
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()
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)
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
845 deffile = "%s_%i.csv" % (filenames[0], len(filenames))
847 dlg = ExportCSVDialog(self, filenames)
848 res = dlg.GetResponse()
850 dlg.Destroy()
851 if not res.ok:
852 return
854 deffile = f"{filenames[0]:s}_{len(filenames):d}.csv"
855 wcards = 'CSV Files (*.csv)|*.csv|All files (*.*)|*.*'
857 outfile = FileSave(self, 'Save Groups to CSV File',
858 default_file=deffile, wildcard=wcards)
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
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)
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)
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)])
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)
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
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
912 prompt, prjfile = self.get_athenaproject()
913 self.save_athena_project(prjfile, groups, prompt=prompt,
914 warn_overwrite=False)
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
922 prompt, prjfile = self.get_athena_project()
923 self.save_athena_project(prjfile, groups)
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
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
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
953 def onPreferences(self, evt=None):
954 self.show_subframe('preferences', PreferencesFrame,
955 controller=self.controller)
957 def onPanelSelect(self, evt=None):
958 self.show_subframe('panel_select', PanelSelectionFrame,
959 controller=self.controller)
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
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
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
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()
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()
1000 def onSaveSession(self, evt=None):
1001 groups = self.controller.filelist.GetItems()
1002 if len(groups) < 1:
1003 return
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'
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
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
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
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()
1035 msg = f"""Session will be saved to
1036 '{afile:s}'
1037before clearing"""
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)
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)
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()
1051 fit_dialog_window(dlg, panel)
1054 if wx.ID_OK == dlg.ShowModal():
1055 self.autosave_session()
1056 self.controller.clear_session()
1057 dlg.Destroy()
1060 def onConfigDataProcessing(self, event=None):
1061 pass
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)
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)
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()
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)
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
1102 self.controller.filelist.SetCheckedStrings(selected)
1103 self.controller.filelist.SetStringSelection(res.newname)
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]
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)
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
1127 dlg = RemoveDialog(self, groups)
1128 res = dlg.GetResponse()
1129 dlg.Destroy()
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)
1139 filelist.Clear()
1140 for name in all_fnames:
1141 filelist.Append(name)
1142 self.controller.sync_xasgroups()
1144 def onFreezeGroups(self, event=None):
1145 self._freeze_handler(True)
1147 def onUnFreezeGroups(self, event=None):
1148 self._freeze_handler(False)
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)
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
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)
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})
1199 def has_datagroup(self):
1200 return hasattr(self.controller.get_group(), 'energy')
1202 def onDeglitchData(self, event=None):
1203 if self.has_datagroup():
1204 DeglitchDialog(self, self.controller).Show()
1206 def onSmoothData(self, event=None):
1207 if self.has_datagroup():
1208 SmoothDataDialog(self, self.controller).Show()
1210 def onRebinData(self, event=None):
1211 if self.has_datagroup():
1212 RebinDataDialog(self, self.controller).Show()
1214 def onCorrectOverAbsorptionData(self, event=None):
1215 if self.has_datagroup():
1216 OverAbsorptionDialog(self, self.controller).Show()
1218 def onSpectraCalc(self, event=None):
1220 if self.has_datagroup():
1221 SpectraCalcDialog(self, self.controller).Show()
1223 def onEnergyCalibrateData(self, event=None):
1224 if self.has_datagroup():
1225 EnergyCalibrateDialog(self, self.controller).Show()
1227 def onDeconvolveData(self, event=None):
1228 if self.has_datagroup():
1229 DeconvolutionDialog(self, self.controller).Show()
1231 def onConfigDataFitting(self, event=None):
1232 pass
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)
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)
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
1263 self.controller.save_workdir()
1264 try:
1265 self.controller.close_all_displays()
1266 except Exception:
1267 pass
1269 if self.larch_buffer is not None:
1270 try:
1271 self.larch_buffer.Destroy()
1272 except Exception:
1273 pass
1275 def destroy(wid):
1276 if hasattr(wid, 'Destroy'):
1277 try:
1278 wid.Destroy()
1279 except Exception:
1280 pass
1281 time.sleep(0.01)
1283 for name, wid in self.subframes.items():
1284 destroy(wid)
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()
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()
1299 time.sleep(0.05)
1300 self.Destroy()
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)
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)
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)
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)
1327 def onLoadFitResult(self, event=None):
1328 pass
1329 # print("onLoadFitResult??")
1330 # self.nb.SetSelection(1)
1331 # self.nb_panels[1].onLoadFitResult(event=event)
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()
1343 if len(self.paths2read) < 1:
1344 return
1346 def file_mtime(x):
1347 return os.stat(x).st_mtime
1349 self.paths2read = [Path(p).as_posix() for p in self.paths2read]
1350 self.paths2read = sorted(self.paths2read, key=file_mtime)
1352 path = self.paths2read.pop(0)
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)
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()
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
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
1387 # check for Larch Session File
1388 if is_larch_session_file(fullpath):
1389 self.onLoadSession(path=fullpath)
1390 return
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)
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
1411 array_desc = config.get('array_desc', {})
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', []))
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)
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
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)
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]
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)
1473 cur_panel.skip_plotting = False
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')
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
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
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))
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
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')
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 = []
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)
1545 jrnl = {'source_desc': f'{spath:s}: {gname:s}'}
1546 self.larch.eval(script.format(group=gid, prjgroup=gname))
1548 dgroup = self.install_group(gid, label, process=False,
1549 source=path, journal=jrnl)
1550 groups_added.append(gid)
1552 for gid in groups_added:
1553 rgroup = gid
1554 dgroup = self.larch.symtable.get_group(gid)
1556 conf_xasnorm = dgroup.config.xasnorm
1557 conf_exafs= dgroup.config.exafs
1559 apars = getattr(dgroup, 'athena_params', {})
1560 abkg = getattr(apars, 'bkg', {})
1561 afft = getattr(apars, 'fft', {})
1563 # norm
1564 for attr in ('e0', 'pre1', 'pre2', 'nnorm'):
1565 if hasattr(abkg, attr):
1566 conf_xasnorm[attr] = float(getattr(abkg, attr))
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)
1577 # bkg
1578 for attr in ('e0', 'rbkg'):
1579 if hasattr(abkg, attr):
1580 conf_exafs[attr] = float(getattr(abkg, attr))
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
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))
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
1613 if newname is not None:
1614 refgroup = newname
1615 else:
1616 refgroup = dgroup.filename
1617 dgroup.energy_ref = refgroup
1619 self.larch.eval("del _prj")
1620 cur_panel.skip_plotting = False
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))
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):
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'])
1653 if hasattr(self.larch.symtable, groupname):
1654 groupname = file2groupname(filename,
1655 symtable=self.larch.symtable)
1657 refgroup = config.get('refgroup', groupname + '_ref')
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', []))
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)
1670 config = copy.copy(config)
1671 config['group'] = groupname
1672 config['path'] = path
1673 has_yref = config.get('has_yref', False)
1676 self.larch.eval(script.format(**config))
1678 if config is not None:
1679 self.last_col_config = config
1681 journal = {'source': path}
1682 refjournal = {}
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)
1692 self.install_group(groupname, filename, source=path, journal=journal)
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)")
1708 i0 = '1.0'
1709 if multi_i0 < len(config['array_labels']):
1710 i0 = config['array_labels'][multi_i0]
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)
1729 if len(multi_chans) > 0:
1730 install_multichans(config)
1732 if has_yref:
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)
1742 # check if rebin is needed
1743 thisgroup = getattr(self.larch.symtable, groupname)
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
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}"
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
1785 self.larch.eval(script.format(**config))
1786 if has_yref:
1787 self.larch.eval(f"{gname}.energy_ref = {refgroup}.energy_ref = '{refgroup}'\n")
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)
1797 self.install_group(gname, fname, source=path, journal=journal, plot=False)
1798 if len(multi_chans) > 0:
1799 install_multichans(config)
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}'
1808 self.install_group(refgroup, reffile, source=path, journal=refjournal, plot=False)
1811 if gname is not None:
1812 self.ShowFile(groupname=gname)
1814 self.write_message("read %s" % (spath))
1815 if do_rebin:
1816 RebinDataDialog(self, self.controller).Show()
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)
1827 if filename is None:
1828 filename = dgroup.filename
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)
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)
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))
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))
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
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()
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}")
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'])
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")
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)
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 = {}
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`.
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
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)
1947 now = time.time()
1948 curhist_name = 'plot%d_cursor_hist' % win
1949 cursor_hist = getattr(self.larch.symtable._plotter, curhist_name, [])
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))
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])
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)
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)
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
2002def larix(**kws):
2003 LarixApp(**kws)
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()