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

1#!/usr/bin/env python 

2""" 

3 

4""" 

5import re 

6from copy import deepcopy 

7 

8import numpy as np 

9np.seterr(all='ignore') 

10from pathlib import Path 

11from functools import partial 

12 

13import wx 

14import wx.lib.scrolledpanel as scrolled 

15import wx.lib.agw.flatnotebook as fnb 

16from wxmplot import PlotPanel 

17 

18from wxutils import (SimpleText, FloatCtrl, FloatSpin, GUIColors, Button, Choice, 

19 TextCtrl, pack, Popup, Check, MenuItem, CEN, RIGHT, LEFT, 

20 FRAMESTYLE, HLine, Font) 

21 

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 

31 

32CEN |= wx.ALL 

33FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS 

34FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG 

35 

36YPRE_OPS = ('', 'log(', '-log(', '-') 

37ARR_OPS = ('+', '-', '*', '/') 

38 

39YERR_OPS = ('Constant', 'Sqrt(Y)', 'Array') 

40CONV_OPS = ('Lorenztian', 'Gaussian') 

41 

42DATATYPES = ('xydata', 'xas') 

43ENUNITS_TYPES = ('eV', 'keV', 'degrees', 'not energy') 

44 

45 

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) 

49 

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

52 

53 Select columns for ROI (counts) and correction factors ICR, OCR, and LTIME for Channel 1. 

54 

