Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/columnframe.py: 7%
1093 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1#!/usr/bin/env python
2"""
4"""
5import os
6import re
7from copy import deepcopy
9import numpy as np
10np.seterr(all='ignore')
12from functools import partial
14import wx
15import wx.lib.scrolledpanel as scrolled
16import wx.lib.agw.flatnotebook as fnb
17from wxmplot import PlotPanel
19from wxutils import (SimpleText, FloatCtrl, FloatSpin, GUIColors, Button, Choice,
20 TextCtrl, pack, Popup, Check, MenuItem, CEN, RIGHT, LEFT,
21 FRAMESTYLE, HLine, Font)
23import larch
24from larch import Group
25from larch.xafs.xafsutils import guess_energy_units
26from larch.utils.strutils import fix_varname, fix_filename, file2groupname
27from larch.io import look_for_nans, guess_filereader, is_specfile, sum_fluor_channels
28from larch.utils.physical_constants import PLANCK_HC, DEG2RAD
29from larch.utils import gformat
30from larch.math import safe_log
31from . import FONTSIZE
33CEN |= wx.ALL
34FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS
35FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG
37YPRE_OPS = ('', 'log(', '-log(', '-')
38ARR_OPS = ('+', '-', '*', '/')
40YERR_OPS = ('Constant', 'Sqrt(Y)', 'Array')
41CONV_OPS = ('Lorenztian', 'Gaussian')
43DATATYPES = ('raw', 'xas')
44ENUNITS_TYPES = ('eV', 'keV', 'degrees', 'not energy')
47MULTICHANNEL_TITLE = """ Sum MultiChannel Fluorescence Data, with Dead-Time Corrections:
48 To allow for many Dead-Time-Correction methods, each Channel is built as:
49 ROI_Corrected = ROI * ICR /(OCR * LTIME)
51 Set the Number of Channels, the Step (usually 1) between columns for
52 ROI 1, 2, ..., NChans, and any Bad Channels: a list of Channel numbers (start at 1).
54 Select columns for ROI (counts) and correction factors ICR, OCR, and LTIME for Channel 1.
56"""
58ROI_STEP_TOOLTIP = """number of columns between ROI columns -- typically 1 if the columns are like
59 ROI_Ch1 ROI_Ch2 ROI_Ch3 ... ICR_Ch1 ICR_Ch2 ICR_Ch3 ... OCR_Ch1 OCR_Ch2 OCR_Ch3 ...
61but set to 3 if the columns are arranged as
62 ROI_Ch1 ICR_Ch1 OCR_Ch1 ROI_Ch2 ICR_Ch2 OCR_Ch2 ROI_Ch3 ICR_Ch3 OCR_Ch3 ...
63"""
64MAXCHANS=2000
66class DeadtimeCorrectionFrame(wx.Frame):
67 """Manage MultiChannel Fluorescence Data"""
68 def __init__(self, parent, group, config=None, on_ok=None):
69 self.parent = parent
70 self.group = group
71 self.config = {'bad_chans': [], 'plot_chan': 1, 'nchans': 4, 'step': 1,
72 'roi': '1.0', 'icr': '1.0', 'ocr': '1.0',
73 'ltime': '1.0', 'i0': '1.0'}
74 # 'out_choice': 'summed spectrum',
75 if config is not None:
76 self.config.update(config)
77 self.arrays = {}
78 self.on_ok = on_ok
79 wx.Frame.__init__(self, None, -1, 'MultiChannel Fluorescence Data',
80 style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
82 self.SetFont(Font(FONTSIZE))
83 sizer = wx.GridBagSizer(2, 2)
84 panel = scrolled.ScrolledPanel(self)
86 self.SetMinSize((650, 450))
87 self.yarr_labels = [s for s in self.parent.yarr_labels]
88 wids = self.wids = {}
90 multi_title = wx.StaticText(panel, label=MULTICHANNEL_TITLE, size=(650, 150))
91 multi_title.SetFont(Font(FONTSIZE-1))
92 for s in ('roi', 'icr', 'ocr', 'ltime'):
93 wids[s] = Choice(panel, choices=self.yarr_labels, action=self.read_form, size=(150, -1))
94 sel = self.config.get(s, '1.0')
95 if sel == '1.0':
96 wids[s].SetStringSelection(sel)
97 else:
98 wids[s].SetSelection(sel[0])
99 wids[f'{s}_txt'] = SimpleText(panel, label='<list of column labels>', size=(275, -1))
101 wids['i0'] = Choice(panel, choices=self.yarr_labels, action=self.read_form, size=(150, -1))
102 wids['i0'].SetToolTip("All Channels will be divided by the I0 array")
104 wids['i0'].SetStringSelection(self.parent.yarr2.GetStringSelection())
106 wids['nchans'] = FloatCtrl(panel, value=self.config.get('nchans', 4),
107 precision=0, maxval=MAXCHANS, minval=1, size=(50, -1),
108 action=self.on_nchans)
109 wids['bad_chans'] = TextCtrl(panel, value='', size=(175, -1), action=self.read_form)
110 bad_chans = self.config.get('bad_chans', [])
111 if len(bad_chans) > 0:
112 wids['bad_chans'].SetValue(', '.join(['%d' % c for c in bad_chans]))
113 wids['bad_chans'].SetToolTip("List Channels to skip, separated by commas or spaces")
114 wids['step'] = FloatCtrl(panel, value=self.config.get('step', 1), precision=0,
115 maxval=MAXCHANS, minval=1, size=(50, -1), action=self.read_form)
116 wids['step'].SetToolTip(ROI_STEP_TOOLTIP)
118 wids['plot_chan'] = FloatSpin(panel, value=self.config.get('plot_chan', 1),
119 digits=0, increment=1, max_val=MAXCHANS, min_val=1, size=(50, -1),
120 action=self.onPlotThis)
122 wids['plot_this'] = Button(panel, 'Plot ROI + Correction For Channel', action=self.onPlotThis)
123 wids['plot_all'] = Button(panel, 'Plot All Channels', action=self.onPlotEach)
124 wids['plot_sum'] = Button(panel, 'Plot Sum of Channels', action=self.onPlotSum)
125 wids['save_btn'] = Button(panel, 'Use this Sum of Channels', action=self.onOK_DTC)
127 def tlabel(t):
128 return SimpleText(panel, label=t)
130 sizer.Add(multi_title, (0, 0), (2, 5), LEFT, 3)
131 ir = 2
132 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3)
134 ir += 1
135 sizer.Add(tlabel(' Number of Channels:'), (ir, 0), (1, 1), LEFT, 3)
136 sizer.Add(wids['nchans'], (ir, 1), (1, 1), LEFT, 3)
137 sizer.Add(tlabel(' Step between Channels:'), (ir, 2), (1, 1), LEFT, 3)
138 sizer.Add(wids['step'], (ir, 3), (1, 1), LEFT, 3)
140 ir += 1
141 sizer.Add(tlabel(' Bad Channels :'), (ir, 0), (1, 1), LEFT, 3)
142 sizer.Add(wids['bad_chans'], (ir, 1), (1, 2), LEFT, 3)
144 ir += 1
145 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3)
147 ir += 1
148 sizer.Add(tlabel(' Signal '), (ir, 0), (1, 1), LEFT, 3)
149 sizer.Add(tlabel(' Array for Channel #1 '), (ir, 1), (1, 1), LEFT, 3)
150 sizer.Add(tlabel(' Array Labels used for all Channels '), (ir, 2), (1, 3), LEFT, 3)
152 for s in ('roi', 'icr', 'ocr', 'ltime'):
153 ir += 1
154 sizer.Add(tlabel(f' {s.upper()} #1 : '), (ir, 0), (1, 1), LEFT, 3)
155 sizer.Add(wids[s], (ir, 1), (1, 1), LEFT, 3)
156 sizer.Add(wids[f'{s}_txt'], (ir, 2), (1, 3), LEFT, 3)
158 ir += 1
159 sizer.Add(tlabel(' I0 : '), (ir, 0), (1, 1), LEFT, 3)
160 sizer.Add(wids['i0'], (ir, 1), (1, 1), LEFT, 3)
162 ir += 1
163 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3)
165 ir += 1
166 sizer.Add(wids['plot_this'], (ir, 0), (1, 2), LEFT, 3)
167 sizer.Add(tlabel(' Channel:'), (ir, 2), (1, 1), LEFT, 3)
168 sizer.Add(wids['plot_chan'], (ir, 3), (1, 1), LEFT, 3)
170 ir += 1
171 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3)
172 ir += 1
173 sizer.Add(wids['plot_all'], (ir, 0), (1, 1), LEFT, 3)
174 sizer.Add(wids['plot_sum'], (ir, 1), (1, 1), LEFT, 3)
175 sizer.Add(wids['save_btn'], (ir, 2), (1, 2), LEFT, 3)
177 pack(panel, sizer)
178 panel.SetupScrolling()
180 mainsizer = wx.BoxSizer(wx.VERTICAL)
181 mainsizer.Add(panel, 1, wx.GROW|wx.ALL, 1)
183 pack(self, mainsizer)
184 self.Show()
185 self.Raise()
187 def get_en_i0(self):
188 en = self.group.xdat
189 i0 = 1.0
190 if self.config['i0'] != '1.0':
191 i0 = self.group.data[self.config['i0'], :]
192 return en, i0
194 def read_arrays(self, pchan):
195 def get_array(name, pchan, default=1.0):
196 out = default
197 if self.config[name] != '1.0':
198 ix = self.config[name][pchan-1]
199 if ix > 0:
200 out = self.group.data[ix, :]
201 return out
202 roi = get_array('roi', pchan, default=None)
203 icr = get_array('icr', pchan)
204 ocr = get_array('ocr', pchan)
205 ltime = get_array('ltime', pchan)
206 return roi, icr*ocr/ltime
208 def onOK_DTC(self, event=None):
209 self.read_form()
210 if callable(self.on_ok):
211 self.on_ok(self.config)
212 self.Destroy()
214 def onPlotSum(self, event=None):
215 self.read_form()
216 en, i0 = self.get_en_i0()
217 label, sum = sum_fluor_channels(self.group, self.config['roi'],
218 icr=self.config['icr'],
219 ocr=self.config['ocr'],
220 ltime=self.config['ltime'],
221 add_data=False)
222 if sum is not None:
223 popts = dict(marker=None, markersize=0, linewidth=2.5,
224 show_legend=True, ylabel=label, label=label,
225 xlabel='Energy (eV)')
226 self.parent.plotpanel.plot(en, sum/i0, new=True, **popts)
228 def onPlotEach(self, event=None):
229 self.read_form()
230 new = True
231 en, i0 = self.get_en_i0()
232 popts = dict(marker=None, markersize=0, linewidth=2.5,
233 show_legend=True, xlabel='Energy (eV)',
234 ylabel=f'Corrected Channels')
236 nused = 0
237 for pchan in range(1, self.config['nchans']+1):
238 roi, dtc = self.read_arrays(pchan)
239 if roi is not None:
240 popts['label'] = f'Chan{pchan} Corrected'
241 if new:
242 self.parent.plotpanel.plot(en, roi*dtc/i0, new=True, **popts)
243 new = False
244 else:
245 self.parent.plotpanel.oplot(en, roi*dtc/i0, **popts)
247 def onPlotThis(self, event=None):
248 self.read_form()
249 en, i0 = self.get_en_i0()
250 pchan = self.config['plot_chan']
251 roi, dtc = self.read_arrays(pchan)
252 if roi is None:
253 return
254 ylabel = self.wids['roi'].GetStringSelection()
255 popts = dict(marker=None, markersize=0, linewidth=2.5, show_legend=True,
256 ylabel=f'Chan{pchan}', xlabel='Energy (eV)',
257 label=f'Chan{pchan} Raw')
259 self.parent.plotpanel.plot(en, roi/i0, new=True, **popts)
260 popts['label'] = f'Chan{pchan} Corrected'
261 self.parent.plotpanel.oplot(en, roi*dtc/i0, **popts)
263 def on_nchans(self, event=None, value=None, **kws):
264 try:
265 nchans = self.wids['nchans'].GetValue()
266 pchan = self.wids['plot_chan'].GetValue()
267 self.wids['plot_chan'].SetMax(nchans)
268 self.wids['plot_chan'].SetValue(pchan)
269 except:
270 pass
272 def read_form(self, event=None, value=None, **kws):
273 try:
274 wids = self.wids
275 nchans = int(wids['nchans'].GetValue())
276 step = int(wids['step'].GetValue())
277 badchans = wids['bad_chans'].GetValue().replace(',', ' ').strip()
278 except:
279 return
281 bad_channels = []
282 if len(badchans) > 0:
283 try:
284 bad_channels = [int(s) for s in badchans.split()]
285 wids['bad_chans'].SetBackgroundColour('#FFFFFF')
286 except:
287 bad_channels = []
288 wids['bad_chans'].SetBackgroundColour('#F0B03080')
290 pchan = int(wids['plot_chan'].GetValue())
292 self.config['bad_chans'] = bad_channels
293 self.config['plot_chan'] = pchan
294 self.config['nchans'] = nchans
295 self.config['step'] = step
296 self.config['i0'] = wids['i0'].GetSelection()
297 if wids['i0'].GetStringSelection() in ('1.0', ''):
298 self.config['i0'] = '1.0'
300 for s in ('roi', 'icr', 'ocr', 'ltime'):
301 lab = wids[s].GetStringSelection()
302 ilab = wids[s].GetSelection()
303 if lab in ('1.0', ''):
304 wids[f"{s}_txt"].SetLabel(lab)
305 wids[f"{s}_txt"].SetToolTip(lab)
306 self.config[s] = '1.0'
307 else:
308 chans = [ilab + i*step for i in range(nchans)]
309 labs = []
310 for i in range(nchans):
311 if (i+1) in bad_channels:
312 chans[i] = -1
313 else:
314 nchan = chans[i]
315 if nchan < len(self.group.array_labels):
316 labs.append(self.group.array_labels[nchan])
317 wids[f"{s}_txt"].SetLabel(', '.join(labs))
318 self.config[s] = chans
321class MultiColumnFrame(wx.Frame) :
322 """Select Multiple Columns for import, optional i0 channel"""
323 def __init__(self, parent, group, config=None, on_ok=None):
324 self.parent = parent
325 self.group = group
327 self.on_ok = on_ok
328 wx.Frame.__init__(self, None, -1, 'Import Multiple Columns from a file',
329 style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
331 self.config = {'channels': [], 'i0': '1.0'}
332 if config is not None:
333 self.config.update(config)
335 self.SetFont(Font(FONTSIZE))
336 sizer = wx.GridBagSizer(2, 2)
337 panel = scrolled.ScrolledPanel(self)
339 self.SetMinSize((475, 350))
340 self.yarr_labels = [s for s in self.parent.yarr_labels]
341 wids = self.wids = {}
343 wids['i0'] = Choice(panel, choices=self.yarr_labels, size=(200, -1))
344 wids['i0'].SetToolTip("All Channels will be divided by the I0 array")
346 wids['i0'].SetStringSelection(self.parent.yarr2.GetStringSelection())
348 bpanel = wx.Panel(panel)
349 bsizer = wx.BoxSizer(wx.HORIZONTAL)
351 bsizer.Add(Button(bpanel, ' Select All ', action=self.onSelAll))
352 bsizer.Add(Button(bpanel, ' Select None ', action=self.onSelNone))
353 bsizer.Add(Button(bpanel, ' Import Selected Columns ', action=self.onOK_Multi))
354 pack(bpanel, bsizer)
356 ir = 0
357 sizer.Add(bpanel, (ir, 0), (1, 5), LEFT, 3)
358 ir += 1
359 sizer.Add(HLine(panel, size=(450, 2)), (ir, 0), (1, 5), LEFT, 3)
361 ir += 1
362 sizer.Add(SimpleText(panel, label=' I0 Array: '), (ir, 0), (1, 1), LEFT, 3)
363 sizer.Add(wids['i0'], (ir, 1), (1, 3), LEFT, 3)
365 ir += 1
366 sizer.Add(SimpleText(panel, label=' Array Name'), (ir, 0), (1, 1), LEFT, 3)
367 sizer.Add(SimpleText(panel, label=' Import? '), (ir, 1), (1, 1), LEFT, 3)
368 sizer.Add(SimpleText(panel, label=' Plot'), (ir, 2), (1, 1), LEFT, 3)
370 array_labels = getattr(group, 'array_labels', self.yarr_labels)
371 nlabels = len(array_labels)
372 narrays, npts = group.data.shape
373 for i in range(narrays):
374 if i < nlabels:
375 name = array_labels[i]
376 else:
377 name = f'unnamed_column{i+1}'
378 self.wids[f'use_{i}'] = chuse = Check(panel, label='', default=(i in self.config['channels']))
379 slabel = SimpleText(panel, label=f' {name} ')
380 bplot = Button(panel, 'Plot', action=partial(self.onPlot, index=i))
382 ir += 1
383 sizer.Add(slabel, (ir, 0), (1, 1), LEFT, 3)
384 sizer.Add(chuse, (ir, 1), (1, 1), LEFT, 3)
385 sizer.Add(bplot, (ir, 2), (1, 1), LEFT, 3)
387 pack(panel, sizer)
388 panel.SetupScrolling()
390 mainsizer = wx.BoxSizer(wx.VERTICAL)
391 mainsizer.Add(panel, 1, wx.GROW|wx.ALL, 1)
393 pack(self, mainsizer)
394 self.Show()
395 self.Raise()
398 def onSelAll(self, event=None, *kws):
399 for wname, wid in self.wids.items():
400 if wname.startswith('use_'):
401 wid.SetValue(1)
403 def onSelNone(self, event=None, *kws):
404 for wname, wid in self.wids.items():
405 if wname.startswith('use_'):
406 wid.SetValue(0)
408 def onPlot(self, event=None, index=None):
409 if index is not None:
410 x = self.group.xdat
411 y = self.group.data[index, :]
412 try:
413 label = self.group.array_labels[index]
414 except:
415 label = f'column {index+1}'
417 popts = dict(marker=None, markersize=0, linewidth=2.5,
418 ylabel=label, xlabel=self.group.plot_xlabel, label=label)
419 self.parent.plotpanel.plot(x, y, **popts)
421 def onOK_Multi(self, evt=None):
422 group = self.group
423 self.config['i0'] = self.wids['i0'].GetSelection()
424 channels = []
425 for wname, wid in self.wids.items():
426 if wname.startswith('use_') and wid.IsChecked():
427 chan = int(wname.replace('use_', ''))
428 channels.append(chan)
430 self.config['channels'] = channels
431 if callable(self.on_ok):
432 self.on_ok(self.config)
433 self.Destroy()
436class EditColumnFrame(wx.Frame) :
437 """Edit Column Labels for a larch grouop"""
438 def __init__(self, parent, group, on_ok=None):
439 self.parent = parent
440 self.group = group
441 self.on_ok = on_ok
442 wx.Frame.__init__(self, None, -1, 'Edit Array Names',
443 style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
445 self.SetFont(Font(FONTSIZE))
446 sizer = wx.GridBagSizer(2, 2)
447 panel = scrolled.ScrolledPanel(self)
449 self.SetMinSize((700, 450))
451 self.wids = {}
452 ir = 0
453 sizer.Add(Button(panel, 'Apply Changes', size=(200, -1),
454 action=self.onOK_Edit),
455 (0, 1), (1, 2), LEFT, 3)
456 sizer.Add(Button(panel, 'Use Column Number', size=(200, -1),
457 action=self.onColNumber),
458 (0, 3), (1, 2), LEFT, 3)
459 sizer.Add(HLine(panel, size=(550, 2)),
460 (1, 1), (1, 5), LEFT, 3)
462 cind = SimpleText(panel, label='Column')
463 cold = SimpleText(panel, label=' Current Name')
464 cnew = SimpleText(panel, label=' Enter New Name')
465 cret = SimpleText(panel, label=' Result ')
466 cinfo = SimpleText(panel, label=' Data Range')
467 cplot = SimpleText(panel, label=' Plot')
469 ir = 2
470 sizer.Add(cind, (ir, 0), (1, 1), LEFT, 3)
471 sizer.Add(cold, (ir, 1), (1, 1), LEFT, 3)
472 sizer.Add(cnew, (ir, 2), (1, 1), LEFT, 3)
473 sizer.Add(cret, (ir, 3), (1, 1), LEFT, 3)
474 sizer.Add(cinfo, (ir, 4), (1, 1), LEFT, 3)
475 sizer.Add(cplot, (ir, 5), (1, 1), LEFT, 3)
477 nlabels = len(group.array_labels)
478 narrays, npts = group.data.shape
479 for i in range(narrays):
480 if i < nlabels:
481 name = group.array_labels[i]
482 else:
483 name = f'unnamed_column{i+1}'
484 ir += 1
485 cind = SimpleText(panel, label=f' {i+1} ')
486 cold = SimpleText(panel, label=f' {name} ')
487 cret = SimpleText(panel, label=fix_varname(name))
488 cnew = wx.TextCtrl(panel, value=name, size=(150, -1),
489 style=wx.TE_PROCESS_ENTER)
491 cnew.Bind(wx.EVT_TEXT_ENTER, partial(self.update, index=i))
492 cnew.Bind(wx.EVT_KILL_FOCUS, partial(self.update, index=i))
494 arr = group.data[i,:]
495 info_str = f" [{gformat(arr.min(),length=9)}:{gformat(arr.max(), length=9)}] "
496 cinfo = SimpleText(panel, label=info_str)
497 cplot = Button(panel, 'Plot', action=partial(self.onPlot, index=i))
500 self.wids[f"{i}"] = cnew
501 self.wids[f"ret_{i}"] = cret
503 sizer.Add(cind, (ir, 0), (1, 1), LEFT, 3)
504 sizer.Add(cold, (ir, 1), (1, 1), LEFT, 3)
505 sizer.Add(cnew, (ir, 2), (1, 1), LEFT, 3)
506 sizer.Add(cret, (ir, 3), (1, 1), LEFT, 3)
507 sizer.Add(cinfo, (ir, 4), (1, 1), LEFT, 3)
508 sizer.Add(cplot, (ir, 5), (1, 1), LEFT, 3)
510 pack(panel, sizer)
511 panel.SetupScrolling()
513 mainsizer = wx.BoxSizer(wx.VERTICAL)
514 mainsizer.Add(panel, 1, wx.GROW|wx.ALL, 1)
516 pack(self, mainsizer)
517 self.Show()
518 self.Raise()
520 def onPlot(self, event=None, index=None):
521 if index is not None:
522 x = self.group.index
523 y = self.group.data[index, :]
524 label = self.wids["ret_%i" % index].GetLabel()
525 popts = dict(marker='o', markersize=4, linewidth=1.5,
526 ylabel=label, xlabel='data point', label=label)
527 self.parent.plotpanel.plot(x, y, **popts)
529 def onColNumber(self, evt=None, index=-1):
530 for name, wid in self.wids.items():
531 val = name
532 if name.startswith('ret_'):
533 val = name[4:]
534 setter = wid.SetLabel
535 else:
536 setter = wid.SetValue
537 setter("col%d" % (int(val) +1))
539 def update(self, evt=None, index=-1):
540 newval = fix_varname(self.wids[f"{index}"].GetValue())
541 self.wids[f"ret_{index}"].SetLabel(newval)
543 def update_char(self, evt=None, index=-1):
544 if evt.GetKeyCode() == wx.WXK_RETURN:
545 self.update(evt=evt, index=index)
546 # evt.Skip()
548 def onOK_Edit(self, evt=None):
549 group = self.group
550 array_labels = []
551 for i in range(len(self.group.array_labels)):
552 newname = self.wids["ret_%i" % i].GetLabel()
553 array_labels.append(newname)
555 if callable(self.on_ok):
556 self.on_ok(array_labels)
557 self.Destroy()
559class ColumnDataFileFrame(wx.Frame) :
560 """Column Data File, select columns"""
561 def __init__(self, parent, filename=None, groupname=None, config=None,
562 read_ok_cb=None, edit_groupname=True, _larch=None):
563 self.parent = parent
564 self._larch = _larch
565 self.path = filename
567 group = self.read_column_file(self.path)
569 self.subframes = {}
570 self.workgroup = Group(raw=group)
571 for attr in ('path', 'filename', 'groupname', 'datatype',
572 'array_labels', 'data'):
573 setattr(self.workgroup, attr, getattr(group, attr, None))
575 self.array_labels = [l.lower() for l in group.array_labels]
577 if self.workgroup.datatype is None:
578 self.workgroup.datatype = 'raw'
579 en_units = 'not energy'
580 for arrlab in self.array_labels[:3]:
581 arrlab = arrlab.lower()
582 if arrlab.startswith('en') or 'ener' in arrlab:
583 en_units = 'eV'
584 self.workgroup.datatype = 'xas'
586 self.read_ok_cb = read_ok_cb
587 self.config = dict(xarr=None, yarr1=None, yarr2=None, yop='/',
588 ypop='', monod=3.1355316, en_units=en_units,
589 yerr_op='constant', yerr_val=1, yerr_arr=None,
590 yrpop='', yrop='/', yref1='', yref2='',
591 is_trans=False,
592 has_yref=False, dtc_config={}, multicol_config={})
593 if config is not None:
594 self.config.update(config)
595 dtype = config.get('datatype', None)
596 if dtype in ('xas', 'raw'):
597 self.workgroup.datatype = dtype
599 if self.config['yarr2'] is None and 'i0' in self.array_labels:
600 self.config['yarr2'] = 'i0'
602 if self.config['yarr1'] is None:
603 if 'itrans' in self.array_labels:
604 self.config['yarr1'] = 'itrans'
605 elif 'i1' in self.array_labels:
606 self.config['yarr1'] = 'i1'
608 if self.config['yref1'] is None:
609 if 'iref' in self.array_labels:
610 self.config['yref1'] = 'iref'
611 elif 'irefer' in self.array_labels:
612 self.config['yref1'] = 'irefer'
613 elif 'i2' in self.array_labels:
614 self.config['yref1'] = 'i2'
616 if self.config['yref2'] is None and 'i1' in self.array_labels:
617 self.config['yref2'] = 'i1'
619 message = "Data Columns for %s" % group.filename
620 wx.Frame.__init__(self, None, -1,
621 'Build Arrays from Data Columns for %s' % group.filename,
622 style=FRAMESTYLE)
624 x0, y0 = parent.GetPosition()
625 self.SetPosition((x0+60, y0+60))
627 self.SetFont(Font(FONTSIZE))
628 panel = wx.Panel(self)
629 self.SetMinSize((725, 700))
630 self.colors = GUIColors()
632 def subtitle(s, fontsize=12, colour=wx.Colour(10, 10, 180)):
633 return SimpleText(panel, s, font=Font(fontsize),
634 colour=colour, style=LEFT)
636 # title row
637 title = subtitle(message, colour=self.colors.title)
639 yarr_labels = self.yarr_labels = self.array_labels + ['1.0', '']
640 xarr_labels = self.xarr_labels = self.array_labels + ['_index']
642 self.xarr = Choice(panel, choices=xarr_labels, action=self.onXSelect, size=(150, -1))
643 self.yarr1 = Choice(panel, choices= self.array_labels, action=self.onUpdate, size=(150, -1))
644 self.yarr2 = Choice(panel, choices=yarr_labels, action=self.onUpdate, size=(150, -1))
645 self.yerr_arr = Choice(panel, choices=yarr_labels, action=self.onUpdate, size=(150, -1))
646 self.yerr_arr.Disable()
648 self.datatype = Choice(panel, choices=DATATYPES, action=self.onUpdate, size=(150, -1))
649 self.datatype.SetStringSelection(self.workgroup.datatype)
651 self.en_units = Choice(panel, choices=ENUNITS_TYPES,
652 action=self.onEnUnitsSelect, size=(150, -1))
654 self.ypop = Choice(panel, choices=YPRE_OPS, action=self.onUpdate, size=(100, -1))
655 self.yop = Choice(panel, choices=ARR_OPS, action=self.onUpdate, size=(100, -1))
656 self.yerr_op = Choice(panel, choices=YERR_OPS, action=self.onYerrChoice, size=(100, -1))
657 self.yerr_op.SetSelection(0)
659 self.is_trans = Check(panel, label='is transmission data?',
660 default=self.config['is_trans'],
661 action=self.onTransCheck)
663 self.yerr_val = FloatCtrl(panel, value=1, precision=4, size=(75, -1))
664 self.monod_val = FloatCtrl(panel, value=3.1355316, precision=7, size=(75, -1))
666 xlab = SimpleText(panel, ' X array = ')
667 ylab = SimpleText(panel, ' Y array = ')
668 units_lab = SimpleText(panel, ' Units of X array: ')
669 yerr_lab = SimpleText(panel, ' Y uncertainty = ')
670 dtype_lab = SimpleText(panel, ' Data Type: ')
671 monod_lab = SimpleText(panel, ' Mono D spacing (Ang): ')
672 yerrval_lab = SimpleText(panel, ' Value:')
673 self.info_message = subtitle(' ', colour=wx.Colour(100, 10, 10))
675 # yref
676 self.has_yref = Check(panel, label='data file includes energy reference data',
677 default=self.config['has_yref'],
678 action=self.onYrefCheck)
679 refylab = SimpleText(panel, ' Refer array = ')
680 self.yref1 = Choice(panel, choices=yarr_labels, action=self.onUpdate, size=(150, -1))
681 self.yref2 = Choice(panel, choices=yarr_labels, action=self.onUpdate, size=(150, -1))
682 self.yrpop = Choice(panel, choices=YPRE_OPS, action=self.onUpdate, size=(100, -1))
683 self.yrop = Choice(panel, choices=ARR_OPS, action=self.onUpdate, size=(100, -1))
685 self.ysuf = SimpleText(panel, '')
686 self.ypop.SetStringSelection(self.config['ypop'])
687 self.yop.SetStringSelection(self.config['yop'])
688 self.yrpop.SetStringSelection(self.config['yrpop'])
689 self.yrop.SetStringSelection(self.config['yrop'])
690 self.monod_val.SetValue(self.config['monod'])
691 self.monod_val.SetAction(self.onUpdate)
692 self.monod_val.Enable(self.config['en_units'].startswith('deg'))
693 self.en_units.SetStringSelection(self.config['en_units'])
694 self.yerr_op.SetStringSelection(self.config['yerr_op'])
695 self.yerr_val.SetValue(self.config['yerr_val'])
696 if '(' in self.config['ypop']:
697 self.ysuf.SetLabel(')')
699 ixsel, iysel = 0, 1
700 iy2sel = iyesel = iyr1sel = iyr2sel = len(yarr_labels)-1
701 if self.config['xarr'] in xarr_labels:
702 ixsel = xarr_labels.index(self.config['xarr'])
703 if self.config['yarr1'] in self.array_labels:
704 iysel = self.array_labels.index(self.config['yarr1'])
705 if self.config['yarr2'] in yarr_labels:
706 iy2sel = yarr_labels.index(self.config['yarr2'])
707 if self.config['yerr_arr'] in yarr_labels:
708 iyesel = yarr_labels.index(self.config['yerr_arr'])
709 if self.config['yref1'] in self.array_labels:
710 iyr1sel = self.array_labels.index(self.config['yref1'])
711 if self.config['yref2'] in self.array_labels:
712 iyr2sel = self.array_labels.index(self.config['yref2'])
714 self.xarr.SetSelection(ixsel)
715 self.yarr1.SetSelection(iysel)
716 self.yarr2.SetSelection(iy2sel)
717 self.yerr_arr.SetSelection(iyesel)
718 self.yref1.SetSelection(iyr1sel)
719 self.yref2.SetSelection(iyr2sel)
721 self.wid_filename = wx.TextCtrl(panel, value=fix_filename(group.filename),
722 size=(250, -1))
723 self.wid_groupname = wx.TextCtrl(panel, value=group.groupname,
724 size=(150, -1))
725 if not edit_groupname:
726 self.wid_groupname.Disable()
727 self.wid_reffilename = wx.TextCtrl(panel, value=fix_filename(group.filename + '_ref'),
728 size=(250, -1))
729 self.wid_refgroupname = wx.TextCtrl(panel, value=group.groupname + '_ref',
730 size=(150, -1))
732 self.onTransCheck(is_trans=self.config['is_trans'])
733 self.onYrefCheck(has_yref=self.config['has_yref'])
736 bpanel = wx.Panel(panel)
737 bsizer = wx.BoxSizer(wx.HORIZONTAL)
738 _ok = Button(bpanel, 'OK', action=self.onOK)
739 _cancel = Button(bpanel, 'Cancel', action=self.onCancel)
740 _edit = Button(bpanel, 'Edit Array Names', action=self.onEditNames)
741 self.multi_sel = Button(bpanel, 'Select Multilple Columns', action=self.onMultiColumn)
742 self.multi_clear = Button(bpanel, 'Clear Multiple Columns', action=self.onClearMultiColumn)
743 self.multi_clear.Disable()
744 _edit.SetToolTip('Change the current Column Names')
746 self.multi_sel.SetToolTip('Select Multiple Columns to import as separate groups')
747 self.multi_clear.SetToolTip('Clear Multiple Column Selection')
748 bsizer.Add(_ok)
749 bsizer.Add(_cancel)
750 bsizer.Add(_edit)
751 bsizer.Add(self.multi_sel)
752 bsizer.Add(self.multi_clear)
753 _ok.SetDefault()
754 pack(bpanel, bsizer)
756 self.dtc_button = Button(panel, 'Sum and Correct Fluoresence Data', action=self.onDTC)
757 self.dtc_button.SetToolTip('Select channels and do deadtime-corrections for multi-element fluorescence data')
759 sizer = wx.GridBagSizer(2, 2)
760 sizer.Add(title, (0, 0), (1, 7), LEFT, 5)
762 ir = 1
763 sizer.Add(subtitle(' X [Energy] Array:'), (ir, 0), (1, 2), LEFT, 0)
764 sizer.Add(dtype_lab, (ir, 3), (1, 1), RIGHT, 0)
765 sizer.Add(self.datatype, (ir, 4), (1, 2), LEFT, 0)
767 ir += 1
768 sizer.Add(xlab, (ir, 0), (1, 1), LEFT, 0)
769 sizer.Add(self.xarr, (ir, 1), (1, 2), LEFT, 0)
770 sizer.Add(units_lab, (ir, 3), (1, 1), RIGHT, 0)
771 sizer.Add(self.en_units, (ir, 4), (1, 2), LEFT, 0)
773 ir += 1
774 sizer.Add(monod_lab, (ir, 2), (1, 2), RIGHT, 0)
775 sizer.Add(self.monod_val,(ir, 4), (1, 1), LEFT, 0)
777 ir += 1
778 sizer.Add(subtitle(' Y [\u03BC(E)] Array:'), (ir, 0), (1, 1), LEFT, 0)
779 sizer.Add(self.is_trans, (ir, 1), (1, 2), LEFT, 0)
780 sizer.Add(self.dtc_button, (ir, 3), (1, 2), RIGHT, 0)
781 ir += 1
782 sizer.Add(ylab, (ir, 0), (1, 1), LEFT, 0)
783 sizer.Add(self.ypop, (ir, 1), (1, 1), LEFT, 0)
784 sizer.Add(self.yarr1, (ir, 2), (1, 1), LEFT, 0)
785 sizer.Add(self.yop, (ir, 3), (1, 1), RIGHT, 0)
786 sizer.Add(self.yarr2, (ir, 4), (1, 1), LEFT, 0)
787 sizer.Add(self.ysuf, (ir, 5), (1, 1), LEFT, 0)
790 ir += 1
791 sizer.Add(yerr_lab, (ir, 0), (1, 1), LEFT, 0)
792 sizer.Add(self.yerr_op, (ir, 1), (1, 1), LEFT, 0)
793 sizer.Add(self.yerr_arr, (ir, 2), (1, 1), LEFT, 0)
794 sizer.Add(yerrval_lab, (ir, 3), (1, 1), RIGHT, 0)
795 sizer.Add(self.yerr_val, (ir, 4), (1, 2), LEFT, 0)
797 ir += 1
798 sizer.Add(SimpleText(panel, ' Display Name:'), (ir, 0), (1, 1), LEFT, 0)
799 sizer.Add(self.wid_filename, (ir, 1), (1, 2), LEFT, 0)
800 sizer.Add(SimpleText(panel, ' Group Name:'), (ir, 3), (1, 1), RIGHT, 0)
801 sizer.Add(self.wid_groupname, (ir, 4), (1, 2), LEFT, 0)
803 ir += 1
804 sizer.Add(self.info_message, (ir, 0), (1, 5), LEFT, 1)
806 ir += 2
807 sizer.Add(subtitle(' Reference [\u03BC_ref(E)] Array: '),
808 (ir, 0), (1, 2), LEFT, 0)
809 sizer.Add(self.has_yref, (ir, 2), (1, 3), LEFT, 0)
811 ir += 1
812 sizer.Add(refylab, (ir, 0), (1, 1), LEFT, 0)
813 sizer.Add(self.yrpop, (ir, 1), (1, 1), LEFT, 0)
814 sizer.Add(self.yref1, (ir, 2), (1, 1), LEFT, 0)
815 sizer.Add(self.yrop, (ir, 3), (1, 1), RIGHT, 0)
816 sizer.Add(self.yref2, (ir, 4), (1, 2), LEFT, 0)
818 ir += 1
819 sizer.Add(SimpleText(panel, ' Reference Name:'), (ir, 0), (1, 1), LEFT, 0)
820 sizer.Add(self.wid_reffilename, (ir, 1), (1, 2), LEFT, 0)
821 sizer.Add(SimpleText(panel, ' Group Name:'), (ir, 3), (1, 1), RIGHT, 0)
822 sizer.Add(self.wid_refgroupname, (ir, 4), (1, 2), LEFT, 0)
824 ir +=1
825 sizer.Add(bpanel, (ir, 0), (1, 5), LEFT, 3)
827 pack(panel, sizer)
829 self.nb = fnb.FlatNotebook(self, -1, agwStyle=FNB_STYLE)
830 self.nb.SetTabAreaColour(wx.Colour(248,248,240))
831 self.nb.SetActiveTabColour(wx.Colour(254,254,195))
832 self.nb.SetNonActiveTabTextColour(wx.Colour(40,40,180))
833 self.nb.SetActiveTabTextColour(wx.Colour(80,0,0))
835 self.plotpanel = PlotPanel(self, messenger=self.plot_messages)
836 try:
837 plotopts = self._larch.symtable._sys.wx.plotopts
838 self.plotpanel.conf.set_theme(plotopts['theme'])
839 self.plotpanel.conf.enable_grid(plotopts['show_grid'])
840 except:
841 pass
844 self.plotpanel.SetMinSize((200, 200))
845 textpanel = wx.Panel(self)
846 ftext = wx.TextCtrl(textpanel, style=wx.TE_MULTILINE|wx.TE_READONLY,
847 size=(400, 250))
849 ftext.SetValue(group.text)
850 ftext.SetFont(Font(FONTSIZE))
852 textsizer = wx.BoxSizer(wx.VERTICAL)
853 textsizer.Add(ftext, 1, LEFT|wx.GROW, 1)
854 pack(textpanel, textsizer)
856 self.nb.AddPage(textpanel, ' Text of Data File ', True)
857 self.nb.AddPage(self.plotpanel, ' Plot of Selected Arrays ', True)
859 mainsizer = wx.BoxSizer(wx.VERTICAL)
860 mainsizer.Add(panel, 0, wx.GROW|wx.ALL, 2)
861 mainsizer.Add(self.nb, 1, LEFT|wx.GROW, 2)
862 pack(self, mainsizer)
864 self.statusbar = self.CreateStatusBar(2, 0)
865 self.statusbar.SetStatusWidths([-1, -1])
866 statusbar_fields = [group.filename, ""]
867 for i in range(len(statusbar_fields)):
868 self.statusbar.SetStatusText(statusbar_fields[i], i)
870 self.set_energy_units()
871 dtc_conf = self.config.get('dtc_config', {})
872 if len(dtc_conf) > 0:
873 self.onDTC_OK(dtc_conf, update=False)
875 self.Show()
876 self.Raise()
877 self.onUpdate()
879 def onDTC(self, event=None):
880 self.show_subframe('dtc_conf', DeadtimeCorrectionFrame,
881 config=self.config['dtc_config'],
882 group=self.workgroup,
883 on_ok=self.onDTC_OK)
885 def onDTC_OK(self, config, update=True, **kws):
886 label, sum = sum_fluor_channels(self.workgroup, config['roi'],
887 icr=config['icr'],
888 ocr=config['ocr'],
889 ltime=config['ltime'],
890 add_data=False)
891 if sum is None:
892 return
893 self.info_message.SetLabel(f"Added array '{label}' with summed and corrected fluorecence data")
894 self.workgroup.array_labels.append(label)
895 self.set_array_labels(self.workgroup.array_labels)
896 npts = len(sum)
897 new = np.append(self.workgroup.raw.data, sum.reshape(1, npts), axis=0)
898 self.workgroup.raw.data = new[()]
899 self.workgroup.data = new[()]
900 self.yarr1.SetStringSelection(label)
901 self.config['dtc_config'] = config
902 if update:
903 self.onUpdate()
905 def onClearMultiColumn(self, event=None):
906 self.config['multicol_config'] = {}
907 self.info_message.SetLabel(f" cleared reading of multiple columns")
908 self.multi_clear.Disable()
909 self.yarr1.Enable()
910 self.ypop.Enable()
911 self.yop.Enable()
912 self.onUpdate()
915 def onMultiColumn(self, event=None):
916 self.show_subframe('multicol', MultiColumnFrame,
917 config=self.config['multicol_config'],
918 group=self.workgroup,
919 on_ok=self.onMultiColumn_OK)
922 def onMultiColumn_OK(self, config, update=True, **kws):
923 chans = config.get('channels', [])
924 if len(chans) == 0:
925 self.config['multicol_config'] = {}
926 else:
927 self.config['multicol_config'] = config
928 self.yarr1.SetSelection(chans[0])
929 self.yarr2.SetSelection(config['i0'])
930 self.ypop.SetStringSelection('')
931 self.yarr1.Disable()
932 self.ypop.Disable()
933 self.yop.Disable()
934 y2 = self.yarr2.GetStringSelection()
935 msg = f" Will import {len(config['channels'])} Y arrays, divided by '{y2}'"
936 self.info_message.SetLabel(msg)
937 self.multi_clear.Enable()
938 if update:
939 self.onUpdate()
942 def read_column_file(self, path):
943 """read column file, generally as initial read"""
944 parent, filename = os.path.split(path)
945 reader, text = guess_filereader(path, return_text=True)
947 if reader == 'read_specfile':
948 if not is_specfile(path, require_multiple_scans=True):
949 reader = 'read_ascii'
951 if reader in ('read_xdi', 'read_gsexdi'):
952 # first check for Nans and Infs
953 nan_result = look_for_nans(path)
954 if 'read error' in nan_result.message:
955 title = "Cannot read %s" % path
956 message = "Error reading %s\n%s" %(path, nan_result.message)
957 r = Popup(self.parent, message, title)
958 return None
959 if 'no data' in nan_result.message:
960 title = "No data in %s" % path
961 message = "No data found in file %s" % path
962 r = Popup(self.parent, message, title)
963 return None
965 if ('has nans' in nan_result.message or
966 'has infs' in nan_result.message):
967 reader = 'read_ascii'
969 tmpname = '_tmpfile_'
970 read_cmd = "%s = %s('%s')" % (tmpname, reader, path)
971 self.reader = reader
972 _larch = self._larch
974 if (not isinstance(_larch, larch.Interpreter) and
975 hasattr(_larch, '_larch')):
976 _larch = _larch._larch
977 try:
978 _larch.eval(read_cmd, add_history=True)
979 except:
980 pass
981 if len(_larch.error) > 0 and reader in ('read_xdi', 'read_gsexdi'):
982 read_cmd = "%s = %s('%s')" % (tmpname, 'read_ascii', path)
983 try:
984 _larch.eval(read_cmd, add_history=True)
985 except:
986 pass
987 if len(_larch.error) == 0:
988 self.reader = 'read_ascii'
990 if len(_larch.error) > 0:
991 msg = ["Error trying to read '%s':" % path, ""]
992 for err in _larch.error:
993 exc_name, errmsg = err.get_error()
994 msg.append(errmsg)
996 title = "Cannot read %s" % path
997 r = Popup(self.parent, "\n".join(msg), title)
998 return None
999 group = deepcopy(_larch.symtable.get_symbol(tmpname))
1000 _larch.symtable.del_symbol(tmpname)
1002 group.text = text
1003 group.path = path
1004 group.filename = filename
1005 group.groupname = file2groupname(filename,
1006 symtable=self._larch.symtable)
1007 return group
1009 def show_subframe(self, name, frameclass, **opts):
1010 shown = False
1011 if name in self.subframes:
1012 try:
1013 self.subframes[name].Raise()
1014 shown = True
1015 except:
1016 pass
1017 if not shown:
1018 self.subframes[name] = frameclass(self, **opts)
1019 self.subframes[name].Show()
1020 self.subframes[name].Raise()
1023 def onEditNames(self, evt=None):
1024 self.show_subframe('editcol', EditColumnFrame,
1025 group=self.workgroup,
1026 on_ok=self.set_array_labels)
1028 def set_array_labels(self, arr_labels):
1029 self.workgroup.array_labels = arr_labels
1030 yarr_labels = self.yarr_labels = arr_labels + ['1.0', '']
1031 xarr_labels = self.xarr_labels = arr_labels + ['_index']
1032 def update(wid, choices):
1033 curstr = wid.GetStringSelection()
1034 curind = wid.GetSelection()
1035 wid.SetChoices(choices)
1036 if curstr in choices:
1037 wid.SetStringSelection(curstr)
1038 else:
1039 wid.SetSelection(curind)
1040 update(self.xarr, xarr_labels)
1041 update(self.yarr1, yarr_labels)
1042 update(self.yarr2, yarr_labels)
1043 update(self.yerr_arr, yarr_labels)
1044 self.onUpdate()
1046 def onOK(self, event=None):
1047 """ build arrays according to selection """
1048 self.read_form()
1049 cout = create_arrays(self.workgroup, **self.config)
1050 self.config.update(cout)
1051 conf = self.config
1052 if self.ypop.Enabled: #not using multicolumn mode
1053 conf['multicol_config'] = {'channels': [], 'i0': conf['iy2']}
1055 self.expressions = conf['expressions']
1056 filename = conf['user_filename']
1057 groupname = conf['groupname']
1058 conf['array_labels'] = self.workgroup.array_labels
1060 # generate script to pass back to calling program:
1061 labstr = ', '.join(self.array_labels)
1062 buff = [f"{{group}} = {self.reader}('{{path}}', labels='{labstr}')",
1063 "{group}.path = '{path}'",
1064 "{group}.is_frozen = False",
1065 "{group}.energy_ref = '{group}'"]
1067 dtc_conf = conf.get('dtc_config', {})
1068 if len(dtc_conf) > 0:
1069 sumcmd = "sum_fluor_channels({{group}}, {roi}, icr={icr}, ocr={ocr}, ltime={ltime})"
1070 buff.append(sumcmd.format(**dtc_conf))
1072 buff.append("{group}.datatype = '%s'" % (conf['datatype']))
1074 for attr in ('plot_xlabel', 'plot_ylabel'):
1075 val = getattr(self.workgroup, attr)
1076 buff.append("{group}.%s = '%s'" % (attr, val))
1078 xexpr = self.expressions['xdat']
1079 en_units = conf['en_units']
1080 if en_units.startswith('deg'):
1081 monod = conf['monod']
1082 buff.append(f"monod = {monod:.9f}")
1083 buff.append(f"{{group}}.xdat = PLANCK_HC/(2*monod*sin(DEG2RAD*({xexpr:s})))")
1084 elif en_units.startswith('keV'):
1085 buff.append(f"{{group}}.xdat = 1000.0*{xexpr:s}")
1086 else:
1087 buff.append(f"{{group}}.xdat = {xexpr:s}")
1089 for aname in ('ydat', 'yerr'):
1090 expr = self.expressions[aname]
1091 buff.append(f"{{group}}.{aname} = {expr}")
1093 if getattr(self.workgroup, 'datatype', 'raw') == 'xas':
1094 if self.reader == 'read_gsescan':
1095 buff.append("{group}.xdat = {group}.x")
1096 buff.append("{group}.energy = {group}.xdat")
1097 buff.append("{group}.mu = {group}.ydat")
1098 buff.append("sort_xafs({group}, overwrite=True, fix_repeats=True)")
1099 else:
1100 buff.append("{group}.scale = 1./({group}.ydat.ptp()+1.e-15)")
1102 array_desc = dict(xdat=self.workgroup.plot_xlabel,
1103 ydat=self.workgroup.plot_ylabel,
1104 yerr=self.expressions['yerr'])
1106 reffile = refgroup = None
1107 if conf['has_yref']:
1108 reffile = conf['reffile']
1109 refgroup = conf['refgroup']
1110 refexpr = self.expressions['yref']
1111 array_desc['yref'] = getattr(self.workgroup, 'yrlabel', 'reference')
1113 buff.append("# reference group")
1114 buff.append("{refgroup} = deepcopy({group})")
1115 buff.append(f"{{refgroup}}.ydat = {{refgroup}}.mu = {refexpr}")
1116 buff.append(f"{{refgroup}}.plot_ylabel = '{self.workgroup.yrlabel}'")
1117 buff.append("{refgroup}.energy_ref = {group}.energy_ref = '{refgroup}'")
1118 buff.append("# end reference group")
1120 script = "\n".join(buff)
1121 conf['array_desc'] = array_desc
1123 if self.read_ok_cb is not None:
1124 self.read_ok_cb(script, self.path, conf)
1126 for f in self.subframes.values():
1127 try:
1128 f.Destroy()
1129 except:
1130 pass
1131 self.Destroy()
1133 def onCancel(self, event=None):
1134 self.workgroup.import_ok = False
1135 for f in self.subframes.values():
1136 try:
1137 f.Destroy()
1138 except:
1139 pass
1140 self.Destroy()
1142 def onYerrChoice(self, evt=None):
1143 yerr_choice = evt.GetString()
1144 self.yerr_arr.Disable()
1145 self.yerr_val.Disable()
1146 if 'const' in yerr_choice.lower():
1147 self.yerr_val.Enable()
1148 elif 'array' in yerr_choice.lower():
1149 self.yerr_arr.Enable()
1150 # self.onUpdate()
1152 def onTransCheck(self, evt=None, is_trans=False):
1153 if evt is not None:
1154 is_trans = evt.IsChecked()
1155 if is_trans:
1156 self.ypop.SetStringSelection('-log(')
1157 else:
1158 self.ypop.SetStringSelection('')
1159 try:
1160 self.onUpdate()
1161 except:
1162 pass
1164 def onYrefCheck(self, evt=None, has_yref=False):
1165 if evt is not None:
1166 has_yref = evt.IsChecked()
1167 self.yref1.Enable(has_yref)
1168 self.yref2.Enable(has_yref)
1169 self.yrpop.Enable(has_yref)
1170 self.yrop.Enable(has_yref)
1171 self.wid_reffilename.Enable(has_yref)
1172 self.wid_refgroupname.Enable(has_yref)
1175 def onXSelect(self, evt=None):
1176 ix = self.xarr.GetSelection()
1177 xname = self.xarr.GetStringSelection()
1179 workgroup = self.workgroup
1180 ncol, npts = self.workgroup.data.shape
1181 if xname.startswith('_index') or ix >= ncol:
1182 workgroup.xdat = 1.0*np.arange(npts)
1183 else:
1184 workgroup.xdat = 1.0*self.workgroup.data[ix, :]
1185 self.onUpdate()
1187 self.monod_val.Disable()
1188 if self.datatype.GetStringSelection().strip().lower() == 'raw':
1189 self.en_units.SetSelection(4)
1190 else:
1191 eguess = guess_energy_units(workgroup.xdat)
1192 if eguess.startswith('keV'):
1193 self.en_units.SetSelection(1)
1194 elif eguess.startswith('deg'):
1195 self.en_units.SetSelection(2)
1196 self.monod_val.Enable()
1197 else:
1198 self.en_units.SetSelection(0)
1200 def onEnUnitsSelect(self, evt=None):
1201 self.monod_val.Enable(self.en_units.GetStringSelection().startswith('deg'))
1202 self.onUpdate()
1204 def set_energy_units(self):
1205 ix = self.xarr.GetSelection()
1206 xname = self.xarr.GetStringSelection()
1207 workgroup = self.workgroup
1208 ncol, npts = workgroup.data.shape
1210 if xname.startswith('_index') or ix >= ncol:
1211 workgroup.xdat = 1.0*np.arange(npts)
1212 else:
1213 workgroup.xdat = 1.0*self.workgroup.data[ix, :]
1214 if self.datatype.GetStringSelection().strip().lower() != 'raw':
1215 eguess = guess_energy_units(workgroup.xdat)
1216 if eguess.startswith('eV'):
1217 self.en_units.SetStringSelection('eV')
1218 elif eguess.startswith('keV'):
1219 self.en_units.SetStringSelection('keV')
1221 def read_form(self, **kws):
1222 """return form configuration"""
1223 datatype = self.datatype.GetStringSelection().strip().lower()
1224 if self.workgroup.datatype == 'raw' and datatype == 'xas':
1225 self.workgroup.datatype = 'xas'
1226 eguess = guess_energy_units(self.workgroup.xdat)
1227 if eguess.startswith('keV'):
1228 self.en_units.SetSelection(1)
1229 elif eguess.startswith('deg'):
1230 self.en_units.SetSelection(2)
1231 self.monod_val.Enable()
1232 else:
1233 self.en_units.SetSelection(0)
1234 if datatype == 'raw':
1235 self.en_units.SetStringSelection('not energy')
1237 conf = {'datatype': datatype,
1238 'ix': self.xarr.GetSelection(),
1239 'xarr': self.xarr.GetStringSelection(),
1240 'en_units': self.en_units.GetStringSelection(),
1241 'monod': float(self.monod_val.GetValue()),
1242 'yarr1': self.yarr1.GetStringSelection().strip(),
1243 'yarr2': self.yarr2.GetStringSelection().strip(),
1244 'iy1': self.yarr1.GetSelection(),
1245 'iy2': self.yarr2.GetSelection(),
1246 'yop': self.yop.GetStringSelection().strip(),
1247 'ypop': self.ypop.GetStringSelection().strip(),
1248 'iyerr': self.yerr_arr.GetSelection(),
1249 'yerr_arr': self.yerr_arr.GetStringSelection(),
1250 'yerr_op': self.yerr_op.GetStringSelection().lower(),
1251 'yerr_val': self.yerr_val.GetValue(),
1252 'has_yref': self.has_yref.IsChecked(),
1253 'yref1': self.yref1.GetStringSelection().strip(),
1254 'yref2': self.yref2.GetStringSelection().strip(),
1255 'iry1': self.yref1.GetSelection(),
1256 'iry2': self.yref2.GetSelection(),
1257 'yrpop': self.yrpop.GetStringSelection().strip(),
1258 'yrop': self.yop.GetStringSelection().strip(),
1259 'user_filename': self.wid_filename.GetValue(),
1260 'groupname': fix_varname(self.wid_groupname.GetValue()),
1261 'reffile': self.wid_reffilename.GetValue(),
1262 'refgroup': fix_varname(self.wid_refgroupname.GetValue()),
1263 }
1264 self.config.update(conf)
1265 return conf
1267 def onUpdate(self, evt=None, **kws):
1268 """column selections changed calc xdat and ydat"""
1269 workgroup = self.workgroup
1270 try:
1271 ncol, npts = self.workgroup.data.shape
1272 except:
1273 return
1275 conf = self.read_form()
1276 cout = create_arrays(workgroup, **conf)
1277 self.expressions = cout.pop('expressions')
1278 conf.update(cout)
1280 if energy_may_need_rebinning(workgroup):
1281 self.info_message.SetLabel("Warning: XAS data may need to be rebinned!")
1283 path, fname = os.path.split(workgroup.filename)
1284 popts = dict(marker='o', markersize=4, linewidth=1.5, title=fname,
1285 xlabel=workgroup.plot_xlabel,
1286 ylabel=workgroup.plot_ylabel,
1287 label=workgroup.plot_ylabel)
1289 self.plotpanel.plot(workgroup.xdat, workgroup.ydat, **popts)
1290 if conf['has_yref']:
1291 yrlabel = getattr(workgroup, 'plot_yrlabel', 'reference')
1292 self.plotpanel.oplot(workgroup.xdat, workgroup.yref,
1293 y2label=yrlabel,
1294 linewidth=2.0, color='#E08070',
1295 label=yrlabel, zorder=-40, side='right')
1297 for i in range(self.nb.GetPageCount()):
1298 if 'plot' in self.nb.GetPageText(i).lower():
1299 self.nb.SetSelection(i)
1301 def plot_messages(self, msg, panel=1):
1302 self.statusbar.SetStatusText(msg, panel)
1305def create_arrays(dgroup, datatype='xas', ix=0, xarr='energy', en_units='eV',
1306 monod=3.1355316, yarr1=None, yarr2=None, iy1=2, iy2=1, yop='/',
1307 ypop='', iyerr=5, yerr_arr=None, yerr_op='constant', yerr_val=1.0,
1308 has_yref=False, yref1=None, yref2=None, iry1=3, iry2=2,
1309 yrpop='', yrop='/', **kws):
1310 """
1311 build arrays and values for datagroup based on configuration as from ColumnFile
1312 """
1313 ncol, npts = dgroup.data.shape
1314 exprs = dict(xdat=None, ydat=None, yerr=None, yref=None)
1316 # print("CREATE ARRAYS ", dgroup, datatype, ncol, npts)
1317 if not hasattr(dgroup, 'index'):
1318 dgroup.index = 1.0*np.arange(npts)
1320 if xarr.startswith('_index') or ix >= ncol:
1321 dgroup.xdat = 1.0*np.arange(npts)
1322 xarr = '_index'
1323 exprs['xdat'] = 'arange({npts})'
1324 else:
1325 dgroup.xdat = 1.0*dgroup.data[ix, :]
1326 exprs['xdat'] = '{group}.data[{ix}, : ]'
1328 xlabel = xarr
1329 monod = float(monod)
1330 if en_units.startswith('deg'):
1331 dgroup.xdat = PLANCK_HC/(2*monod*np.sin(DEG2RAD*dgroup.xdat))
1332 xlabel = xarr + ' (eV)'
1333 elif en_units.startswith('keV'):
1334 dgroup.xdat *= 1000.0
1335 xlabel = xarr + ' (eV)'
1337 def pre_op(opstr, arr):
1338 if opstr == '-':
1339 return '', opstr, -arr
1340 suf = ''
1341 if opstr in ('-log(', 'log('):
1342 suf = ')'
1343 arr = safe_log(arr)
1344 if opstr.startswith('-'): arr = -arr
1345 arr[np.where(np.isnan(arr))] = 0
1346 return suf, opstr, arr
1348 if yarr1 is None:
1349 yarr1 = dgroup.array_labels[iy1]
1351 if yarr2 is None:
1352 yarr2 = dgroup.array_labels[iy2]
1354 ylabel = yarr1
1355 if len(yarr2) == 0:
1356 yarr2 = '1.0'
1357 else:
1358 ylabel = f"{ylabel}{yop}{yarr2}"
1360 if yarr1 == '0.0':
1361 ydarr1 = np.zeros(npts)*1.0
1362 yexpr1 = f'np.zeros(npts)'
1363 elif len(yarr1) == 0 or yarr1 == '1.0' or iy1 >= ncol:
1364 ydarr1 = np.ones(npts)*1.0
1365 yexpr1 = f'np.ones({npts})'
1366 else:
1367 ydarr1 = dgroup.data[iy1, :]
1368 yexpr1 = '{group}.data[{iy1}, : ]'
1370 dgroup.ydat = ydarr1
1371 exprs['ydat'] = yexpr1
1373 if yarr2 == '0.0':
1374 ydarr2 = np.zeros(npts)*1.0
1375 yexpr2 = '0.0'
1376 elif len(yarr2) == 0 or yarr2 == '1.0' or iy2 >= ncol:
1377 ydarr2 = np.ones(npts)*1.0
1378 yexpr2 = '1.0'
1379 else:
1380 ydarr2 = dgroup.data[iy2, :]
1381 yexpr2 = '{group}.data[{iy2}, : ]'
1383 if yop in ('+', '-', '*', '/'):
1384 exprs['ydat'] = f"{yexpr1}{yop}{yexpr2}"
1385 if yop == '+':
1386 dgroup.ydat = ydarr1 + ydarr2
1387 elif yop == '-':
1388 dgroup.ydat = ydarr1 - ydarr2
1389 elif yop == '*':
1390 dgroup.ydat = ydarr1 * ydarr2
1391 elif yop == '/':
1392 dgroup.ydat = ydarr1 / ydarr2
1394 ysuf, ypop, dgroup.ydat = pre_op(ypop, dgroup.ydat)
1395 ypopx = ypop.replace('log', 'safe_log')
1396 exprs['ydat'] = f"{ypopx}{exprs['ydat']}{ysuf}"
1397 ylabel = f"{ypop}{ylabel}{ysuf}"
1399 # error
1400 exprs['yerr'] = '1'
1401 if yerr_op.startswith('const'):
1402 yderr = yerr_val
1403 exprs['yerr'] = f"{yerr_val}"
1404 elif yerr_op.startswith('array'):
1405 yderr = dgroup.data[iyerr, :]
1406 exprs['yerr'] = '{group}.data[{iyerr}, :]'
1407 elif yerr_op.startswith('sqrt'):
1408 yderr = np.sqrt(dgroup.ydat)
1409 exprs['yerr'] = 'sqrt({group}.ydat)'
1411 # reference
1412 yrlabel = None
1413 if has_yref:
1414 yrlabel = yref1
1415 if len(yref2) == 0:
1416 yref2 = '1.0'
1417 else:
1418 yrlabel = f"{yrlabel}{yrop}{yref2}"
1420 if yref1 == '0.0':
1421 ydrarr1 = np.zeros(npts)*1.0
1422 yrexpr1 = 'zeros({npts})'
1423 elif len(yref1) == 0 or yref1 == '1.0' or iry1 >= ncol:
1424 ydrarr1 = np.ones(npts)*1.0
1425 yrexpr1 = 'ones({npts})'
1426 else:
1427 ydrarr1 = dgroup.data[iry1, :]
1428 yrexpr1 = '{group}.data[{iry1}, : ]'
1430 dgroup.yref = ydrarr1
1431 exprs['yref'] = yrexpr1
1433 if yref2 == '0.0':
1434 ydrarr2 = np.zeros(npts)*1.0
1435 ydrexpr2 = '0.0'
1436 elif len(yref2) == 0 or yref2 == '1.0' or iry2 >= ncol:
1437 ydrarr2 = np.ones(npts)*1.0
1438 yrexpr2 = '1.0'
1439 else:
1440 ydrarr2 = dgroup.data[iry2, :]
1441 yrexpr2 = '{group}.data[{iry2}, : ]'
1443 if yrop in ('+', '-', '*', '/'):
1444 exprs['yref'] = f'{yrexpr1} {yop} {yrexpr2}'
1445 if yrop == '+':
1446 dgroup.yref = ydrarr1 + ydrarr2
1447 elif yrop == '-':
1448 dgroup.yref = ydrarr1 - ydrarr2
1449 elif yrop == '*':
1450 dgroup.yref = ydrarr1 * ydarr2
1451 elif yrop == '/':
1452 dgroup.yref = ydrarr1 / ydrarr2
1454 yrsuf, yprop, dgroup.yref = pre_op(yrpop, dgroup.yref)
1455 yrpopx = yrpop.replace('log', 'safe_log')
1456 exprs['yref'] = f"{yrpopx}{exprs['yref']}{yrsuf}"
1457 yrlabel = f'{yrpop} {yrlabel} {yrsuf}'
1458 dgroup.yrlabel = yrlabel
1461 try:
1462 npts = min(len(dgroup.xdat), len(dgroup.ydat))
1463 except AttributeError:
1464 return
1465 except ValueError:
1466 return
1468 en = dgroup.xdat
1469 dgroup.datatype = datatype
1470 dgroup.npts = npts
1471 dgroup.plot_xlabel = xlabel
1472 dgroup.plot_ylabel = ylabel
1473 dgroup.xdat = np.array(dgroup.xdat[:npts])
1474 dgroup.ydat = np.array(dgroup.ydat[:npts])
1475 dgroup.y = dgroup.ydat
1476 dgroup.yerr = yderr
1477 if isinstance(yderr, np.ndarray):
1478 dgroup.yerr = np.array(yderr[:npts])
1479 if yrlabel is not None:
1480 dgroup.plot_yrlabel = yrlabel
1482 if dgroup.datatype == 'xas':
1483 dgroup.energy = dgroup.xdat
1484 dgroup.mu = dgroup.ydat
1486 return dict(xarr=xarr, ypop=ypop, yop=yop, yarr1=yarr1, yarr2=yarr2,
1487 monod=monod, en_units=en_units, yerr_op=yerr_op,
1488 yerr_val=yerr_val, yerr_arr=yerr_arr, yrpop=yrpop, yrop=yrop,
1489 yref1=yref1, yref2=yref2, has_yref=has_yref,
1490 expressions=exprs)
1492def energy_may_need_rebinning(workgroup):
1493 "test if energy may need rebinning"
1494 if getattr(workgroup, 'datatype', '?') != 'xas':
1495 return False
1496 en = getattr(workgroup, 'xdat', [-8.0e12])
1497 if len(en) < 2:
1498 return False
1499 if not isinstance(en, np.ndarray):
1500 en = np.array(en)
1501 if len(en) > 2000 or any(np.diff(en))< 0:
1502 return True
1503 if (len(en) > 200 and (max(en) - min(en)) > 350 and
1504 np.diff(en[:-100]).mean() < 1.0):
1505 return True