Coverage for larch/wxlib/columnframe.py: 7%
1128 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"""
4"""
5import re
6from copy import deepcopy
8import numpy as np
9np.seterr(all='ignore')
10from pathlib import Path
11from functools import partial
13import wx
14import wx.lib.scrolledpanel as scrolled
15import wx.lib.agw.flatnotebook as fnb
16from wxmplot import PlotPanel
18from wxutils import (SimpleText, FloatCtrl, FloatSpin, GUIColors, Button, Choice,
19 TextCtrl, pack, Popup, Check, MenuItem, CEN, RIGHT, LEFT,
20 FRAMESTYLE, HLine, Font)
22import larch
23from larch import Group
24from larch.xafs.xafsutils import guess_energy_units
25from larch.utils.strutils import fix_varname, fix_filename, file2groupname
26from larch.io import look_for_nans, guess_filereader, is_specfile, sum_fluor_channels
27from larch.utils.physical_constants import PLANCK_HC, DEG2RAD
28from larch.utils import gformat
29from larch.math import safe_log
30from . import FONTSIZE
32CEN |= wx.ALL
33FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS
34FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG
36YPRE_OPS = ('', 'log(', '-log(', '-')
37ARR_OPS = ('+', '-', '*', '/')
39YERR_OPS = ('Constant', 'Sqrt(Y)', 'Array')
40CONV_OPS = ('Lorenztian', 'Gaussian')
42DATATYPES = ('xydata', 'xas')
43ENUNITS_TYPES = ('eV', 'keV', 'degrees', 'not energy')
46MULTICHANNEL_TITLE = """ Sum MultiChannel Fluorescence Data, with Dead-Time Corrections:
47 To allow for many Dead-Time-Correction methods, each Channel is built as:
48 ROI_Corrected = ROI * ICR /(OCR * LTIME)
50 Set the Number of Channels, the Step (usually 1) between columns for
51 ROI 1, 2, ..., NChans, and any Bad Channels: a list of Channel numbers (start at 1).
53 Select columns for ROI (counts) and correction factors ICR, OCR, and LTIME for Channel 1.
55"""
57ROI_STEP_TOOLTIP = """number of columns between ROI columns -- typically 1 if the columns are like
58 ROI_Ch1 ROI_Ch2 ROI_Ch3 ... ICR_Ch1 ICR_Ch2 ICR_Ch3 ... OCR_Ch1 OCR_Ch2 OCR_Ch3 ...
60but set to 3 if the columns are arranged as
61 ROI_Ch1 ICR_Ch1 OCR_Ch1 ROI_Ch2 ICR_Ch2 OCR_Ch2 ROI_Ch3 ICR_Ch3 OCR_Ch3 ...
62"""
63MAXCHANS=2000
65class DeadtimeCorrectionFrame(wx.Frame):
66 """Manage MultiChannel Fluorescence Data"""
67 def __init__(self, parent, group, config=None, on_ok=None):
68 self.parent = parent
69 self.group = group
70 self.config = {'bad_chans': [], 'plot_chan': 1, 'nchans': 4, 'step': 1,
71 'roi': '1.0', 'icr': '1.0', 'ocr': '1.0',
72 'ltime': '1.0', 'i0': '1.0'}
73 # 'out_choice': 'summed spectrum',
74 if config is not None:
75 self.config.update(config)
76 self.arrays = {}
77 self.on_ok = on_ok
78 wx.Frame.__init__(self, None, -1, 'MultiChannel Fluorescence Data',
79 style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
81 self.SetFont(Font(FONTSIZE))
82 sizer = wx.GridBagSizer(2, 2)
83 panel = scrolled.ScrolledPanel(self)
85 self.SetMinSize((650, 450))
86 self.yarr_labels = [s for s in self.parent.yarr_labels]
87 wids = self.wids = {}
89 multi_title = wx.StaticText(panel, label=MULTICHANNEL_TITLE, size=(650, 150))
90 multi_title.SetFont(Font(FONTSIZE-1))
91 for s in ('roi', 'icr', 'ocr', 'ltime'):
92 wids[s] = Choice(panel, choices=self.yarr_labels, action=self.read_form, size=(150, -1))
93 sel = self.config.get(s, '1.0')
94 if sel == '1.0':
95 wids[s].SetStringSelection(sel)
96 else:
97 wids[s].SetSelection(sel[0])
98 wids[f'{s}_txt'] = SimpleText(panel, label='<list of column labels>', size=(275, -1))
100 wids['i0'] = Choice(panel, choices=self.yarr_labels, action=self.read_form, size=(150, -1))
101 wids['i0'].SetToolTip("All Channels will be divided by the I0 array")
103 wids['i0'].SetStringSelection(self.parent.yarr2.GetStringSelection())
105 wids['nchans'] = FloatCtrl(panel, value=self.config.get('nchans', 4),
106 precision=0, maxval=MAXCHANS, minval=1, size=(50, -1),
107 action=self.on_nchans)
108 wids['bad_chans'] = TextCtrl(panel, value='', size=(175, -1), action=self.read_form)
109 bad_chans = self.config.get('bad_chans', [])
110 if len(bad_chans) > 0:
111 wids['bad_chans'].SetValue(', '.join(['%d' % c for c in bad_chans]))
112 wids['bad_chans'].SetToolTip("List Channels to skip, separated by commas or spaces")
113 wids['step'] = FloatCtrl(panel, value=self.config.get('step', 1), precision=0,
114 maxval=MAXCHANS, minval=1, size=(50, -1), action=self.read_form)
115 wids['step'].SetToolTip(ROI_STEP_TOOLTIP)
117 wids['plot_chan'] = FloatSpin(panel, value=self.config.get('plot_chan', 1),
118 digits=0, increment=1, max_val=MAXCHANS, min_val=1, size=(50, -1),
119 action=self.onPlotThis)
121 wids['plot_this'] = Button(panel, 'Plot ROI + Correction For Channel', action=self.onPlotThis)
122 wids['plot_all'] = Button(panel, 'Plot All Channels', action=self.onPlotEach)
123 wids['plot_sum'] = Button(panel, 'Plot Sum of Channels', action=self.onPlotSum)
124 wids['save_btn'] = Button(panel, 'Use this Sum of Channels', action=self.onOK_DTC)
126 def tlabel(t):
127 return SimpleText(panel, label=t)
129 sizer.Add(multi_title, (0, 0), (2, 5), LEFT, 3)
130 ir = 2
131 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3)
133 ir += 1
134 sizer.Add(tlabel(' Number of Channels:'), (ir, 0), (1, 1), LEFT, 3)
135 sizer.Add(wids['nchans'], (ir, 1), (1, 1), LEFT, 3)
136 sizer.Add(tlabel(' Step between Channels:'), (ir, 2), (1, 1), LEFT, 3)
137 sizer.Add(wids['step'], (ir, 3), (1, 1), LEFT, 3)
139 ir += 1
140 sizer.Add(tlabel(' Bad Channels :'), (ir, 0), (1, 1), LEFT, 3)
141 sizer.Add(wids['bad_chans'], (ir, 1), (1, 2), LEFT, 3)
143 ir += 1
144 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3)
146 ir += 1
147 sizer.Add(tlabel(' Signal '), (ir, 0), (1, 1), LEFT, 3)
148 sizer.Add(tlabel(' Array for Channel #1 '), (ir, 1), (1, 1), LEFT, 3)
149 sizer.Add(tlabel(' Array Labels used for all Channels '), (ir, 2), (1, 3), LEFT, 3)
151 for s in ('roi', 'icr', 'ocr', 'ltime'):
152 ir += 1
153 sizer.Add(tlabel(f' {s.upper()} #1 : '), (ir, 0), (1, 1), LEFT, 3)
154 sizer.Add(wids[s], (ir, 1), (1, 1), LEFT, 3)
155 sizer.Add(wids[f'{s}_txt'], (ir, 2), (1, 3), LEFT, 3)
157 ir += 1
158 sizer.Add(tlabel(' I0 : '), (ir, 0), (1, 1), LEFT, 3)
159 sizer.Add(wids['i0'], (ir, 1), (1, 1), LEFT, 3)
161 ir += 1
162 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3)
164 ir += 1
165 sizer.Add(wids['plot_this'], (ir, 0), (1, 2), LEFT, 3)
166 sizer.Add(tlabel(' Channel:'), (ir, 2), (1, 1), LEFT, 3)
167 sizer.Add(wids['plot_chan'], (ir, 3), (1, 1), LEFT, 3)
169 ir += 1
170 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3)
171 ir += 1
172 sizer.Add(wids['plot_all'], (ir, 0), (1, 1), LEFT, 3)
173 sizer.Add(wids['plot_sum'], (ir, 1), (1, 1), LEFT, 3)
174 sizer.Add(wids['save_btn'], (ir, 2), (1, 2), LEFT, 3)
176 pack(panel, sizer)
177 panel.SetupScrolling()
179 mainsizer = wx.BoxSizer(wx.VERTICAL)
180 mainsizer.Add(panel, 1, wx.GROW|wx.ALL, 1)
182 pack(self, mainsizer)
183 self.Show()
184 self.Raise()
186 def get_en_i0(self):
187 en = self.group.xplot
188 i0 = 1.0
189 if self.config['i0'] != '1.0':
190 i0 = self.group.data[self.config['i0'], :]
191 return en, i0
193 def read_arrays(self, pchan):
194 def get_array(name, pchan, default=1.0):
195 out = default
196 if self.config[name] != '1.0':
197 ix = self.config[name][pchan-1]
198 if ix > 0:
199 out = self.group.data[ix, :]
200 return out
201 roi = get_array('roi', pchan, default=None)
202 icr = get_array('icr', pchan)
203 ocr = get_array('ocr', pchan)
204 ltime = get_array('ltime', pchan)
205 return roi, icr*ocr/ltime
207 def onOK_DTC(self, event=None):
208 self.read_form()
209 if callable(self.on_ok):
210 self.on_ok(self.config)
211 self.Destroy()
213 def onPlotSum(self, event=None):
214 self.read_form()
215 en, i0 = self.get_en_i0()
216 label, sum = sum_fluor_channels(self.group, self.config['roi'],
217 icr=self.config['icr'],
218 ocr=self.config['ocr'],
219 ltime=self.config['ltime'],
220 add_data=False)
221 if sum is not None:
222 popts = dict(marker=None, markersize=0, linewidth=2.5,
223 show_legend=True, ylabel=label, label=label,
224 xlabel='Energy (eV)')
225 self.parent.plotpanel.plot(en, sum/i0, new=True, **popts)
227 def onPlotEach(self, event=None):
228 self.read_form()
229 new = True
230 en, i0 = self.get_en_i0()
231 popts = dict(marker=None, markersize=0, linewidth=2.5,
232 show_legend=True, xlabel='Energy (eV)',
233 ylabel=f'Corrected Channels')
235 nused = 0
236 for pchan in range(1, self.config['nchans']+1):
237 roi, dtc = self.read_arrays(pchan)
238 if roi is not None:
239 popts['label'] = f'Chan{pchan} Corrected'
240 if new:
241 self.parent.plotpanel.plot(en, roi*dtc/i0, new=True, **popts)
242 new = False
243 else:
244 self.parent.plotpanel.oplot(en, roi*dtc/i0, **popts)
246 def onPlotThis(self, event=None):
247 self.read_form()
248 en, i0 = self.get_en_i0()
249 pchan = self.config['plot_chan']
250 roi, dtc = self.read_arrays(pchan)
251 if roi is None:
252 return
253 ylabel = self.wids['roi'].GetStringSelection()
254 popts = dict(marker=None, markersize=0, linewidth=2.5, show_legend=True,
255 ylabel=f'Chan{pchan}', xlabel='Energy (eV)',
256 label=f'Chan{pchan} Raw')
258 self.parent.plotpanel.plot(en, roi/i0, new=True, **popts)
259 popts['label'] = f'Chan{pchan} Corrected'
260 self.parent.plotpanel.oplot(en, roi*dtc/i0, **popts)
262 def on_nchans(self, event=None, value=None, **kws):
263 try:
264 nchans = self.wids['nchans'].GetValue()
265 pchan = self.wids['plot_chan'].GetValue()
266 self.wids['plot_chan'].SetMax(nchans)
267 self.wids['plot_chan'].SetValue(pchan)
268 except:
269 pass
271 def read_form(self, event=None, value=None, **kws):
272 try:
273 wids = self.wids
274 nchans = int(wids['nchans'].GetValue())
275 step = int(wids['step'].GetValue())
276 badchans = wids['bad_chans'].GetValue().replace(',', ' ').strip()
277 except:
278 return
280 bad_channels = []
281 if len(badchans) > 0:
282 try:
283 bad_channels = [int(s) for s in badchans.split()]
284 wids['bad_chans'].SetBackgroundColour('#FFFFFF')
285 except:
286 bad_channels = []
287 wids['bad_chans'].SetBackgroundColour('#F0B03080')
289 pchan = int(wids['plot_chan'].GetValue())
291 self.config['bad_chans'] = bad_channels
292 self.config['plot_chan'] = pchan
293 self.config['nchans'] = nchans
294 self.config['step'] = step
295 self.config['i0'] = wids['i0'].GetSelection()
296 if wids['i0'].GetStringSelection() in ('1.0', ''):
297 self.config['i0'] = '1.0'
299 for s in ('roi', 'icr', 'ocr', 'ltime'):
300 lab = wids[s].GetStringSelection()
301 ilab = wids[s].GetSelection()
302 if lab in ('1.0', ''):
303 wids[f"{s}_txt"].SetLabel(lab)
304 wids[f"{s}_txt"].SetToolTip(lab)
305 self.config[s] = '1.0'
306 else:
307 chans = [ilab + i*step for i in range(nchans)]
308 labs = []
309 for i in range(nchans):
310 if (i+1) in bad_channels:
311 chans[i] = -1
312 else:
313 nchan = chans[i]
314 if nchan < len(self.group.array_labels):
315 labs.append(self.group.array_labels[nchan])
316 wids[f"{s}_txt"].SetLabel(', '.join(labs))
317 self.config[s] = chans
320class MultiColumnFrame(wx.Frame) :
321 """Select Multiple Columns for import, optional i0 channel"""
322 def __init__(self, parent, group, config=None, on_ok=None):
323 self.parent = parent
324 self.group = group
326 self.on_ok = on_ok
327 wx.Frame.__init__(self, None, -1, 'Import Multiple Columns from a file',
328 style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
330 self.config = {'channels': [], 'i0': '1.0'}
331 if config is not None:
332 self.config.update(config)
334 self.SetFont(Font(FONTSIZE))
335 sizer = wx.GridBagSizer(2, 2)
336 panel = scrolled.ScrolledPanel(self)
338 self.SetMinSize((475, 350))
339 self.yarr_labels = [s for s in self.parent.yarr_labels]
340 wids = self.wids = {}
342 wids['i0'] = Choice(panel, choices=self.yarr_labels, size=(200, -1))
343 wids['i0'].SetToolTip("All Channels will be divided by the I0 array")
345 wids['i0'].SetStringSelection(self.parent.yarr2.GetStringSelection())
347 bpanel = wx.Panel(panel)
348 bsizer = wx.BoxSizer(wx.HORIZONTAL)
350 bsizer.Add(Button(bpanel, ' Select All ', action=self.onSelAll))
351 bsizer.Add(Button(bpanel, ' Select None ', action=self.onSelNone))
352 bsizer.Add(Button(bpanel, ' Plot Selected ', action=self.onPlotSel))
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=' Select '), (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 onPlotSel(self, event=None):
409 group = self.group
410 self.config['i0'] = self.wids['i0'].GetSelection()
411 channels = []
412 x = self.group.xplot
413 popts = dict(marker=None, markersize=0, linewidth=2.5,
414 ylabel='selected arrays', show_legend=True,
415 xlabel=self.group.plot_xlabel, delay_draw=True)
416 first = True
417 for wname, wid in self.wids.items():
418 if wname.startswith('use_') and wid.IsChecked():
419 chan = int(wname.replace('use_', ''))
420 y = self.group.data[chan, :]
421 try:
422 label = self.group.array_labels[chan]
423 except:
424 label = f'column {chan+1}'
425 plot = self.parent.plotpanel.oplot
426 if first:
427 first = False
428 plot = self.parent.plotpanel.plot
429 plot(x, y, label=label, **popts)
430 self.parent.plotpanel.draw()
433 def onPlot(self, event=None, index=None):
434 if index is not None:
435 x = self.group.xplot
436 y = self.group.data[index, :]
437 try:
438 label = self.group.array_labels[index]
439 except:
440 label = f'column {index+1}'
442 popts = dict(marker=None, markersize=0, linewidth=2.5,
443 ylabel=label, xlabel=self.group.plot_xlabel, label=label)
444 self.parent.plotpanel.plot(x, y, **popts)
446 def onOK_Multi(self, evt=None):
447 group = self.group
448 self.config['i0'] = self.wids['i0'].GetSelection()
449 channels = []
450 for wname, wid in self.wids.items():
451 if wname.startswith('use_') and wid.IsChecked():
452 chan = int(wname.replace('use_', ''))
453 channels.append(chan)
455 self.config['channels'] = channels
456 if callable(self.on_ok):
457 self.on_ok(self.config)
458 self.Destroy()
461class EditColumnFrame(wx.Frame) :
462 """Edit Column Labels for a larch grouop"""
463 def __init__(self, parent, group, on_ok=None):
464 self.parent = parent
465 self.group = group
466 self.on_ok = on_ok
467 wx.Frame.__init__(self, None, -1, 'Edit Array Names',
468 style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
470 self.SetFont(Font(FONTSIZE))
471 sizer = wx.GridBagSizer(2, 2)
472 panel = scrolled.ScrolledPanel(self)
474 self.SetMinSize((700, 450))
476 self.wids = {}
477 ir = 0
478 sizer.Add(Button(panel, 'Apply Changes', size=(200, -1),
479 action=self.onOK_Edit),
480 (0, 1), (1, 2), LEFT, 3)
481 sizer.Add(Button(panel, 'Use Column Number', size=(200, -1),
482 action=self.onColNumber),
483 (0, 3), (1, 2), LEFT, 3)
484 sizer.Add(HLine(panel, size=(550, 2)),
485 (1, 1), (1, 5), LEFT, 3)
487 cind = SimpleText(panel, label='Column')
488 cold = SimpleText(panel, label=' Current Name')
489 cnew = SimpleText(panel, label=' Enter New Name')
490 cret = SimpleText(panel, label=' Result ')
491 cinfo = SimpleText(panel, label=' Data Range')
492 cplot = SimpleText(panel, label=' Plot')
494 ir = 2
495 sizer.Add(cind, (ir, 0), (1, 1), LEFT, 3)
496 sizer.Add(cold, (ir, 1), (1, 1), LEFT, 3)
497 sizer.Add(cnew, (ir, 2), (1, 1), LEFT, 3)
498 sizer.Add(cret, (ir, 3), (1, 1), LEFT, 3)
499 sizer.Add(cinfo, (ir, 4), (1, 1), LEFT, 3)
500 sizer.Add(cplot, (ir, 5), (1, 1), LEFT, 3)
502 nlabels = len(group.array_labels)
503 narrays, npts = group.data.shape
504 for i in range(narrays):
505 if i < nlabels:
506 name = group.array_labels[i]
507 else:
508 name = f'unnamed_column{i+1}'
509 ir += 1
510 cind = SimpleText(panel, label=f' {i+1} ')
511 cold = SimpleText(panel, label=f' {name} ')
512 cret = SimpleText(panel, label=fix_varname(name))
513 cnew = wx.TextCtrl(panel, value=name, size=(150, -1),
514 style=wx.TE_PROCESS_ENTER)
516 cnew.Bind(wx.EVT_TEXT_ENTER, partial(self.update, index=i))
517 cnew.Bind(wx.EVT_KILL_FOCUS, partial(self.update, index=i))
519 arr = group.data[i,:]
520 info_str = f" [{gformat(arr.min(),length=9)}:{gformat(arr.max(), length=9)}] "
521 cinfo = SimpleText(panel, label=info_str)
522 cplot = Button(panel, 'Plot', action=partial(self.onPlot, index=i))
525 self.wids[f"{i}"] = cnew
526 self.wids[f"ret_{i}"] = cret
528 sizer.Add(cind, (ir, 0), (1, 1), LEFT, 3)
529 sizer.Add(cold, (ir, 1), (1, 1), LEFT, 3)
530 sizer.Add(cnew, (ir, 2), (1, 1), LEFT, 3)
531 sizer.Add(cret, (ir, 3), (1, 1), LEFT, 3)
532 sizer.Add(cinfo, (ir, 4), (1, 1), LEFT, 3)
533 sizer.Add(cplot, (ir, 5), (1, 1), LEFT, 3)
535 pack(panel, sizer)
536 panel.SetupScrolling()
538 mainsizer = wx.BoxSizer(wx.VERTICAL)
539 mainsizer.Add(panel, 1, wx.GROW|wx.ALL, 1)
541 pack(self, mainsizer)
542 self.Show()
543 self.Raise()
545 def onPlot(self, event=None, index=None):
546 if index is not None:
547 x = self.group.index
548 y = self.group.data[index, :]
549 label = self.wids["ret_%i" % index].GetLabel()
550 popts = dict(marker='o', markersize=4, linewidth=1.5,
551 ylabel=label, xlabel='data point', label=label)
552 self.parent.plotpanel.plot(x, y, **popts)
554 def onColNumber(self, evt=None, index=-1):
555 for name, wid in self.wids.items():
556 val = name
557 if name.startswith('ret_'):
558 val = name[4:]
559 setter = wid.SetLabel
560 else:
561 setter = wid.SetValue
562 setter("col%d" % (int(val) +1))
564 def update(self, evt=None, index=-1):
565 newval = fix_varname(self.wids[f"{index}"].GetValue())
566 self.wids[f"ret_{index}"].SetLabel(newval)
568 def update_char(self, evt=None, index=-1):
569 if evt.GetKeyCode() == wx.WXK_RETURN:
570 self.update(evt=evt, index=index)
571 # evt.Skip()
573 def onOK_Edit(self, evt=None):
574 group = self.group
575 array_labels = []
576 for i in range(len(self.group.array_labels)):
577 newname = self.wids["ret_%i" % i].GetLabel()
578 array_labels.append(newname)
580 if callable(self.on_ok):
581 self.on_ok(array_labels)
582 self.Destroy()
584class ColumnDataFileFrame(wx.Frame) :
585 """Column Data File, select columns"""
586 def __init__(self, parent, filename=None, groupname=None, config=None,
587 read_ok_cb=None, edit_groupname=True, _larch=None):
588 self.parent = parent
589 self._larch = _larch
590 self.path = filename
592 group = self.read_column_file(self.path)
593 # print("COLUMN FILE Read ", self.path, getattr(group, 'datatype', 'unknown'))
594 self.subframes = {}
595 self.workgroup = Group(raw=group)
596 for attr in ('path', 'filename', 'groupname', 'datatype',
597 'array_labels', 'data'):
598 setattr(self.workgroup, attr, getattr(group, attr, None))
600 self.array_labels = [l.lower() for l in group.array_labels]
602 has_energy = False
603 en_units = 'unknown'
604 for arrlab in self.array_labels[:5]:
605 arrlab = arrlab.lower()
606 if arrlab.startswith('en') or 'ener' in arrlab:
607 en_units = 'eV'
608 has_energy = True
610 # print("C : ", has_energy, self.workgroup.datatype, config)
612 if self.workgroup.datatype in (None, 'unknown'):
613 self.workgroup.datatype = 'xas' if has_energy else 'xydata'
615 en_units = 'eV' if self.workgroup.datatype == 'xas' else 'unknown'
617 self.read_ok_cb = read_ok_cb
618 self.config = dict(xarr=None, yarr1=None, yarr2=None, yop='/',
619 ypop='', monod=3.1355316, en_units=en_units,
620 yerr_op='constant', yerr_val=1, yerr_arr=None,
621 yrpop='', yrop='/', yref1='', yref2='',
622 has_yref=False, dtc_config={}, multicol_config={})
623 if config is not None:
624 if 'datatype' in config:
625 config.pop('datatype')
626 self.config.update(config)
628 if self.config['yarr2'] is None and 'i0' in self.array_labels:
629 self.config['yarr2'] = 'i0'
631 if self.config['yarr1'] is None:
632 if 'itrans' in self.array_labels:
633 self.config['yarr1'] = 'itrans'
634 elif 'i1' in self.array_labels:
635 self.config['yarr1'] = 'i1'
637 if self.config['yref1'] is None:
638 if 'iref' in self.array_labels:
639 self.config['yref1'] = 'iref'
640 elif 'irefer' in self.array_labels:
641 self.config['yref1'] = 'irefer'
642 elif 'i2' in self.array_labels:
643 self.config['yref1'] = 'i2'
645 if self.config['yref2'] is None and 'i1' in self.array_labels:
646 self.config['yref2'] = 'i1'
648 use_trans = self.config.get('is_trans', False) or 'log' in self.config['ypop']
650 message = "Data Columns for %s" % group.filename
651 wx.Frame.__init__(self, None, -1,
652 'Build Arrays from Data Columns for %s' % group.filename,
653 style=FRAMESTYLE)
655 x0, y0 = parent.GetPosition()
656 self.SetPosition((x0+60, y0+60))
658 self.SetFont(Font(FONTSIZE))
659 panel = wx.Panel(self)
660 self.SetMinSize((725, 700))
661 self.colors = GUIColors()
663 def subtitle(s, fontsize=12, colour=wx.Colour(10, 10, 180)):
664 return SimpleText(panel, s, font=Font(fontsize),
665 colour=colour, style=LEFT)
667 # title row
668 title = subtitle(message, colour=self.colors.title)
670 yarr_labels = self.yarr_labels = self.array_labels + ['1.0', '']
671 xarr_labels = self.xarr_labels = self.array_labels + ['_index']
673 self.xarr = Choice(panel, choices=xarr_labels, action=self.onXSelect, size=(150, -1))
674 self.yarr1 = Choice(panel, choices= self.array_labels, action=self.onUpdate, size=(150, -1))
675 self.yarr2 = Choice(panel, choices=yarr_labels, action=self.onUpdate, size=(150, -1))
676 self.yerr_arr = Choice(panel, choices=yarr_labels, action=self.onUpdate, size=(150, -1))
677 self.yerr_arr.Disable()
679 self.datatype = Choice(panel, choices=DATATYPES, action=self.onUpdate, size=(150, -1))
680 self.datatype.SetStringSelection(self.workgroup.datatype)
682 self.en_units = Choice(panel, choices=ENUNITS_TYPES,
683 action=self.onEnUnitsSelect, size=(150, -1))
685 self.ypop = Choice(panel, choices=YPRE_OPS, action=self.onUpdate, size=(100, -1))
686 self.yop = Choice(panel, choices=ARR_OPS, action=self.onUpdate, size=(100, -1))
687 self.yerr_op = Choice(panel, choices=YERR_OPS, action=self.onYerrChoice, size=(100, -1))
688 self.yerr_op.SetSelection(0)
690 self.is_trans = Check(panel, label='is transmission data?',
691 default=use_trans, action=self.onTransCheck)
693 self.yerr_val = FloatCtrl(panel, value=1, precision=4, size=(75, -1))
694 self.monod_val = FloatCtrl(panel, value=3.1355316, precision=7, size=(75, -1))
696 xlab = SimpleText(panel, ' X array = ')
697 ylab = SimpleText(panel, ' Y array = ')
698 units_lab = SimpleText(panel, ' Units of X array: ')
699 yerr_lab = SimpleText(panel, ' Y uncertainty = ')
700 dtype_lab = SimpleText(panel, ' Data Type: ')
701 monod_lab = SimpleText(panel, ' Mono D spacing (Ang): ')
702 yerrval_lab = SimpleText(panel, ' Value:')
703 self.info_message = subtitle(' ', colour=wx.Colour(100, 10, 10))
705 # yref
706 self.has_yref = Check(panel, label='data file includes energy reference data',
707 default=self.config['has_yref'],
708 action=self.onYrefCheck)
709 refylab = SimpleText(panel, ' Refer array = ')
710 self.yref1 = Choice(panel, choices=yarr_labels, action=self.onUpdate, size=(150, -1))
711 self.yref2 = Choice(panel, choices=yarr_labels, action=self.onUpdate, size=(150, -1))
712 self.yrpop = Choice(panel, choices=YPRE_OPS, action=self.onUpdate, size=(100, -1))
713 self.yrop = Choice(panel, choices=ARR_OPS, action=self.onUpdate, size=(100, -1))
715 self.ysuf = SimpleText(panel, '')
716 # print("COL FILE READER set ypop to ", use_trans, self.config['ypop'])
717 self.ypop.SetStringSelection(self.config['ypop'])
718 self.yop.SetStringSelection(self.config['yop'])
719 self.yrpop.SetStringSelection(self.config['yrpop'])
720 self.yrop.SetStringSelection(self.config['yrop'])
721 self.monod_val.SetValue(self.config['monod'])
722 self.monod_val.SetAction(self.onUpdate)
723 self.monod_val.Enable(self.config['en_units'].startswith('deg'))
724 self.en_units.SetStringSelection(self.config['en_units'])
725 self.yerr_op.SetStringSelection(self.config['yerr_op'])
726 self.yerr_val.SetValue(self.config['yerr_val'])
727 if '(' in self.config['ypop']:
728 self.ysuf.SetLabel(')')
731 ixsel, iysel = 0, 1
732 iy2sel = iyesel = iyr1sel = iyr2sel = len(yarr_labels)-1
733 if self.config['xarr'] in xarr_labels:
734 ixsel = xarr_labels.index(self.config['xarr'])
735 if self.config['yarr1'] in self.array_labels:
736 iysel = self.array_labels.index(self.config['yarr1'])
737 if self.config['yarr2'] in yarr_labels:
738 iy2sel = yarr_labels.index(self.config['yarr2'])
739 if self.config['yerr_arr'] in yarr_labels:
740 iyesel = yarr_labels.index(self.config['yerr_arr'])
741 if self.config['yref1'] in self.array_labels:
742 iyr1sel = self.array_labels.index(self.config['yref1'])
743 if self.config['yref2'] in self.array_labels:
744 iyr2sel = self.array_labels.index(self.config['yref2'])
746 self.xarr.SetSelection(ixsel)
747 self.yarr1.SetSelection(iysel)
748 self.yarr2.SetSelection(iy2sel)
749 self.yerr_arr.SetSelection(iyesel)
750 self.yref1.SetSelection(iyr1sel)
751 self.yref2.SetSelection(iyr2sel)
753 self.wid_filename = wx.TextCtrl(panel, value=fix_filename(group.filename),
754 size=(250, -1))
755 self.wid_groupname = wx.TextCtrl(panel, value=group.groupname,
756 size=(150, -1))
757 if not edit_groupname:
758 self.wid_groupname.Disable()
759 self.wid_reffilename = wx.TextCtrl(panel, value=fix_filename(group.filename + '_ref'),
760 size=(250, -1))
761 self.wid_refgroupname = wx.TextCtrl(panel, value=group.groupname + '_ref',
762 size=(150, -1))
764 self.onTransCheck(is_trans=use_trans)
765 self.onYrefCheck(has_yref=self.config['has_yref'])
768 bpanel = wx.Panel(panel)
769 bsizer = wx.BoxSizer(wx.HORIZONTAL)
770 _ok = Button(bpanel, 'OK', action=self.onOK)
771 _cancel = Button(bpanel, 'Cancel', action=self.onCancel)
772 _edit = Button(bpanel, 'Edit Array Names', action=self.onEditNames)
773 self.multi_sel = Button(bpanel, 'Select Multilple Columns', action=self.onMultiColumn)
774 self.multi_clear = Button(bpanel, 'Clear Multiple Columns', action=self.onClearMultiColumn)
775 self.multi_clear.Disable()
776 _edit.SetToolTip('Change the current Column Names')
778 self.multi_sel.SetToolTip('Select Multiple Columns to import as separate groups')
779 self.multi_clear.SetToolTip('Clear Multiple Column Selection')
780 bsizer.Add(_ok)
781 bsizer.Add(_cancel)
782 bsizer.Add(_edit)
783 bsizer.Add(self.multi_sel)
784 bsizer.Add(self.multi_clear)
785 _ok.SetDefault()
786 pack(bpanel, bsizer)
788 self.dtc_button = Button(panel, 'Sum and Correct Fluoresence Data', action=self.onDTC)
789 self.dtc_button.SetToolTip('Select channels and do deadtime-corrections for multi-element fluorescence data')
791 sizer = wx.GridBagSizer(2, 2)
792 sizer.Add(title, (0, 0), (1, 7), LEFT, 5)
794 ir = 1
795 sizer.Add(subtitle(' X [Energy] Array:'), (ir, 0), (1, 2), LEFT, 0)
796 sizer.Add(dtype_lab, (ir, 3), (1, 1), RIGHT, 0)
797 sizer.Add(self.datatype, (ir, 4), (1, 2), LEFT, 0)
799 ir += 1
800 sizer.Add(xlab, (ir, 0), (1, 1), LEFT, 0)
801 sizer.Add(self.xarr, (ir, 1), (1, 2), LEFT, 0)
802 sizer.Add(units_lab, (ir, 3), (1, 1), RIGHT, 0)
803 sizer.Add(self.en_units, (ir, 4), (1, 2), LEFT, 0)
805 ir += 1
806 sizer.Add(monod_lab, (ir, 2), (1, 2), RIGHT, 0)
807 sizer.Add(self.monod_val,(ir, 4), (1, 1), LEFT, 0)
809 ir += 1
810 sizer.Add(subtitle(' Y [\u03BC(E)] Array:'), (ir, 0), (1, 1), LEFT, 0)
811 sizer.Add(self.is_trans, (ir, 1), (1, 2), LEFT, 0)
812 sizer.Add(self.dtc_button, (ir, 3), (1, 2), RIGHT, 0)
813 ir += 1
814 sizer.Add(ylab, (ir, 0), (1, 1), LEFT, 0)
815 sizer.Add(self.ypop, (ir, 1), (1, 1), LEFT, 0)
816 sizer.Add(self.yarr1, (ir, 2), (1, 1), LEFT, 0)
817 sizer.Add(self.yop, (ir, 3), (1, 1), RIGHT, 0)
818 sizer.Add(self.yarr2, (ir, 4), (1, 1), LEFT, 0)
819 sizer.Add(self.ysuf, (ir, 5), (1, 1), LEFT, 0)
822 ir += 1
823 sizer.Add(yerr_lab, (ir, 0), (1, 1), LEFT, 0)
824 sizer.Add(self.yerr_op, (ir, 1), (1, 1), LEFT, 0)
825 sizer.Add(self.yerr_arr, (ir, 2), (1, 1), LEFT, 0)
826 sizer.Add(yerrval_lab, (ir, 3), (1, 1), RIGHT, 0)
827 sizer.Add(self.yerr_val, (ir, 4), (1, 2), LEFT, 0)
829 ir += 1
830 sizer.Add(SimpleText(panel, ' Display Name:'), (ir, 0), (1, 1), LEFT, 0)
831 sizer.Add(self.wid_filename, (ir, 1), (1, 2), LEFT, 0)
832 sizer.Add(SimpleText(panel, ' Group Name:'), (ir, 3), (1, 1), RIGHT, 0)
833 sizer.Add(self.wid_groupname, (ir, 4), (1, 2), LEFT, 0)
835 ir += 1
836 sizer.Add(self.info_message, (ir, 0), (1, 5), LEFT, 1)
838 ir += 2
839 sizer.Add(subtitle(' Reference [\u03BC_ref(E)] Array: '),
840 (ir, 0), (1, 2), LEFT, 0)
841 sizer.Add(self.has_yref, (ir, 2), (1, 3), LEFT, 0)
843 ir += 1
844 sizer.Add(refylab, (ir, 0), (1, 1), LEFT, 0)
845 sizer.Add(self.yrpop, (ir, 1), (1, 1), LEFT, 0)
846 sizer.Add(self.yref1, (ir, 2), (1, 1), LEFT, 0)
847 sizer.Add(self.yrop, (ir, 3), (1, 1), RIGHT, 0)
848 sizer.Add(self.yref2, (ir, 4), (1, 2), LEFT, 0)
850 ir += 1
851 sizer.Add(SimpleText(panel, ' Reference Name:'), (ir, 0), (1, 1), LEFT, 0)
852 sizer.Add(self.wid_reffilename, (ir, 1), (1, 2), LEFT, 0)
853 sizer.Add(SimpleText(panel, ' Group Name:'), (ir, 3), (1, 1), RIGHT, 0)
854 sizer.Add(self.wid_refgroupname, (ir, 4), (1, 2), LEFT, 0)
856 ir +=1
857 sizer.Add(bpanel, (ir, 0), (1, 5), LEFT, 3)
859 pack(panel, sizer)
861 self.nb = fnb.FlatNotebook(self, -1, agwStyle=FNB_STYLE)
862 self.nb.SetTabAreaColour(wx.Colour(248,248,240))
863 self.nb.SetActiveTabColour(wx.Colour(254,254,195))
864 self.nb.SetNonActiveTabTextColour(wx.Colour(40,40,180))
865 self.nb.SetActiveTabTextColour(wx.Colour(80,0,0))
867 self.plotpanel = PlotPanel(self, messenger=self.plot_messages)
868 try:
869 plotopts = self._larch.symtable._sys.wx.plotopts
870 self.plotpanel.conf.set_theme(plotopts['theme'])
871 self.plotpanel.conf.enable_grid(plotopts['show_grid'])
872 except:
873 pass
876 self.plotpanel.SetMinSize((200, 200))
877 textpanel = wx.Panel(self)
878 ftext = wx.TextCtrl(textpanel, style=wx.TE_MULTILINE|wx.TE_READONLY,
879 size=(400, 250))
881 ftext.SetValue(group.text)
882 ftext.SetFont(Font(FONTSIZE))
884 textsizer = wx.BoxSizer(wx.VERTICAL)
885 textsizer.Add(ftext, 1, LEFT|wx.GROW, 1)
886 pack(textpanel, textsizer)
888 self.nb.AddPage(textpanel, ' Text of Data File ', True)
889 self.nb.AddPage(self.plotpanel, ' Plot of Selected Arrays ', True)
891 mainsizer = wx.BoxSizer(wx.VERTICAL)
892 mainsizer.Add(panel, 0, wx.GROW|wx.ALL, 2)
893 mainsizer.Add(self.nb, 1, LEFT|wx.GROW, 2)
894 pack(self, mainsizer)
896 self.statusbar = self.CreateStatusBar(2, 0)
897 self.statusbar.SetStatusWidths([-1, -1])
898 statusbar_fields = [group.filename, ""]
899 for i in range(len(statusbar_fields)):
900 self.statusbar.SetStatusText(statusbar_fields[i], i)
902 self.set_energy_units()
903 dtc_conf = self.config.get('dtc_config', {})
904 if len(dtc_conf) > 0:
905 self.onDTC_OK(dtc_conf, update=False)
907 self.Show()
908 self.Raise()
909 self.onUpdate()
911 def onDTC(self, event=None):
912 self.show_subframe('dtc_conf', DeadtimeCorrectionFrame,
913 config=self.config['dtc_config'],
914 group=self.workgroup,
915 on_ok=self.onDTC_OK)
917 def onDTC_OK(self, config, update=True, **kws):
918 label, sum = sum_fluor_channels(self.workgroup, config['roi'],
919 icr=config['icr'],
920 ocr=config['ocr'],
921 ltime=config['ltime'],
922 add_data=False)
923 if sum is None:
924 return
925 self.info_message.SetLabel(f"Added array '{label}' with summed and corrected fluorecence data")
926 self.workgroup.array_labels.append(label)
927 self.set_array_labels(self.workgroup.array_labels)
928 npts = len(sum)
929 new = np.append(self.workgroup.raw.data, sum.reshape(1, npts), axis=0)
930 self.workgroup.raw.data = new[()]
931 self.workgroup.data = new[()]
932 self.yarr1.SetStringSelection(label)
933 self.config['dtc_config'] = config
934 if update:
935 self.onUpdate()
937 def onClearMultiColumn(self, event=None):
938 self.config['multicol_config'] = {}
939 self.info_message.SetLabel(f" cleared reading of multiple columns")
940 self.multi_clear.Disable()
941 self.yarr1.Enable()
942 self.ypop.Enable()
943 self.yop.Enable()
944 self.onUpdate()
947 def onMultiColumn(self, event=None):
948 self.show_subframe('multicol', MultiColumnFrame,
949 config=self.config['multicol_config'],
950 group=self.workgroup,
951 on_ok=self.onMultiColumn_OK)
954 def onMultiColumn_OK(self, config, update=True, **kws):
955 chans = config.get('channels', [])
956 if len(chans) == 0:
957 self.config['multicol_config'] = {}
958 else:
959 self.config['multicol_config'] = config
960 self.yarr1.SetSelection(chans[0])
961 self.yarr2.SetSelection(config['i0'])
962 self.ypop.SetStringSelection('')
963 self.yarr1.Disable()
964 self.ypop.Disable()
965 self.yop.Disable()
966 y2 = self.yarr2.GetStringSelection()
967 msg = f" Will import {len(config['channels'])} Y arrays, divided by '{y2}'"
968 self.info_message.SetLabel(msg)
969 self.multi_clear.Enable()
970 if update:
971 self.onUpdate()
974 def read_column_file(self, path):
975 """read column file, generally as initial read"""
976 path = Path(path).absolute()
977 filename = path.name
978 path = path.as_posix()
979 reader, text = guess_filereader(path, return_text=True)
981 if reader == 'read_specfile':
982 if not is_specfile(path, require_multiple_scans=True):
983 reader = 'read_ascii'
985 if reader in ('read_xdi', 'read_gsexdi'):
986 # first check for Nans and Infs
987 nan_result = look_for_nans(path)
988 if 'read error' in nan_result.message:
989 title = "Cannot read %s" % path
990 message = "Error reading %s\n%s" %(path, nan_result.message)
991 r = Popup(self.parent, message, title)
992 return None
993 if 'no data' in nan_result.message:
994 title = "No data in %s" % path
995 message = "No data found in file %s" % path
996 r = Popup(self.parent, message, title)
997 return None
999 if ('has nans' in nan_result.message or
1000 'has infs' in nan_result.message):
1001 reader = 'read_ascii'
1003 tmpname = '_tmpfile_'
1004 read_cmd = "%s = %s('%s')" % (tmpname, reader, path)
1005 self.reader = reader
1006 _larch = self._larch
1008 if (not isinstance(_larch, larch.Interpreter) and
1009 hasattr(_larch, '_larch')):
1010 _larch = _larch._larch
1011 try:
1012 _larch.eval(read_cmd, add_history=True)
1013 except:
1014 pass
1015 if len(_larch.error) > 0 and reader in ('read_xdi', 'read_gsexdi'):
1016 read_cmd = "%s = %s('%s')" % (tmpname, 'read_ascii', path)
1017 try:
1018 _larch.eval(read_cmd, add_history=True)
1019 except:
1020 pass
1021 if len(_larch.error) == 0:
1022 self.reader = 'read_ascii'
1024 if len(_larch.error) > 0:
1025 msg = ["Error trying to read '%s':" % path, ""]
1026 for err in _larch.error:
1027 exc_name, errmsg = err.get_error()
1028 msg.append(errmsg)
1030 title = "Cannot read %s" % path
1031 r = Popup(self.parent, "\n".join(msg), title)
1032 return None
1033 group = deepcopy(_larch.symtable.get_symbol(tmpname))
1034 _larch.symtable.del_symbol(tmpname)
1036 group.text = text
1037 group.path = path
1038 group.filename = filename
1039 group.groupname = file2groupname(filename,
1040 symtable=self._larch.symtable)
1041 return group
1043 def show_subframe(self, name, frameclass, **opts):
1044 shown = False
1045 if name in self.subframes:
1046 try:
1047 self.subframes[name].Raise()
1048 shown = True
1049 except:
1050 pass
1051 if not shown:
1052 self.subframes[name] = frameclass(self, **opts)
1053 self.subframes[name].Show()
1054 self.subframes[name].Raise()
1057 def onEditNames(self, evt=None):
1058 self.show_subframe('editcol', EditColumnFrame,
1059 group=self.workgroup,
1060 on_ok=self.set_array_labels)
1062 def set_array_labels(self, arr_labels):
1063 self.workgroup.array_labels = arr_labels
1064 yarr_labels = self.yarr_labels = arr_labels + ['1.0', '']
1065 xarr_labels = self.xarr_labels = arr_labels + ['_index']
1066 def update(wid, choices):
1067 curstr = wid.GetStringSelection()
1068 curind = wid.GetSelection()
1069 wid.SetChoices(choices)
1070 if curstr in choices:
1071 wid.SetStringSelection(curstr)
1072 else:
1073 wid.SetSelection(curind)
1074 update(self.xarr, xarr_labels)
1075 update(self.yarr1, yarr_labels)
1076 update(self.yarr2, yarr_labels)
1077 update(self.yerr_arr, yarr_labels)
1078 self.onUpdate()
1080 def onOK(self, event=None):
1081 """ build arrays according to selection """
1082 self.read_form()
1083 cout = create_arrays(self.workgroup, **self.config)
1084 self.config.update(cout)
1085 conf = self.config
1086 if self.ypop.Enabled: #not using multicolumn mode
1087 conf['multicol_config'] = {'channels': [], 'i0': conf['iy2']}
1089 self.expressions = conf['expressions']
1090 filename = conf['filename']
1091 groupname = conf['groupname']
1093 conf['array_labels'] = self.workgroup.array_labels
1095 # generate script to pass back to calling program:
1096 labstr = ', '.join(self.array_labels)
1097 buff = [f"{ group} = {self.reader}('{ path} ', labels='{labstr}')",
1098 "{group}.path = '{path}'",
1099 "{group}.is_frozen = False",
1100 "{group}.energy_ref = '{group}'"]
1102 dtc_conf = conf.get('dtc_config', {})
1103 if len(dtc_conf) > 0:
1104 sumcmd = "sum_fluor_channels({{group}}, {roi}, icr={icr}, ocr={ocr}, ltime={ltime})"
1105 buff.append(sumcmd.format(**dtc_conf))
1107 buff.append("{group}.datatype = '%s'" % (conf['datatype']))
1109 for attr in ('plot_xlabel', 'plot_ylabel'):
1110 val = getattr(self.workgroup, attr)
1111 buff.append("{group}.%s = '%s'" % (attr, val))
1113 xexpr = self.expressions['xplot']
1114 en_units = conf['en_units']
1115 if en_units.startswith('deg'):
1116 monod = conf['monod']
1117 buff.append(f"monod = {monod:.9f}")
1118 buff.append(f"{ group} .xplot = PLANCK_HC/(2*monod*sin(DEG2RAD*({xexpr:s})))")
1119 elif en_units.startswith('keV'):
1120 buff.append(f"{ group} .xplot = 1000.0*{xexpr:s}")
1121 else:
1122 buff.append(f"{ group} .xplot = {xexpr:s}")
1124 for aname in ('yplot', 'yerr'):
1125 expr = self.expressions[aname]
1126 buff.append(f"{ group} .{aname} = {expr}")
1129 dtype = getattr(self.workgroup, 'datatype', 'xytype')
1130 if dtype == 'xas':
1131 if self.reader == 'read_gsescan':
1132 buff.append("{group}.xplot = {group}.x")
1133 buff.append("{group}.energy = {group}.xplot[:]")
1134 buff.append("{group}.mu = {group}.yplot[:]")
1135 buff.append("sort_xafs({group}, overwrite=True, fix_repeats=True)")
1136 elif dtype == 'xydata':
1137 buff.append("{group}.x = {group}.xplot[:]")
1138 buff.append("{group}.y = {group}.yplot[:]")
1139 buff.append("{group}.scale = (ptp({group}.yplot)+1.e-15)")
1140 buff.append("{group}.xshift = 0.0")
1142 array_desc = dict(xplot=self.workgroup.plot_xlabel,
1143 yplot=self.workgroup.plot_ylabel,
1144 yerr=self.expressions['yerr'])
1146 reffile = refgroup = None
1147 if conf['has_yref']:
1148 reffile = conf['reffile']
1149 refgroup = conf['refgroup']
1150 refexpr = self.expressions['yref']
1151 array_desc['yref'] = getattr(self.workgroup, 'yrlabel', 'reference')
1153 buff.append("# reference group")
1154 buff.append("{refgroup} = deepcopy({group})")
1155 buff.append(f"{ refgroup} .yplot = { refgroup} .mu = {refexpr}")
1156 buff.append(f"{ refgroup} .plot_ylabel = '{self.workgroup.yrlabel}'")
1157 buff.append("{refgroup}.energy_ref = {group}.energy_ref = '{refgroup}'")
1158 buff.append("# end reference group")
1160 script = "\n".join(buff)
1161 conf['array_desc'] = array_desc
1163 if self.read_ok_cb is not None:
1164 self.read_ok_cb(script, self.path, conf)
1166 for f in self.subframes.values():
1167 try:
1168 f.Destroy()
1169 except:
1170 pass
1171 self.Destroy()
1173 def onCancel(self, event=None):
1174 self.workgroup.import_ok = False
1175 for f in self.subframes.values():
1176 try:
1177 f.Destroy()
1178 except:
1179 pass
1180 self.Destroy()
1182 def onYerrChoice(self, evt=None):
1183 yerr_choice = evt.GetString()
1184 self.yerr_arr.Disable()
1185 self.yerr_val.Disable()
1186 if 'const' in yerr_choice.lower():
1187 self.yerr_val.Enable()
1188 elif 'array' in yerr_choice.lower():
1189 self.yerr_arr.Enable()
1190 # self.onUpdate()
1192 def onTransCheck(self, evt=None, is_trans=False):
1193 if evt is not None:
1194 is_trans = evt.IsChecked()
1195 if is_trans:
1196 self.ypop.SetStringSelection('-log(')
1197 else:
1198 self.ypop.SetStringSelection('')
1199 try:
1200 self.onUpdate()
1201 except:
1202 pass
1204 def onYrefCheck(self, evt=None, has_yref=False):
1205 if evt is not None:
1206 has_yref = evt.IsChecked()
1207 self.yref1.Enable(has_yref)
1208 self.yref2.Enable(has_yref)
1209 self.yrpop.Enable(has_yref)
1210 self.yrop.Enable(has_yref)
1211 self.wid_reffilename.Enable(has_yref)
1212 self.wid_refgroupname.Enable(has_yref)
1215 def onXSelect(self, evt=None):
1216 ix = self.xarr.GetSelection()
1217 xname = self.xarr.GetStringSelection()
1219 workgroup = self.workgroup
1220 ncol, npts = self.workgroup.data.shape
1221 if xname.startswith('_index') or ix >= ncol:
1222 workgroup.xplot = 1.0*np.arange(npts)
1223 else:
1224 workgroup.xplot = 1.0*self.workgroup.data[ix, :]
1225 self.onUpdate()
1227 self.monod_val.Disable()
1228 if self.datatype.GetStringSelection().strip().lower() == 'xydata':
1229 self.en_units.SetSelection(4)
1230 else:
1231 eguess = guess_energy_units(workgroup.xplot)
1232 if eguess.startswith('keV'):
1233 self.en_units.SetSelection(1)
1234 elif eguess.startswith('deg'):
1235 self.en_units.SetSelection(2)
1236 self.monod_val.Enable()
1237 else:
1238 self.en_units.SetSelection(0)
1240 def onEnUnitsSelect(self, evt=None):
1241 self.monod_val.Enable(self.en_units.GetStringSelection().startswith('deg'))
1242 self.onUpdate()
1244 def set_energy_units(self):
1245 ix = self.xarr.GetSelection()
1246 xname = self.xarr.GetStringSelection()
1247 workgroup = self.workgroup
1248 try:
1249 ncol, npts = workgroup.data.shape
1250 except (AttributeError, ValueError):
1251 return
1253 if xname.startswith('_index') or ix >= ncol:
1254 workgroup.xplot = 1.0*np.arange(npts)
1255 else:
1256 workgroup.xplot = 1.0*self.workgroup.data[ix, :]
1257 if self.datatype.GetStringSelection().strip().lower() != 'xydata':
1258 eguess = guess_energy_units(workgroup.xplot)
1259 if eguess.startswith('eV'):
1260 self.en_units.SetStringSelection('eV')
1261 elif eguess.startswith('keV'):
1262 self.en_units.SetStringSelection('keV')
1264 def read_form(self, **kws):
1265 """return form configuration"""
1266 datatype = self.datatype.GetStringSelection().strip().lower()
1267 if self.workgroup.datatype == 'xydata' and datatype == 'xas':
1268 self.workgroup.datatype = 'xas'
1269 eguess = guess_energy_units(self.workgroup.xplot)
1270 if eguess.startswith('keV'):
1271 self.en_units.SetSelection(1)
1272 elif eguess.startswith('deg'):
1273 self.en_units.SetSelection(2)
1274 self.monod_val.Enable()
1275 else:
1276 self.en_units.SetSelection(0)
1277 if datatype == 'xydata':
1278 self.en_units.SetStringSelection('not energy')
1280 ypop = self.ypop.GetStringSelection().strip()
1281 self.is_trans.SetValue('log' in ypop)
1284 conf = {'datatype': datatype,
1285 'ix': self.xarr.GetSelection(),
1286 'xarr': self.xarr.GetStringSelection(),
1287 'en_units': self.en_units.GetStringSelection(),
1288 'monod': float(self.monod_val.GetValue()),
1289 'yarr1': self.yarr1.GetStringSelection().strip(),
1290 'yarr2': self.yarr2.GetStringSelection().strip(),
1291 'iy1': self.yarr1.GetSelection(),
1292 'iy2': self.yarr2.GetSelection(),
1293 'yop': self.yop.GetStringSelection().strip(),
1294 'ypop': ypop,
1295 'iyerr': self.yerr_arr.GetSelection(),
1296 'yerr_arr': self.yerr_arr.GetStringSelection(),
1297 'yerr_op': self.yerr_op.GetStringSelection().lower(),
1298 'yerr_val': self.yerr_val.GetValue(),
1299 'has_yref': self.has_yref.IsChecked(),
1300 'yref1': self.yref1.GetStringSelection().strip(),
1301 'yref2': self.yref2.GetStringSelection().strip(),
1302 'iry1': self.yref1.GetSelection(),
1303 'iry2': self.yref2.GetSelection(),
1304 'yrpop': self.yrpop.GetStringSelection().strip(),
1305 'yrop': self.yop.GetStringSelection().strip(),
1306 'filename': self.wid_filename.GetValue(),
1307 'groupname': fix_varname(self.wid_groupname.GetValue()),
1308 'reffile': self.wid_reffilename.GetValue(),
1309 'refgroup': fix_varname(self.wid_refgroupname.GetValue()),
1310 }
1311 self.config.update(conf)
1312 return conf
1314 def onUpdate(self, evt=None, **kws):
1315 """column selections changed calc xplot and yplot"""
1316 workgroup = self.workgroup
1317 try:
1318 ncol, npts = self.workgroup.data.shape
1319 except:
1320 return
1322 conf = self.read_form()
1323 cout = create_arrays(workgroup, **conf)
1324 self.expressions = cout.pop('expressions')
1325 conf.update(cout)
1327 if energy_may_need_rebinning(workgroup):
1328 self.info_message.SetLabel("Warning: XAS data may need to be rebinned!")
1330 fname = Path(workgroup.filename).name
1331 popts = dict(marker='o', markersize=4, linewidth=1.5, title=fname,
1332 xlabel=workgroup.plot_xlabel,
1333 ylabel=workgroup.plot_ylabel,
1334 label=workgroup.plot_ylabel)
1336 self.plotpanel.plot(workgroup.xplot, workgroup.yplot, **popts)
1337 if conf['has_yref']:
1338 yrlabel = getattr(workgroup, 'plot_yrlabel', 'reference')
1339 self.plotpanel.oplot(workgroup.xplot, workgroup.yref,
1340 y2label=yrlabel,
1341 linewidth=2.0, color='#E08070',
1342 label=yrlabel, zorder=-40, side='right')
1344 for i in range(self.nb.GetPageCount()):
1345 if 'plot' in self.nb.GetPageText(i).lower():
1346 self.nb.SetSelection(i)
1348 def plot_messages(self, msg, panel=1):
1349 self.statusbar.SetStatusText(msg, panel)
1352def create_arrays(dgroup, datatype='xas', ix=0, xarr='energy', en_units='eV',
1353 monod=3.1355316, yarr1=None, yarr2=None, iy1=2, iy2=1, yop='/',
1354 ypop='', iyerr=5, yerr_arr=None, yerr_op='constant', yerr_val=1.0,
1355 has_yref=False, yref1=None, yref2=None, iry1=3, iry2=2,
1356 yrpop='', yrop='/', **kws):
1357 """
1358 build arrays and values for datagroup based on configuration as from ColumnFile
1359 """
1360 ncol, npts = dgroup.data.shape
1361 exprs = dict(xplot=None, yplot=None, yerr=None, yref=None)
1363 if not hasattr(dgroup, 'index'):
1364 dgroup.index = 1.0*np.arange(npts)
1366 if xarr.startswith('_index') or ix >= ncol:
1367 dgroup.xplot = 1.0*np.arange(npts)
1368 xarr = '_index'
1369 exprs['xplot'] = 'arange({npts})'
1370 else:
1371 dgroup.xplot = 1.0*dgroup.data[ix, :]
1372 exprs['xplot'] = '{group}.data[{ix}, : ]'
1374 xlabel = xarr
1375 monod = float(monod)
1376 if en_units.startswith('deg'):
1377 dgroup.xplot = PLANCK_HC/(2*monod*np.sin(DEG2RAD*dgroup.xplot))
1378 xlabel = xarr + ' (eV)'
1379 elif en_units.startswith('keV'):
1380 dgroup.xplot *= 1000.0
1381 xlabel = xarr + ' (eV)'
1383 def pre_op(opstr, arr):
1384 if opstr == '-':
1385 return '', opstr, -arr
1386 suf = ''
1387 if opstr in ('-log(', 'log('):
1388 suf = ')'
1389 arr = safe_log(arr)
1390 if opstr.startswith('-'): arr = -arr
1391 arr[np.where(np.isnan(arr))] = 0
1392 return suf, opstr, arr
1394 if yarr1 is None:
1395 yarr1 = dgroup.array_labels[iy1]
1397 if yarr2 is None:
1398 yarr2 = dgroup.array_labels[iy2]
1400 ylabel = yarr1
1401 if len(yarr2) == 0:
1402 yarr2 = '1.0'
1403 else:
1404 ylabel = f"{ylabel}{yop}{yarr2}"
1406 if yarr1 == '0.0':
1407 ydarr1 = np.zeros(npts)*1.0
1408 yexpr1 = f'np.zeros(npts)'
1409 elif len(yarr1) == 0 or yarr1 == '1.0' or iy1 >= ncol:
1410 ydarr1 = np.ones(npts)*1.0
1411 yexpr1 = f'np.ones({npts})'
1412 else:
1413 ydarr1 = dgroup.data[iy1, :]
1414 yexpr1 = '{group}.data[{iy1}, : ]'
1416 dgroup.yplot = ydarr1
1417 exprs['yplot'] = yexpr1
1419 if yarr2 == '0.0':
1420 ydarr2 = np.zeros(npts)*1.0
1421 yexpr2 = '0.0'
1422 elif len(yarr2) == 0 or yarr2 == '1.0' or iy2 >= ncol:
1423 ydarr2 = np.ones(npts)*1.0
1424 yexpr2 = '1.0'
1425 else:
1426 ydarr2 = dgroup.data[iy2, :]
1427 yexpr2 = '{group}.data[{iy2}, : ]'
1429 if yop in ('+', '-', '*', '/'):
1430 exprs['yplot'] = f"{yexpr1}{yop}{yexpr2}"
1431 if yop == '+':
1432 dgroup.yplot = ydarr1 + ydarr2
1433 elif yop == '-':
1434 dgroup.yplot = ydarr1 - ydarr2
1435 elif yop == '*':
1436 dgroup.yplot = ydarr1 * ydarr2
1437 elif yop == '/':
1438 dgroup.yplot = ydarr1 / ydarr2
1440 ysuf, ypop, dgroup.yplot = pre_op(ypop, dgroup.yplot)
1441 ypopx = ypop.replace('log', 'safe_log')
1442 exprs['yplot'] = f"{ypopx}{exprs['yplot']}{ysuf}"
1443 ylabel = f"{ypop}{ylabel}{ysuf}"
1445 # error
1446 exprs['yerr'] = '1'
1447 if yerr_op.startswith('const'):
1448 yderr = yerr_val
1449 exprs['yerr'] = f"{yerr_val}"
1450 elif yerr_op.startswith('array'):
1451 yderr = dgroup.data[iyerr, :]
1452 exprs['yerr'] = '{group}.data[{iyerr}, :]'
1453 elif yerr_op.startswith('sqrt'):
1454 yderr = np.sqrt(dgroup.yplot)
1455 exprs['yerr'] = 'sqrt({group}.yplot)'
1457 # reference
1458 yrlabel = None
1459 if has_yref:
1460 yrlabel = yref1
1461 if len(yref2) == 0:
1462 yref2 = '1.0'
1463 else:
1464 yrlabel = f"{yrlabel}{yrop}{yref2}"
1466 if yref1 == '0.0':
1467 ydrarr1 = np.zeros(npts)*1.0
1468 yrexpr1 = 'zeros({npts})'
1469 elif len(yref1) == 0 or yref1 == '1.0' or iry1 >= ncol:
1470 ydrarr1 = np.ones(npts)*1.0
1471 yrexpr1 = 'ones({npts})'
1472 else:
1473 ydrarr1 = dgroup.data[iry1, :]
1474 yrexpr1 = '{group}.data[{iry1}, : ]'
1476 dgroup.yref = ydrarr1
1477 exprs['yref'] = yrexpr1
1479 if yref2 == '0.0':
1480 ydrarr2 = np.zeros(npts)*1.0
1481 ydrexpr2 = '0.0'
1482 elif len(yref2) == 0 or yref2 == '1.0' or iry2 >= ncol:
1483 ydrarr2 = np.ones(npts)*1.0
1484 yrexpr2 = '1.0'
1485 else:
1486 ydrarr2 = dgroup.data[iry2, :]
1487 yrexpr2 = '{group}.data[{iry2}, : ]'
1489 if yrop in ('+', '-', '*', '/'):
1490 exprs['yref'] = f'{yrexpr1} {yop} {yrexpr2}'
1491 if yrop == '+':
1492 dgroup.yref = ydrarr1 + ydrarr2
1493 elif yrop == '-':
1494 dgroup.yref = ydrarr1 - ydrarr2
1495 elif yrop == '*':
1496 dgroup.yref = ydrarr1 * ydarr2
1497 elif yrop == '/':
1498 dgroup.yref = ydrarr1 / ydrarr2
1500 yrsuf, yprop, dgroup.yref = pre_op(yrpop, dgroup.yref)
1501 yrpopx = yrpop.replace('log', 'safe_log')
1502 exprs['yref'] = f"{yrpopx}{exprs['yref']}{yrsuf}"
1503 yrlabel = f'{yrpop} {yrlabel} {yrsuf}'
1504 dgroup.yrlabel = yrlabel
1507 try:
1508 npts = min(len(dgroup.xplot), len(dgroup.yplot))
1509 except AttributeError:
1510 return
1511 except ValueError:
1512 return
1514 en = dgroup.xplot
1515 dgroup.datatype = datatype
1516 dgroup.npts = npts
1517 dgroup.plot_xlabel = xlabel
1518 dgroup.plot_ylabel = ylabel
1519 dgroup.xplot = np.array(dgroup.xplot[:npts])
1520 dgroup.yplot = np.array(dgroup.yplot[:npts])
1521 dgroup.y = dgroup.yplot
1522 dgroup.yerr = yderr
1523 if isinstance(yderr, np.ndarray):
1524 dgroup.yerr = np.array(yderr[:npts])
1525 if yrlabel is not None:
1526 dgroup.plot_yrlabel = yrlabel
1528 if dgroup.datatype == 'xas':
1529 dgroup.energy = dgroup.xplot
1530 dgroup.mu = dgroup.yplot
1532 return dict(xarr=xarr, ypop=ypop, yop=yop, yarr1=yarr1, yarr2=yarr2,
1533 monod=monod, en_units=en_units, yerr_op=yerr_op,
1534 yerr_val=yerr_val, yerr_arr=yerr_arr, yrpop=yrpop, yrop=yrop,
1535 yref1=yref1, yref2=yref2, has_yref=has_yref,
1536 expressions=exprs)
1538def energy_may_need_rebinning(workgroup):
1539 "test if energy may need rebinning"
1540 if getattr(workgroup, 'datatype', '?') != 'xas':
1541 return False
1542 en = getattr(workgroup, 'xplot', [-8.0e12])
1543 if len(en) < 2:
1544 return False
1545 if not isinstance(en, np.ndarray):
1546 en = np.array(en)
1547 if len(en) > 2000 or any(np.diff(en))< 0:
1548 return True
1549 if (len(en) > 200 and (max(en) - min(en)) > 350 and
1550 np.diff(en[:-100]).mean() < 1.0):
1551 return True