55""" 

56 

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

59 

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 

64 

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) 

80 

81 self.SetFont(Font(FONTSIZE)) 

82 sizer = wx.GridBagSizer(2, 2) 

83 panel = scrolled.ScrolledPanel(self) 

84 

85 self.SetMinSize((650, 450)) 

86 self.yarr_labels = [s for s in self.parent.yarr_labels] 

87 wids = self.wids = {} 

88 

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

99 

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

102 

103 wids['i0'].SetStringSelection(self.parent.yarr2.GetStringSelection()) 

104 

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) 

116 

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) 

120 

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) 

125 

126 def tlabel(t): 

127 return SimpleText(panel, label=t) 

128 

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) 

132 

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) 

138 

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) 

142 

143 ir += 1 

144 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3) 

145 

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) 

150 

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) 

156 

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) 

160 

161 ir += 1 

162 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 5), LEFT, 3) 

163 

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) 

168 

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) 

175 

176 pack(panel, sizer) 

177 panel.SetupScrolling() 

178 

179 mainsizer = wx.BoxSizer(wx.VERTICAL) 

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

181 

182 pack(self, mainsizer) 

183 self.Show() 

184 self.Raise() 

185 

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 

192 

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 

206 

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

212 

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) 

226 

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

234 

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) 

245 

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

257 

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) 

261 

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 

270 

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 

279 

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

288 

289 pchan = int(wids['plot_chan'].GetValue()) 

290 

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' 

298 

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 

318 

319 

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 

325 

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) 

329 

330 self.config = {'channels': [], 'i0': '1.0'} 

331 if config is not None: 

332 self.config.update(config) 

333 

334 self.SetFont(Font(FONTSIZE)) 

335 sizer = wx.GridBagSizer(2, 2) 

336 panel = scrolled.ScrolledPanel(self) 

337 

338 self.SetMinSize((475, 350)) 

339 self.yarr_labels = [s for s in self.parent.yarr_labels] 

340 wids = self.wids = {} 

341 

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

344 

345 wids['i0'].SetStringSelection(self.parent.yarr2.GetStringSelection()) 

346 

347 bpanel = wx.Panel(panel) 

348 bsizer = wx.BoxSizer(wx.HORIZONTAL) 

349 

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) 

355 

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) 

360 

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) 

364 

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) 

369 

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

381 

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) 

386 

387 pack(panel, sizer) 

388 panel.SetupScrolling() 

389 

390 mainsizer = wx.BoxSizer(wx.VERTICAL) 

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

392 

393 pack(self, mainsizer) 

394 self.Show() 

395 self.Raise() 

396 

397 

398 def onSelAll(self, event=None, *kws): 

399 for wname, wid in self.wids.items(): 

400 if wname.startswith('use_'): 

401 wid.SetValue(1) 

402 

403 def onSelNone(self, event=None, *kws): 

404 for wname, wid in self.wids.items(): 

405 if wname.startswith('use_'): 

406 wid.SetValue(0) 

407 

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

431 

432 

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

441 

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) 

445 

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) 

454 

455 self.config['channels'] = channels 

456 if callable(self.on_ok): 

457 self.on_ok(self.config) 

458 self.Destroy() 

459 

460 

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) 

469 

470 self.SetFont(Font(FONTSIZE)) 

471 sizer = wx.GridBagSizer(2, 2) 

472 panel = scrolled.ScrolledPanel(self) 

473 

474 self.SetMinSize((700, 450)) 

475 

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) 

486 

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

493 

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) 

501 

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) 

515 

516 cnew.Bind(wx.EVT_TEXT_ENTER, partial(self.update, index=i)) 

517 cnew.Bind(wx.EVT_KILL_FOCUS, partial(self.update, index=i)) 

518 

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

523 

524 

525 self.wids[f"{i}"] = cnew 

526 self.wids[f"ret_{i}"] = cret 

527 

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) 

534 

535 pack(panel, sizer) 

536 panel.SetupScrolling() 

537 

538 mainsizer = wx.BoxSizer(wx.VERTICAL) 

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

540 

541 pack(self, mainsizer) 

542 self.Show() 

543 self.Raise() 

544 

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) 

553 

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

563 

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) 

567 

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

572 

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) 

579 

580 if callable(self.on_ok): 

581 self.on_ok(array_labels) 

582 self.Destroy() 

583 

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 

591 

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

599 

600 self.array_labels = [l.lower() for l in group.array_labels] 

601 

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 

609 

610 # print("C : ", has_energy, self.workgroup.datatype, config) 

611 

612 if self.workgroup.datatype in (None, 'unknown'): 

613 self.workgroup.datatype = 'xas' if has_energy else 'xydata' 

614 

615 en_units = 'eV' if self.workgroup.datatype == 'xas' else 'unknown' 

616 

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) 

627 

628 if self.config['yarr2'] is None and 'i0' in self.array_labels: 

629 self.config['yarr2'] = 'i0' 

630 

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' 

636 

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' 

644 

645 if self.config['yref2'] is None and 'i1' in self.array_labels: 

646 self.config['yref2'] = 'i1' 

647 

648 use_trans = self.config.get('is_trans', False) or 'log' in self.config['ypop'] 

649 

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) 

654 

655 x0, y0 = parent.GetPosition() 

656 self.SetPosition((x0+60, y0+60)) 

657 

658 self.SetFont(Font(FONTSIZE)) 

659 panel = wx.Panel(self) 

660 self.SetMinSize((725, 700)) 

661 self.colors = GUIColors() 

662 

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) 

666 

667 # title row 

668 title = subtitle(message, colour=self.colors.title) 

669 

670 yarr_labels = self.yarr_labels = self.array_labels + ['1.0', ''] 

671 xarr_labels = self.xarr_labels = self.array_labels + ['_index'] 

672 

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

678 

679 self.datatype = Choice(panel, choices=DATATYPES, action=self.onUpdate, size=(150, -1)) 

680 self.datatype.SetStringSelection(self.workgroup.datatype) 

681 

682 self.en_units = Choice(panel, choices=ENUNITS_TYPES, 

683 action=self.onEnUnitsSelect, size=(150, -1)) 

684 

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) 

689 

690 self.is_trans = Check(panel, label='is transmission data?', 

691 default=use_trans, action=self.onTransCheck) 

692 

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

695 

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

704 

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

714 

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

729 

730 

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

745 

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) 

752 

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

763 

764 self.onTransCheck(is_trans=use_trans) 

765 self.onYrefCheck(has_yref=self.config['has_yref']) 

766 

767 

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

777 

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) 

787 

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

790 

791 sizer = wx.GridBagSizer(2, 2) 

792 sizer.Add(title, (0, 0), (1, 7), LEFT, 5) 

793 

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) 

798 

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) 

804 

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) 

808 

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) 

820 

821 

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) 

828 

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) 

834 

835 ir += 1 

836 sizer.Add(self.info_message, (ir, 0), (1, 5), LEFT, 1) 

837 

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) 

842 

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) 

849 

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) 

855 

856 ir +=1 

857 sizer.Add(bpanel, (ir, 0), (1, 5), LEFT, 3) 

858 

859 pack(panel, sizer) 

860 

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

866 

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 

874 

875 

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

880 

881 ftext.SetValue(group.text) 

882 ftext.SetFont(Font(FONTSIZE)) 

883 

884 textsizer = wx.BoxSizer(wx.VERTICAL) 

885 textsizer.Add(ftext, 1, LEFT|wx.GROW, 1) 

886 pack(textpanel, textsizer) 

887 

888 self.nb.AddPage(textpanel, ' Text of Data File ', True) 

889 self.nb.AddPage(self.plotpanel, ' Plot of Selected Arrays ', True) 

890 

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) 

895 

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) 

901 

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) 

906 

907 self.Show() 

908 self.Raise() 

909 self.onUpdate() 

910 

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) 

916 

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

936 

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

945 

946 

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) 

952 

953 

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

972 

973 

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) 

980 

981 if reader == 'read_specfile': 

982 if not is_specfile(path, require_multiple_scans=True): 

983 reader = 'read_ascii' 

984 

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 

998 

999 if ('has nans' in nan_result.message or 

1000 'has infs' in nan_result.message): 

1001 reader = 'read_ascii' 

1002 

1003 tmpname = '_tmpfile_' 

1004 read_cmd = "%s = %s('%s')" % (tmpname, reader, path) 

1005 self.reader = reader 

1006 _larch = self._larch 

1007 

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' 

1023 

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) 

1029 

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) 

1035 

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 

1042 

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

1055 

1056 

1057 def onEditNames(self, evt=None): 

1058 self.show_subframe('editcol', EditColumnFrame, 

1059 group=self.workgroup, 

1060 on_ok=self.set_array_labels) 

1061 

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

1079 

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

1088 

1089 self.expressions = conf['expressions'] 

1090 filename = conf['filename'] 

1091 groupname = conf['groupname'] 

1092 

1093 conf['array_labels'] = self.workgroup.array_labels 

1094 

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

1101 

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

1106 

1107 buff.append("{group}.datatype = '%s'" % (conf['datatype'])) 

1108 

1109 for attr in ('plot_xlabel', 'plot_ylabel'): 

1110 val = getattr(self.workgroup, attr) 

1111 buff.append("{group}.%s = '%s'" % (attr, val)) 

1112 

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

1123 

1124 for aname in ('yplot', 'yerr'): 

1125 expr = self.expressions[aname] 

1126 buff.append(f"{ group} .{aname} = {expr}") 

1127 

1128 

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

1141 

1142 array_desc = dict(xplot=self.workgroup.plot_xlabel, 

1143 yplot=self.workgroup.plot_ylabel, 

1144 yerr=self.expressions['yerr']) 

1145 

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

1152 

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

1159 

1160 script = "\n".join(buff) 

1161 conf['array_desc'] = array_desc 

1162 

1163 if self.read_ok_cb is not None: 

1164 self.read_ok_cb(script, self.path, conf) 

1165 

1166 for f in self.subframes.values(): 

1167 try: 

1168 f.Destroy() 

1169 except: 

1170 pass 

1171 self.Destroy() 

1172 

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

1181 

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

1191 

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 

1203 

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) 

1213 

1214 

1215 def onXSelect(self, evt=None): 

1216 ix = self.xarr.GetSelection() 

1217 xname = self.xarr.GetStringSelection() 

1218 

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

1226 

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) 

1239 

1240 def onEnUnitsSelect(self, evt=None): 

1241 self.monod_val.Enable(self.en_units.GetStringSelection().startswith('deg')) 

1242 self.onUpdate() 

1243 

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 

1252 

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

1263 

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

1279 

1280 ypop = self.ypop.GetStringSelection().strip() 

1281 self.is_trans.SetValue('log' in ypop) 

1282 

1283 

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 

1313 

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 

1321 

1322 conf = self.read_form() 

1323 cout = create_arrays(workgroup, **conf) 

1324 self.expressions = cout.pop('expressions') 

1325 conf.update(cout) 

1326 

1327 if energy_may_need_rebinning(workgroup): 

1328 self.info_message.SetLabel("Warning: XAS data may need to be rebinned!") 

1329 

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) 

1335 

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

1343 

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

1345 if 'plot' in self.nb.GetPageText(i).lower(): 

1346 self.nb.SetSelection(i) 

1347 

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

1349 self.statusbar.SetStatusText(msg, panel) 

1350 

1351 

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) 

1362 

1363 if not hasattr(dgroup, 'index'): 

1364 dgroup.index = 1.0*np.arange(npts) 

1365 

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}, : ]' 

1373 

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

1382 

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 

1393 

1394 if yarr1 is None: 

1395 yarr1 = dgroup.array_labels[iy1] 

1396 

1397 if yarr2 is None: 

1398 yarr2 = dgroup.array_labels[iy2] 

1399 

1400 ylabel = yarr1 

1401 if len(yarr2) == 0: 

1402 yarr2 = '1.0' 

1403 else: 

1404 ylabel = f"{ylabel}{yop}{yarr2}" 

1405 

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}, : ]' 

1415 

1416 dgroup.yplot = ydarr1 

1417 exprs['yplot'] = yexpr1 

1418 

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}, : ]' 

1428 

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 

1439 

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

1444 

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

1456 

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

1465 

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}, : ]' 

1475 

1476 dgroup.yref = ydrarr1 

1477 exprs['yref'] = yrexpr1 

1478 

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}, : ]' 

1488 

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 

1499 

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 

1505 

1506 

1507 try: 

1508 npts = min(len(dgroup.xplot), len(dgroup.yplot)) 

1509 except AttributeError: 

1510 return 

1511 except ValueError: 

1512 return 

1513 

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 

1527 

1528 if dgroup.datatype == 'xas': 

1529 dgroup.energy = dgroup.xplot 

1530 dgroup.mu = dgroup.yplot 

1531 

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) 

1537 

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