Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/xrfdisplay_fitpeaks.py: 9%
1080 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1#!/usr/bin/env python
2"""
3fitting GUI for XRF display
4"""
5import os
6import sys
7import time
8import copy
9from functools import partial
10from threading import Thread
12import json
13import numpy as np
14import wx
15import wx.lib.agw.pycollapsiblepane as CP
16import wx.lib.scrolledpanel as scrolled
17import wx.dataview as dv
18DVSTYLE = dv.DV_SINGLE|dv.DV_VERT_RULES|dv.DV_ROW_LINES
20from peakutils import peak
22from lmfit import Parameter, Minimizer
24from wxutils import (SimpleText, FloatCtrl, FloatSpin, Choice, Font, pack,
25 Button, Check, HLine, GridPanel, RowPanel, CEN, LEFT,
26 RIGHT, FileSave, GUIColors, FRAMESTYLE, BitmapButton,
27 SetTip, GridPanel, Popup, FloatSpinWithPin, get_icon,
28 fix_filename, flatnotebook, PeriodicTablePanel)
30from . import FONTSIZE, FONTSIZE_FW
31from xraydb import (material_mu, xray_edge, materials, add_material,
32 atomic_number, atomic_symbol, xray_line)
33# from .notebooks import flatnotebook
34# from .periodictable import PeriodicTablePanel
35from .parameter import ParameterPanel
37from larch import Group
39from ..xrf import xrf_background, MCA, FanoFactors
40from ..utils import json_dump, json_load, gformat
41from ..utils.jsonutils import encode4js, decode4js
42from ..utils.physical_constants import E_MASS
43from ..site_config import user_larchdir
44from .xrfdisplay_utils import (XRFGROUP, mcaname, XRFRESULTS_GROUP,
45 MAKE_XRFRESULTS_GROUP)
47def read_filterdata(flist, _larch):
48 """ read filters data"""
49 materials = _larch.symtable.get_symbol('_xray._materials')
50 out = {}
51 out['None'] = ('', 0)
52 for name in flist:
53 if name in materials:
54 out[name] = materials[name]
55 return out
57def VarChoice(p, default=0, size=(75, -1)):
58 if default in (False, 'False', 'Fix', 'No'):
59 default = 0
60 else:
61 default = 1
62 return Choice(p, choices=['Fix', 'Vary'],
63 size=size, default=default)
65NFILTERS = 4
66MIN_CORREL = 0.10
68tooltips = {'ptable': 'Select Elements to include in model',
69 'cen': 'centroid of peak',
70 'step': 'size of step extending to low energy side of peak, fraction of peak height',
71 'gamma': 'gamma (lorentzian-like weight) of Voigt function',
72 'tail': 'intensity of tail function at low energy side of peak',
73 'beta': 'width of tail function at low energy side of peak',
74 'sigma': 'scale sigma from Energy/Noise by this amount',
75 }
77CompositionUnits = ('ng/mm^2', 'wt %', 'ppm')
79Detector_Materials = ['Si', 'Ge']
80EFano_Text = 'Peak Widths: sigma = sqrt(E_Fano * Energy + Noise**2) '
81Geom_Text = 'Angles in degrees: 90=normal to surface, 0=grazing surface'
82Energy_Text = 'All energies in keV'
85FitTols = ['1.e-2', '3.e-3', '1.e-3', '3.e-4', '1.e-4', '3.e-5', '1.e-5',
86 '3.e-6', '1.e-6', '3.e-7', '1.e-7']
87FitSteps = ['1.e-2', '3.e-3', '1.e-3', '3.e-4', '1.e-4', '3.e-5', '1.e-5',
88 '3.e-6', '1.e-6', '3.e-7', '1.e-7']
90FitMaxNFevs = ['200', '500', '1000', '1500', '2000', '3000', '5000', '10000']
92xrfmod_setup = """### XRF Model: {mca_label:s} @ {datetime:s}
93# mca data group for fit:
94{XRFGROUP}.workmca = {mcagroup}
96# setup XRF Model:
97_xrfmodel = xrf_model(xray_energy={en_xray:.2f}, count_time={count_time:.5f},
98 energy_min={en_min:.2f}, energy_max={en_max:.2f})
100_xrfmodel.set_detector(thickness={det_thk:.5f}, material='{det_mat:s}',
101 cal_offset={cal_offset:.5f}, cal_slope={cal_slope:.5f},
102 vary_cal_offset={cal_vary!r}, vary_cal_slope={cal_vary!r},
103 peak_step={peak_step:.5f}, vary_peak_step={peak_step_vary:s},
104 peak_tail={peak_tail:.5f}, vary_peak_tail={peak_tail_vary:s},
105 peak_beta={peak_beta:.5f}, vary_peak_beta={peak_beta_vary:s},
106 peak_gamma={peak_gamma:.5f}, vary_peak_gamma={peak_gamma_vary:s},
107 noise={det_noise:.5f}, vary_noise={det_noise_vary:s})"""
109xrfmod_scattpeak = """
110# add scatter peak
111_xrfmodel.add_scatter_peak(name='{peakname:s}', center={_cen:.2f},
112 amplitude=1e5, step={_step:.5f}, tail={_tail:.5f}, beta={_beta:.5f},
113 sigmax={_sigma:.5f}, vary_center={vcen:s}, vary_step={vstep:s},
114 vary_tail={vtail:s}, vary_beta={vbeta:s}, vary_sigmax={vsigma:s})"""
116xrfmod_fitscript = """
117# run XRF fit, save results
118_xrffitresult = _xrfmodel.fit_spectrum({XRFGROUP}.workmca, energy_min={emin:.2f}, energy_max={emax:.2f},
119 fit_toler={fit_toler:.6g}, fit_step={fit_step:.6g}, max_nfev={max_nfev:d})
120_xrfresults.insert(0, _xrffitresult)
121########
122"""
124xrfmod_filter = "_xrfmodel.add_filter('{name:s}', {thick:.5f}, vary_thickness={vary:s})"
125xrfmod_matrix = "_xrfmodel.set_matrix('{name:s}', {thick:.5f}, density={density:.5f})"
126xrfmod_pileup = "_xrfmodel.add_pileup(scale={scale:.3f}, vary={vary:s})"
127xrfmod_escape = "_xrfmodel.add_escape(scale={scale:.3f}, vary={vary:s})"
129xrfmod_savejs = "_xrfresults[{nfit:d}].save('{filename:s}')"
131xrfmod_elems = """
132# add elements
133for atsym in {elemlist:s}:
134 _xrfmodel.add_element(atsym)
135#endfor
136del atsym"""
138Filter_Lengths = ['microns', 'mm', 'cm']
139Filter_Materials = ['None', 'air', 'nitrogen', 'helium', 'kapton',
140 'beryllium', 'aluminum', 'mylar', 'pmma']
143DEF_CONFIG = { "mca_name": "", "escape_use": True, "escape_amp": 0.25,
144 "pileup_use": True, "pileup_amp": 0.1, "escape_amp_vary":
145 True, "pileup_amp_vary": True, "cal_slope": 0.01,
146 "cal_offset": 0, "cal_vary": True, "det_mat": "Si", "det_thk":
147 0.4, "det_noise_vary": True,
148 "en_xray": 30, "en_min": 2.5, "en_max": 30,
149 "flux_in": 1e9, "det_noise": 0.035, "angle_in": 45.0,
150 "angle_out": 45.0, "det_dist": 50.0, "det_area": 50.0,
151 "elements": [16, 17, 18, 20, 22, 24, 25, 26, 28, 29, 30],
152 "filter1_mat": "beryllium", "filter1_thk": 0.025, "filter1_var": False,
153 "filter2_mat": "air", "filter2_thk": 50.0, "filter2_var": False,
154 "filter3_mat": "kapton", "filter3_thk": 0.0, "filter3_var": False,
155 "filter4_mat": "aluminum", "filter4_thk": 0.0, "filter4_var": False,
156 "matrix_mat": "", "matrix_thk": 0.0, "matrix_den": 1.0,
157 "peak_step": 0.025, "peak_gamma": 0.05, "peak_tail": 0.1,
158 "peak_beta": 0.5, "peak_step_vary": False, "peak_tail_vary": False,
159 "peak_gamma_vary": False, "peak_beta_vary": False,
160 "elastic_use": True, "elastic_cen_vary": False, "elastic_step_vary": False,
161 "elastic_beta_vary": False, "elastic_tail_vary": False,
162 "elastic_sigma_vary": False, "elastic_cen": 30,
163 "elastic_step": 0.025, "elastic_tail": 0.1,
164 "elastic_beta": 0.5, "elastic_sigma": 1.0,
165 "compton1_use": True, "compton1_cen_vary": True,
166 "compton1_step_vary": True, "compton1_beta_vary": False,
167 "compton1_tail_vary": False, "compton1_sigma_vary": False,
168 "compton1_cen": 12.1875, "compton1_step": 0.025, "compton1_tail": 0.25,
169 "compton1_beta": 2.0, "compton1_sigma": 1.5,
170 "compton2_use": True, "compton2_cen_vary": True, "compton2_step_vary": False,
171 "compton2_beta_vary": False, "compton2_tail_vary": False,
172 "compton2_sigma_vary": False, "compton2_cen": 11.875,
173 "compton2_step": 0.025, "compton2_tail": 0.25, "compton2_beta": 2.5,
174 "compton2_sigma": 2.0, "count_time": 120.0,
175 }
177class FitSpectraFrame(wx.Frame):
178 """Frame for Spectral Analysis"""
180 def __init__(self, parent, size=(750, 850)):
181 self.parent = parent
182 self._larch = parent.larch
183 symtable = self._larch.symtable
184 # fetch current spectra from parent
185 if not symtable.has_group(XRFRESULTS_GROUP):
186 self._larch.eval(MAKE_XRFRESULTS_GROUP)
188 self.xrfresults = symtable.get_symbol(XRFRESULTS_GROUP)
189 xrfgroup = symtable.get_group(XRFGROUP)
190 mcagroup = getattr(xrfgroup, '_mca')
191 self.mca = getattr(xrfgroup, mcagroup)
192 self.mcagroup = f'{XRFGROUP}.{mcagroup}'
194 self.config = DEF_CONFIG
196 opts = getattr(xrfgroup, 'fitconfig', None)
197 if opts is None:
198 xrf_conffile = os.path.join(user_larchdir, 'xrf_fitconfig.json')
199 if os.path.exists(xrf_conffile):
200 opts = json_load(xrf_conffile)
201 if opts is not None:
202 self.config.update(opts)
204 efactor = 1.0 if max(self.mca.energy) < 250. else 1000.0
206 if self.mca.incident_energy is None:
207 self.mca.incident_energy = 20.0
208 if self.mca.incident_energy > 250:
209 self.mca.incident_energy /= 1000.0
211 self.nfit = 0
212 self.colors = GUIColors()
213 wx.Frame.__init__(self, parent, -1, 'Fit XRF Spectra',
214 size=size, style=wx.DEFAULT_FRAME_STYLE)
216 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.NORMAL) # fixed width
219 self.wids = {}
220 self.owids = {}
222 pan = GridPanel(self)
223 self.mca_label = self.mca.label
224 self.wids['mca_name'] = SimpleText(pan, self.mca_label, size=(300, -1), style=LEFT)
225 self.wids['btn_calc'] = Button(pan, 'Calculate Model', size=(150, -1),
226 action=self.onShowModel)
227 self.wids['btn_fit'] = Button(pan, 'Fit Model', size=(150, -1),
228 action=self.onFitModel)
230 pan.AddText(" XRF Spectrum: ", colour='#880000')
231 pan.Add(self.wids['mca_name'], dcol=3)
232 pan.Add(self.wids['btn_calc'], newrow=True)
233 pan.Add(self.wids['btn_fit'])
235 self.panels = {}
236 self.panels['Beam & Detector'] = self.beamdet_page
237 self.panels['Filters & Matrix'] = self.materials_page
238 self.panels['Elements & Peaks'] = self.elempeaks_page
239 self.panels['Fit Results'] = self.fitresult_page
240 self.panels['Composition'] = self.composition_page
242 self.nb = flatnotebook(pan, self.panels, on_change=self.onNBChanged)
243 pan.Add((5, 5), newrow=True)
244 pan.Add(self.nb, dcol=5, drow=10, newrow=True)
245 pan.pack()
247 self.Show()
248 self.Raise()
250 def onNBChanged(self, event=None):
251 pagelabel = self.nb._pages.GetPageText(event.GetSelection()).strip()
252 if pagelabel.startswith('Composition'):
253 self.UpdateCompositionPage()
255 def elempeaks_page(self, **kws):
256 "elements and peaks parameters"
257 mca = self.parent.mca
258 wids = self.wids
259 p = GridPanel(self)
260 self.selected_elems = []
261 self.ptable = PeriodicTablePanel(p, multi_select=True, fontsize=11,
262 size=(360, 180),
263 tooltip_msg=tooltips['ptable'],
264 onselect=self.onElemSelect)
265 cnf = self.config
266 for name, xmax, xinc in (('step', 1, 0.005),
267 ('gamma', 10, 0.01),
268 ('beta', 10, 0.01),
269 ('tail', 1.0, 0.05)):
270 fname = 'peak_%s' % name
271 vname = 'peak_%s_vary' % name
272 wids[fname] = FloatSpin(p, value=cnf[fname], digits=3,
273 min_val=0, max_val=xmax,
274 increment=xinc, tooltip=tooltips[name])
275 wids[vname] = VarChoice(p, default=cnf[vname])
277 btn_from_peaks = Button(p, 'Guess Peaks', size=(150, -1),
278 action=self.onElems_GuessPeaks)
279 # tooltip='Guess elements from peak locations')
280 btn_from_rois = Button(p, 'Use ROIS as Peaks', size=(150, -1),
281 action=self.onElems_FromROIS)
282 btn_clear_elems = Button(p, 'Clear All Peaks', size=(150, -1),
283 action=self.onElems_Clear)
284 wx.CallAfter(self.onElems_GuessPeaks)
286 p.AddText('Elements to model:', colour='#880000', dcol=2)
287 p.Add((2, 2), newrow=True)
288 p.Add(self.ptable, dcol=5, drow=5)
289 irow = p.irow
291 p.Add(btn_from_peaks, icol=6, dcol=2, irow=irow)
292 p.Add(btn_from_rois, icol=6, dcol=2, irow=irow+1)
293 p.Add(btn_clear_elems, icol=6, dcol=2, irow=irow+2)
294 p.irow += 5
296 p.Add((2, 2), newrow=True)
297 p.AddText(' Step: ')
298 p.Add(wids['peak_step'])
299 p.Add(wids['peak_step_vary'])
301 p.AddText(' Gamma : ')
302 p.Add(wids['peak_gamma'])
303 p.Add(wids['peak_gamma_vary'])
305 p.Add((2, 2), newrow=True)
306 p.AddText(' Beta: ')
307 p.Add(wids['peak_beta'])
308 p.Add(wids['peak_beta_vary'])
310 p.AddText(' Tail: ')
311 p.Add(wids['peak_tail'])
312 p.Add(wids['peak_tail_vary'])
313 p.Add((2, 2), newrow=True)
314 p.Add(HLine(p, size=(650, 3)), dcol=8)
315 p.Add((2, 2), newrow=True)
317 opts = dict(size=(100, -1), min_val=0, digits=4, increment=0.010)
318 for name, escale in (('elastic', 0), ('compton1', 1), ('compton2', 2)):
319 en = self.mca.incident_energy
320 for i in range(escale):
321 en = en * (1 - 1/(1 + (E_MASS*0.001)/en)) # Compton shift at 90 deg
323 wids[f'{name:s}_use'] = Check(p, label='Include', default=cnf[f'{name:s}_use'])
324 p.Add((2, 2), newrow=True)
325 p.AddText(" %s Peak:" % name.title(), colour='#880000')
326 p.Add(wids[f'{name:s}_use'], dcol=2)
328 for att, title, xmax, xinc, newrow in (('cen', ' Energy (kev): ', 9e12, 0.01, True),
329 ('step', ' Step: ', 1, 0.005, False),
330 ('sigma', ' Sigma Scale:', 10, 0.05, True),
331 ('beta', ' Beta: ', 10, 0.01, False),
332 ('tail', ' Tail: ', 10, 0.01, True)):
333 label = f'{name:s}_{att:s}'
334 val = en if att == 'cen' else cnf[label]
335 wids[f'{label:s}_vary'] = VarChoice(p, default=cnf[f'{label:s}_vary'])
337 wids[label] = FloatSpin(p, value=val, digits=3, min_val=0,
338 max_val=xmax, increment=xinc,
339 tooltip=tooltips[att])
341 p.AddText(title)
342 p.Add(wids[label])
343 p.Add(wids[f'{label:s}_vary'])
344 if newrow:
345 p.Add((2, 2), newrow=True)
347 p.Add(HLine(p, size=(650, 3)), dcol=7)
349 p.pack()
350 return p
352 def beamdet_page(self, **kws):
353 "beam / detector settings"
354 mca = self.mca
355 en_min = 2.0
356 en_max = self.mca.incident_energy
358 cal_offset = getattr(mca, 'offset', 0)
359 cal_slope = getattr(mca, 'slope', 0.010)
360 det_noise = getattr(mca, 'det_noise', 0.035)
361 escape_amp = getattr(mca, 'escape_amp', 0.10)
363 if not hasattr(self.mca, 'pileup_scale'):
364 self.mca.predict_pileup()
365 pileup_amp = max(0.001, getattr(mca, 'pileup_scale', 0.05))
367 wids = self.wids
368 pdet = GridPanel(self, itemstyle=LEFT)
370 def addLine(pan):
371 pan.Add(HLine(pan, size=(650, 3)), dcol=6, newrow=True)
374 wids['escape_use'] = Check(pdet, label='Include Escape in Fit',
375 default=True, action=self.onUsePileupEscape)
376 wids['escape_amp'] = FloatSpin(pdet, value=escape_amp,
377 min_val=0, max_val=100, digits=3,
378 increment=0.01, size=(100, -1))
380 wids['pileup_use'] = Check(pdet, label='Include Pileup in Fit',
381 default=True,
382 action=self.onUsePileupEscape)
383 wids['pileup_amp'] = FloatSpin(pdet, value=pileup_amp,
384 min_val=0, max_val=100, digits=3,
385 increment=0.01, size=(100, -1))
387 wids['escape_amp_vary'] = VarChoice(pdet, default=True)
388 wids['pileup_amp_vary'] = VarChoice(pdet, default=(pileup_amp>0.002))
391 wids['cal_slope'] = FloatSpin(pdet, value=cal_slope,
392 min_val=0, max_val=100,
393 digits=4, increment=0.01, size=(100, -1))
394 wids['cal_offset'] = FloatSpin(pdet, value=cal_offset,
395 min_val=-500, max_val=500,
396 digits=4, increment=0.01, size=(100, -1))
398 wids['cal_vary'] = Check(pdet, label='Vary Calibration in Fit', default=True)
400 wids['det_mat'] = Choice(pdet, choices=Detector_Materials,
401 size=(70, -1), default=0,
402 action=self.onDetMaterial)
404 wids['det_thk'] = FloatSpin(pdet, value=0.400, size=(100, -1),
405 increment=0.010, min_val=0, max_val=10,
406 digits=4)
408 wids['det_noise_vary'] = VarChoice(pdet, default=1)
410 opts = dict(size=(100, -1), min_val=0, max_val=500, digits=3,
411 increment=0.10)
412 wids['en_xray'] = FloatSpin(pdet, value=self.mca.incident_energy,
413 action=self.onSetXrayEnergy, **opts)
414 wids['en_min'] = FloatSpin(pdet, value=en_min, **opts)
415 wids['en_max'] = FloatSpin(pdet, value=en_max, **opts)
416 wids['flux_in'] = FloatCtrl(pdet, value=5.e10, gformat=True,
417 minval=0, size=(100, -1))
419 opts.update({'increment': 0.005})
420 wids['det_noise'] = FloatSpin(pdet, value=det_noise, **opts)
421 wids['det_efano'] = SimpleText(pdet, size=(200, -1),
422 label='E_Fano= %.4e' % FanoFactors['Si'])
424 opts.update(digits=1, max_val=90, min_val=0, increment=1)
425 wids['angle_in'] = FloatSpin(pdet, value=45, **opts)
426 wids['angle_out'] = FloatSpin(pdet, value=45, **opts)
428 opts.update(digits=1, max_val=5e9, min_val=0, increment=1)
429 wids['det_dist'] = FloatSpin(pdet, value=50, **opts)
430 wids['det_area'] = FloatSpin(pdet, value=50, **opts)
432 for notyet in ('angle_in', 'angle_out', 'det_dist', 'det_area',
433 'flux_in'):
434 wids[notyet].Disable()
436 wids['fit_toler'] = Choice(pdet, choices=FitTols, size=(90, -1))
437 wids['fit_toler'].SetStringSelection('1.e-4')
438 wids['fit_step'] = Choice(pdet, choices=FitSteps, size=(90, -1))
439 wids['fit_step'].SetStringSelection('1.e-4')
440 wids['fit_maxnfev'] = Choice(pdet, choices=FitMaxNFevs, size=(90, -1))
441 wids['fit_maxnfev'].SetStringSelection('1000')
443 pdet.AddText(' Beam Energy, Fit Range :', colour='#880000', dcol=2)
444 pdet.AddText(' X-ray Energy (keV): ', newrow=True)
445 pdet.Add(wids['en_xray'])
446 pdet.AddText('Incident Flux (Hz): ', newrow=False)
447 pdet.Add(wids['flux_in'])
448 pdet.AddText(' Fit Energy Min (keV): ', newrow=True)
449 pdet.Add(wids['en_min'])
450 pdet.AddText('Fit Energy Max (keV): ')
451 pdet.Add(wids['en_max'])
452 pdet.AddText(' Fit Step Size: ', newrow=True)
453 pdet.Add(wids['fit_step'])
454 pdet.AddText('Fit Tolerance: ')
455 pdet.Add(wids['fit_toler'])
456 pdet.AddText(' Fit Max Evaluations: ', newrow=True)
457 pdet.Add(wids['fit_maxnfev'])
460 addLine(pdet)
461 pdet.AddText(' Energy Calibration :', colour='#880000', dcol=1, newrow=True)
462 pdet.Add(wids['cal_vary'], dcol=2)
463 pdet.AddText(' Offset (keV): ', newrow=True)
464 pdet.Add(wids['cal_offset'])
465 pdet.AddText('Slope (keV/bin): ')
466 pdet.Add(wids['cal_slope'])
468 addLine(pdet)
469 pdet.AddText(' Detector Material:', colour='#880000', dcol=1, newrow=True)
470 pdet.AddText(EFano_Text, dcol=3)
471 pdet.AddText(' Material: ', newrow=True)
472 pdet.Add(wids['det_mat'])
473 pdet.Add(wids['det_efano'], dcol=2)
474 pdet.AddText(' Thickness (mm): ', newrow=True)
475 pdet.Add(wids['det_thk'])
476 pdet.AddText(' Noise (keV): ', newrow=True)
477 pdet.Add(wids['det_noise'])
478 pdet.Add(wids['det_noise_vary'], dcol=2)
481 addLine(pdet)
482 pdet.AddText(' Escape && Pileup:', colour='#880000', dcol=2, newrow=True)
483 pdet.AddText(' Escape Scale:', newrow=True)
484 pdet.Add(wids['escape_amp'])
485 pdet.Add(wids['escape_amp_vary'])
486 pdet.Add(wids['escape_use'], dcol=3)
488 pdet.AddText(' Pileup Scale:', newrow=True)
489 pdet.Add(wids['pileup_amp'])
490 pdet.Add(wids['pileup_amp_vary'])
491 pdet.Add(wids['pileup_use'], dcol=3)
493 addLine(pdet)
494 pdet.AddText(' Geometry:', colour='#880000', dcol=1, newrow=True)
495 pdet.AddText(Geom_Text, dcol=3)
496 pdet.AddText(' Incident Angle (deg):', newrow=True)
497 pdet.Add(wids['angle_in'])
498 pdet.AddText(' Exit Angle (deg):', newrow=False)
499 pdet.Add(wids['angle_out'])
500 pdet.AddText(' Detector Distance (mm): ', newrow=True)
501 pdet.Add(wids['det_dist'])
502 pdet.AddText(' Detector Area (mm^2): ', newrow=False)
503 pdet.Add(wids['det_area'])
506 addLine(pdet)
507 pdet.pack()
508 return pdet
510 def materials_page(self, **kws):
511 "filters and matrix settings"
512 wids = self.wids
513 pan = GridPanel(self, itemstyle=LEFT)
515 pan.AddText(' Filters :', colour='#880000', dcol=2) # , newrow=True)
516 pan.AddManyText((' Filter #', 'Material', 'Thickness (mm)',
517 'Vary Thickness'), style=LEFT, newrow=True)
518 opts = dict(size=(125, -1), min_val=0, digits=5, increment=0.005)
520 for i in range(NFILTERS):
521 t = 'filter%d' % (i+1)
522 wids['%s_mat'%t] = Choice(pan, choices=Filter_Materials, default=0,
523 size=(150, -1),
524 action=partial(self.onFilterMaterial, index=i+1))
525 wids['%s_thk'%t] = FloatSpin(pan, value=0.0, **opts)
526 wids['%s_var'%t] = VarChoice(pan, default=0)
527 if i == 0: # first selection
528 wids['%s_mat'%t].SetStringSelection('beryllium')
529 wids['%s_thk'%t].SetValue(0.0250)
530 elif i == 1: # second selection
531 wids['%s_mat'%t].SetStringSelection('air')
532 wids['%s_thk'%t].SetValue(50.00)
533 elif i == 2: # third selection
534 wids['%s_mat'%t].SetStringSelection('kapton')
535 wids['%s_thk'%t].SetValue(0.00)
536 elif i == 3: # third selection
537 wids['%s_mat'%t].SetStringSelection('aluminum')
538 wids['%s_thk'%t].SetValue(0.00)
540 pan.AddText(' %i' % (i+1), newrow=True)
541 pan.Add(wids['%s_mat' % t])
542 pan.Add(wids['%s_thk' % t])
543 pan.Add(wids['%s_var' % t])
545 pan.Add(HLine(pan, size=(650, 3)), dcol=6, newrow=True)
547 pan.AddText(' Matrix:', colour='#880000', newrow=True)
548 pan.AddText(' NOTE: thin film limit only', dcol=3)
550 wids['matrix_mat'] = wx.TextCtrl(pan, value='', size=(275, -1))
551 wids['matrix_thk'] = FloatSpin(pan, value=0.0, **opts)
552 wids['matrix_den'] = FloatSpin(pan, value=1.0, **opts)
553 wids['matrix_btn'] = Button(pan, 'Use Material', size=(175, -1),
554 action=self.onUseCurrentMaterialAsFilter)
555 wids['matrix_btn'].Disable()
556 pan.AddText(' Material/Formula:', dcol=1, newrow=True)
557 pan.Add(wids['matrix_mat'], dcol=2)
558 pan.Add(wids['matrix_btn'], dcol=3)
559 pan.AddText(' Thickness (mm):', newrow=True)
560 pan.Add(wids['matrix_thk'])
561 pan.AddText(' Density (gr/cm^3):', newrow=False)
562 pan.Add(wids['matrix_den'])
564 pan.Add(HLine(pan, size=(650, 3)), dcol=6, newrow=True)
566 # Materials
567 pan.AddText(' Known Materials:', colour='#880000', dcol=4, newrow=True)
569 mview = self.owids['materials'] = dv.DataViewListCtrl(pan, style=DVSTYLE)
570 mview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectMaterial)
571 self.selected_material = ''
573 mview.AppendTextColumn('Name', width=150)
574 mview.AppendTextColumn('Formula', width=325)
575 mview.AppendTextColumn('density', width=90)
576 mview.AppendToggleColumn('Filter?', width=75)
577 for col in range(4):
578 this = mview.Columns[col]
579 align = wx.ALIGN_LEFT
580 this.Sortable = True
581 this.Alignment = this.Renderer.Alignment = align
583 mview.SetMinSize((725, 170))
584 mview.DeleteAllItems()
585 self.materials_data = {}
586 for name, data in materials._read_materials_db().items():
587 # print("DATA " , name, data)
588 formula, density = data.formula, data.density
589 self.materials_data[name] = (formula, density)
590 mview.AppendItem((name, formula, "%9.6f"%density,
591 name in Filter_Materials))
592 pan.Add(mview, dcol=5, newrow=True)
594 pan.AddText(' Add Material:', colour='#880000', newrow=True)
595 pan.Add(Button(pan, 'Add', size=(175, -1),
596 action=self.onAddMaterial))
597 pan.Add((10, 10))
598 bx = Button(pan, 'Update Filter List', size=(175, -1),
599 action=self.onUpdateFilterList)
600 pan.Add(bx)
602 self.owids['newmat_name'] = wx.TextCtrl(pan, value='', size=(175, -1))
603 self.owids['newmat_dens'] = FloatSpin(pan, value=1.0, **opts)
604 self.owids['newmat_form'] = wx.TextCtrl(pan, value='', size=(400, -1))
607 for notyet in ('matrix_mat', 'matrix_thk', 'matrix_den',
608 'matrix_btn'):
609 wids[notyet].Disable()
611 pan.AddText(' Name:', newrow=True)
612 pan.Add(self.owids['newmat_name'])
613 pan.AddText(' Density (gr/cm^3):', newrow=False)
614 pan.Add(self.owids['newmat_dens'])
615 pan.AddText(' Formula:', newrow=True)
616 pan.Add(self.owids['newmat_form'], dcol=3)
617 pan.pack()
618 return pan
620 def fitresult_page(self, **kws):
621 sizer = wx.GridBagSizer(10, 5)
622 panel = scrolled.ScrolledPanel(self)
623 # title row
624 wids = self.owids
625 title = SimpleText(panel, 'Fit Results', font=Font(FONTSIZE+1),
626 colour=self.colors.title, style=LEFT)
628 wids['data_title'] = SimpleText(panel, '< > ', font=Font(FONTSIZE+1),
629 colour=self.colors.title, style=LEFT)
631 wids['fitlabel_lab'] = SimpleText(panel, 'Fit Label:')
632 wids['fitlabel_txt'] = wx.TextCtrl(panel, -1, ' ', size=(150, -1))
633 wids['fitlabel_btn'] = Button(panel, 'Set Label', size=(150, -1),
634 action=self.onChangeFitLabel)
636 opts = dict(default=False, size=(175, -1), action=self.onPlot)
637 wids['plot_comps'] = Check(panel, label='Show Components?', **opts)
638 self.plot_choice = Button(panel, 'Plot',
639 size=(150, -1), action=self.onPlot)
641 self.save_result = Button(panel, 'Save Model',
642 size=(150, -1), action=self.onSaveFitResult)
643 SetTip(self.save_result, 'save model and result to be loaded later')
645 self.export_fit = Button(panel, 'Export Fit',
646 size=(150, -1), action=self.onExportFitResult)
647 SetTip(self.export_fit, 'save arrays and results to text file')
649 irow = 0
650 sizer.Add(title, (irow, 0), (1, 1), LEFT)
651 sizer.Add(wids['data_title'], (irow, 1), (1, 3), LEFT)
653 irow += 1
654 sizer.Add(self.save_result, (irow, 0), (1, 1), LEFT)
655 sizer.Add(self.export_fit, (irow, 1), (1, 1), LEFT)
656 sizer.Add(self.plot_choice, (irow, 2), (1, 1), LEFT)
657 sizer.Add(wids['plot_comps'], (irow, 3), (1, 1), LEFT)
659 irow += 1
660 sizer.Add(wids['fitlabel_lab'], (irow, 0), (1, 1), LEFT)
661 sizer.Add(wids['fitlabel_txt'], (irow, 1), (1, 1), LEFT)
662 sizer.Add(wids['fitlabel_btn'], (irow, 2), (1, 2), LEFT)
665 irow += 1
666 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT)
668 irow += 1
669 title = SimpleText(panel, '[[Fit Statistics]]', font=Font(FONTSIZE+1),
670 colour=self.colors.title, style=LEFT)
671 sizer.Add(title, (irow, 0), (1, 4), LEFT)
673 sview = wids['stats'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
674 sview.SetFont(self.font_fixedwidth)
675 sview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectFit)
676 sview.AppendTextColumn('Fit Label', width=120)
677 sview.AppendTextColumn('N_vary', width=80)
678 sview.AppendTextColumn('N_eval', width=80)
679 sview.AppendTextColumn('\u03c7\u00B2', width=130)
680 sview.AppendTextColumn('\u03c7\u00B2_reduced', width=130)
681 sview.AppendTextColumn('Akaike Info', width=130)
683 for col in range(sview.ColumnCount):
684 this = sview.Columns[col]
685 isort, align = True, wx.ALIGN_RIGHT
686 if col == 0:
687 align = wx.ALIGN_LEFT
688 this.Sortable = isort
689 this.Alignment = this.Renderer.Alignment = align
690 sview.SetMinSize((725, 150))
692 irow += 1
693 sizer.Add(sview, (irow, 0), (1, 5), LEFT)
695 irow += 1
696 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT)
698 irow += 1
699 title = SimpleText(panel, '[[Variables]]', font=Font(FONTSIZE+1),
700 colour=self.colors.title, style=LEFT)
701 sizer.Add(title, (irow, 0), (1, 1), LEFT)
703 pview = wids['params'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
704 pview.SetFont(self.font_fixedwidth)
705 wids['paramsdata'] = []
706 pview.AppendTextColumn('Parameter', width=150)
707 pview.AppendTextColumn('Refined Value', width=130)
708 pview.AppendTextColumn('Standard Error', width=130)
709 pview.AppendTextColumn('% Uncertainty', width=130)
710 pview.AppendTextColumn('Initial Value', width=130)
712 for col in range(4):
713 this = pview.Columns[col]
714 align = wx.ALIGN_LEFT
715 if col > 0:
716 align = wx.ALIGN_RIGHT
717 this.Sortable = False
718 this.Alignment = this.Renderer.Alignment = align
720 pview.SetMinSize((725, 200))
721 pview.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.onSelectParameter)
723 irow += 1
724 sizer.Add(pview, (irow, 0), (1, 5), LEFT)
726 irow += 1
727 sizer.Add(HLine(panel, size=(650, 3)), (irow, 0), (1, 5), LEFT)
729 irow += 1
730 title = SimpleText(panel, '[[Correlations]]', font=Font(FONTSIZE+1),
731 colour=self.colors.title, style=LEFT)
733 wids['all_correl'] = Button(panel, 'Show All',
734 size=(100, -1), action=self.onAllCorrel)
736 wids['min_correl'] = FloatSpin(panel, value=MIN_CORREL,
737 min_val=0, size=(100, -1),
738 digits=3, increment=0.1)
740 ctitle = SimpleText(panel, 'minimum correlation: ')
741 sizer.Add(title, (irow, 0), (1, 1), LEFT)
742 sizer.Add(ctitle, (irow, 1), (1, 1), LEFT)
743 sizer.Add(wids['min_correl'], (irow, 2), (1, 1), LEFT)
744 sizer.Add(wids['all_correl'], (irow, 3), (1, 1), LEFT)
746 cview = wids['correl'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
747 cview.SetFont(self.font_fixedwidth)
748 cview.AppendTextColumn('Parameter 1', width=150)
749 cview.AppendTextColumn('Parameter 2', width=150)
750 cview.AppendTextColumn('Correlation', width=150)
752 for col in (0, 1, 2):
753 this = cview.Columns[col]
754 this.Sortable = False
755 align = wx.ALIGN_LEFT
756 if col == 2:
757 align = wx.ALIGN_RIGHT
758 this.Alignment = this.Renderer.Alignment = align
759 cview.SetMinSize((725, 125))
761 irow += 1
762 sizer.Add(cview, (irow, 0), (1, 5), LEFT)
763 pack(panel, sizer)
764 panel.SetMinSize((725, 750))
765 panel.SetupScrolling()
766 return panel
768 def composition_page(self, **kws):
769 sizer = wx.GridBagSizer(10, 5)
770 panel = scrolled.ScrolledPanel(self)
771 wids = self.owids
772 title = SimpleText(panel, 'Composition Results', font=Font(FONTSIZE+1),
773 colour=self.colors.title, style=LEFT)
774 wids['data_title2'] = SimpleText(panel, '< > ', font=Font(FONTSIZE+1),
775 colour=self.colors.title, style=LEFT)
777 cview = wids['composition'] = dv.DataViewListCtrl(panel, style=DVSTYLE)
778 cview.SetFont(self.font_fixedwidth)
779 cview.AppendTextColumn(' Z ', width=50)
780 cview.AppendTextColumn(' Element ', width=100)
781 cview.AppendTextColumn(' Amplitude', width=170)
782 cview.AppendTextColumn(' Concentration', width=170)
783 cview.AppendTextColumn(' Uncertainty', width=180)
785 for col in range(5):
786 this = cview.Columns[col]
787 align = wx.ALIGN_RIGHT
788 if col == 1:
789 align = wx.ALIGN_LEFT
790 this.Sortable = True
791 this.Alignment = this.Renderer.Alignment = align
793 cview.SetMinSize((725, 500))
794 wids['comp_fitlabel'] = Choice(panel, choices=[''], size=(175, -1),
795 action=self.onCompSelectFit)
797 self.compscale_lock = 0.0
798 wids['comp_elemchoice'] = Choice(panel, choices=[''], size=(100, -1))
799 # action=self.onCompSetElemAbundance)
800 wids['comp_elemscale'] = FloatSpin(panel, value=1.0, digits=5, min_val=0,
801 increment=0.01,
802 action=self.onCompSetElemAbundance)
803 wids['comp_units'] = Choice(panel, choices=CompositionUnits, size=(100, -1))
804 wids['comp_scale'] = FloatCtrl(panel, value=0, size=(200, -1), precision=5,
805 minval=0, action=self.onCompSetScale)
807 wids['comp_save'] = Button(panel, 'Save This Concentration Data',
808 size=(200, -1), action=self.onCompSave)
810 irow = 0
811 sizer.Add(title, (irow, 0), (1, 2), LEFT)
812 sizer.Add(wids['data_title2'], (irow, 2), (1, 5), LEFT)
813 irow += 1
814 sizer.Add(SimpleText(panel, 'Fit Label:'), (irow, 0), (1, 1), LEFT)
815 sizer.Add(wids['comp_fitlabel'], (irow, 1), (1, 5), LEFT)
817 irow += 1
818 sizer.Add(SimpleText(panel, 'Scale Element:'), (irow, 0), (1, 1), LEFT)
819 sizer.Add(wids['comp_elemchoice'], (irow, 1), (1, 1), LEFT)
820 sizer.Add(SimpleText(panel, ' to:'), (irow, 2), (1, 1), LEFT)
821 sizer.Add(wids['comp_elemscale'], (irow, 3), (1, 1), LEFT)
822 sizer.Add(wids['comp_units'], (irow, 4), (1, 1), LEFT)
824 irow += 1
825 sizer.Add(SimpleText(panel, 'Scaling Factor:'), (irow, 0), (1, 1), LEFT)
826 sizer.Add(wids['comp_scale'], (irow, 1), (1, 3), LEFT)
828 irow += 1
829 sizer.Add(wids['composition'], (irow, 0), (3, 6), LEFT)
831 irow += 3
832 sizer.Add(wids['comp_save'], (irow, 0), (1, 3), LEFT)
834 pack(panel, sizer)
835 panel.SetMinSize((725, 750))
836 panel.SetupScrolling()
837 return panel
839 def onCompSetScale(self, event=None, value=None):
840 if len(self.xrfresults) < 1 or (time.time() - self.compscale_lock) < 0.25:
841 return
842 self.compscale_lock = time.time()
843 owids = self.owids
844 result = self.get_fitresult(nfit=owids['comp_fitlabel'].GetSelection())
845 cur_elem = owids['comp_elemchoice'].GetStringSelection()
846 conc_vals = {}
847 for elem in result.comps.keys():
848 parname = 'amp_%s' % elem.lower()
849 if parname in result.params:
850 par = result.params[parname]
851 conc_vals[elem] = [par.value, par.stderr]
853 try:
854 scale = self.owids['comp_scale'].GetValue()
855 except:
856 return
858 owids['comp_elemscale'].SetValue(conc_vals[cur_elem][0]*scale)
859 owids['composition'].DeleteAllItems()
860 result.concentration_results = conc_vals
861 result.concentration_scale = scale
863 for elem, dat in conc_vals.items():
864 zat = "%d" % atomic_number(elem)
865 val, serr = dat
866 rval = "%15.4f" % val
867 sval = "%15.4f" % (val*scale)
868 uval = "%15.4f" % (serr*scale)
869 try:
870 uval = uval + ' ({:.2%})'.format(abs(serr/val))
871 except ZeroDivisionError:
872 pass
873 owids['composition'].AppendItem((zat, elem, rval, sval, uval))
875 def onCompSetElemAbundance(self, event=None, value=None):
876 if len(self.xrfresults) < 1 or (time.time() - self.compscale_lock) < 0.25:
877 return
878 self.compscale_lock = time.time()
879 owids = self.owids
880 result = self.get_fitresult(nfit=owids['comp_fitlabel'].GetSelection())
881 cur_elem = owids['comp_elemchoice'].GetStringSelection()
882 conc_vals = {}
883 for elem in result.comps.keys():
884 parname = 'amp_%s' % elem.lower()
885 if parname in result.params:
886 par = result.params[parname]
887 conc_vals[elem] = [par.value, par.stderr]
889 result.concentration_results = conc_vals
890 elem_value = owids['comp_elemscale'].GetValue()
892 scale = elem_value/conc_vals[cur_elem][0]
893 result.concentration_scale = scale
894 owids['comp_scale'].SetValue(scale)
895 owids['composition'].DeleteAllItems()
896 for elem, dat in conc_vals.items():
897 zat = "%d" % atomic_number(elem)
898 val, serr = dat
899 rval = "%15.4f" % val
900 sval = "%15.4f" % (val*scale)
901 uval = "%15.4f" % (serr*scale)
902 try:
903 uval = uval + ' ({:.2%})'.format(abs(serr/val))
904 except ZeroDivisionError:
905 pass
906 owids['composition'].AppendItem((zat, elem, rval, sval, uval))
909 def onCompSave(self, event=None):
910 result = self.get_fitresult(nfit=self.owids['comp_fitlabel'].GetSelection())
911 scale = result.concentration_scale
912 deffile = self.mca.label + '_' + result.label
913 deffile = fix_filename(deffile.replace('.', '_')) + '_xrf.csv'
914 wcards = "CSV (*.csv)|*.csv|All files (*.*)|*.*"
915 sfile = FileSave(self, 'Save Concentration Results',
916 default_file=deffile,
917 wildcard=wcards)
918 if sfile is not None:
919 buff = ["# results for MCA labeled: %s" % self.mca.label,
920 "# fit label: %s" % result.label,
921 "# concentration units: %s" % self.owids['comp_units'].GetStringSelection(),
922 "# count time: %s" % result.count_time,
923 "# scale: %s" % result.concentration_scale,
924 "# Fit Report:" ]
925 for l in result.fit_report.split('\n'):
926 buff.append("# %s" % l)
927 buff.append("###########")
928 buff.append("#Element Concentration Uncertainty Raw_Amplitude")
929 for elem, dat in result.concentration_results.items():
930 eout = (elem + ' '*4)[:4]
931 val, serr = dat
932 rval = "%15.4f" % val
933 sval = "%15.4f" % (val*scale)
934 uval = "%15.4f" % (serr*scale)
935 buff.append(" ".join([eout, sval, uval, rval]))
936 buff.append('')
937 with open(sfile, 'w', encoding=sys.getdefaultencoding()) as fh:
938 fh.write('\n'.join(buff))
940 def onCompSelectFit(self, event=None):
941 result = self.get_fitresult(nfit=self.owids['comp_fitlabel'].GetSelection())
942 cur_elem = self.owids['comp_elemchoice'].GetStringSelection()
943 self.owids['comp_elemchoice'].Clear()
944 elems = [el['symbol'] for el in result.elements]
945 self.owids['comp_elemchoice'].SetChoices(elems)
946 if len(cur_elem) > 0:
947 self.owids['comp_elemchoice'].SetStringSelection(cur_elem)
948 else:
949 self.owids['comp_elemchoice'].SetSelection(0)
950 self.onCompSetElemAbundance()
952 def UpdateCompositionPage(self, event=None):
953 self.xrfresults = self._larch.symtable.get_symbol(XRFRESULTS_GROUP)
954 if len(self.xrfresults) > 0:
955 result = self.get_fitresult()
956 fitlab = self.owids['comp_fitlabel']
957 fitlab.Clear()
958 fitlab.SetChoices([a.label for a in self.xrfresults])
959 fitlab.SetStringSelection(result.label)
960 self.onCompSelectFit()
962 def onElems_Clear(self, event=None):
963 self.ptable.on_clear_all()
965 def onElems_GuessPeaks(self, event=None):
966 mca = self.mca
967 _indices = peak.indexes(mca.counts*1.0, min_dist=5, thres=0.025)
968 peak_energies = mca.energy[_indices]
970 elrange = range(10, 92)
971 atsyms = [atomic_symbol(i) for i in elrange]
972 kalphas = [0.001*xray_line(i, 'Ka').energy for i in elrange]
973 kbetas = [0.001*xray_line(i, 'Kb').energy for i in elrange]
974 self.ptable.on_clear_all()
975 elems = []
976 for iz, en in enumerate(peak_energies):
977 for i, ex in enumerate(kalphas):
978 if abs(en - ex) < 0.025:
979 elems.append(atsyms[i])
980 peak_energies[iz] = -ex
982 for iz, en in enumerate(peak_energies):
983 if en > 0:
984 for i, ex in enumerate(kbetas):
985 if abs(en - ex) < 0.025:
986 if atsyms[i] not in elems:
987 elems.append(atsyms[i])
988 peak_energies[iz] = -ex
990 en = self.wids['en_xray'].GetValue()
991 emin = self.wids['en_min'].GetValue()
992 for elem in elems:
993 kedge = 0.001*xray_edge(elem, 'K').energy
994 l3edge = 0.001*xray_edge(elem, 'L3').energy
995 l2edge = 0.001*xray_edge(elem, 'L3').energy
996 if ((kedge < en and kedge > emin) or
997 (l3edge < en and l3edge > emin) or
998 (l2edge < en and l2edge > emin)):
999 if elem not in self.ptable.selected:
1000 self.ptable.onclick(label=elem)
1002 def onElems_FromROIS(self, event=None):
1003 for roi in self.mca.rois:
1004 words = roi.name.split()
1005 elem = words[0].title()
1006 if (elem in self.ptable.syms and
1007 elem not in self.ptable.selected):
1008 self.ptable.onclick(label=elem)
1009 self.onSetXrayEnergy()
1011 def onSetXrayEnergy(self, event=None):
1012 en = self.wids['en_xray'].GetValue()
1013 self.wids['en_max'].SetValue(en)
1014 self.wids['elastic_cen'].SetValue(en)
1015 self.wids['compton1_cen'].SetValue(en*0.975)
1016 self.wids['compton2_cen'].SetValue(en*0.950)
1017 emin = self.wids['en_min'].GetValue() * 1.25
1019 self.ptable.on_clear_all()
1020 for roi in self.mca.rois:
1021 words = roi.name.split()
1022 elem = words[0].title()
1023 kedge = l3edge = l2edge = 0.0
1024 try:
1025 kedge = 0.001*xray_edge(elem, 'K').energy
1026 l3edge = 0.001*xray_edge(elem, 'L3').energy
1027 l2edge = 0.001*xray_edge(elem, 'L3').energy
1028 except:
1029 pass
1030 if ((kedge < en and kedge > emin) or
1031 (l3edge < en and l3edge > emin) or
1032 (l2edge < en and l2edge > emin)):
1033 if elem not in self.ptable.selected:
1034 self.ptable.onclick(label=elem)
1036 def onDetMaterial(self, event=None):
1037 dmat = self.wids['det_mat'].GetStringSelection()
1038 if dmat not in FanoFactors:
1039 dmat = 'Si'
1040 self.wids['det_efano'].SetLabel('E_Fano= %.4e' % FanoFactors[dmat])
1042 def onFilterMaterial(self, evt=None, index=1):
1043 name = evt.GetString()
1044 den = self.materials_data.get(name, (None, 1.0))[1]
1045 t = 'filter%d' % (index)
1046 thick = self.wids['%s_thk'%t]
1047 if den < 0.1 and thick.GetValue() < 0.1:
1048 thick.SetValue(10.0)
1049 thick.SetIncrement(0.5)
1050 elif den > 0.1 and thick.GetValue() < 1.e-5:
1051 thick.SetValue(0.0250)
1052 thick.SetIncrement(0.005)
1054 def onUseCurrentMaterialAsFilter(self, evt=None):
1055 name = self.selected_material
1056 density = self.materials_data.get(name, (None, 1.0))[1]
1057 self.wids['matrix_den'].SetValue(density)
1058 self.wids['matrix_mat'].SetValue(name)
1060 def onSelectMaterial(self, evt=None):
1061 if self.owids['materials'] is None:
1062 return
1063 item = self.owids['materials'].GetSelectedRow()
1064 name = None
1065 if item > -1:
1066 name = list(self.materials_data.keys())[item]
1067 self.selected_material = name
1069 self.wids['matrix_btn'].Enable(name is not None)
1070 if name is not None:
1071 self.wids['matrix_btn'].SetLabel('Use %s' % name)
1073 def onUpdateFilterList(self, evt=None):
1074 flist = ['None']
1075 for i in range(len(self.materials_data)):
1076 if self.owids['materials'].GetToggleValue(i, 3): # is filter
1077 flist.append(self.owids['materials'].GetTextValue(i, 0))
1079 for i in range(NFILTERS):
1080 t = 'filter%d' % (i+1)
1081 choice = self.wids['%s_mat'%t]
1082 cur = choice.GetStringSelection()
1083 choice.Clear()
1084 choice.SetChoices(flist)
1085 if cur in flist:
1086 choice.SetStringSelection(cur)
1087 else:
1088 choice.SetSelection(0)
1090 def onAddMaterial(self, evt=None):
1091 name = self.owids['newmat_name'].GetValue()
1092 formula = self.owids['newmat_form'].GetValue()
1093 density = self.owids['newmat_dens'].GetValue()
1094 add = len(name) > 0 and len(formula)>0
1095 if add and name in self.materials_data:
1096 add = (Popup(self,
1097 "Overwrite definition of '%s'?" % name,
1098 'Re-define material?',
1099 style=wx.OK|wx.CANCEL)==wx.ID_OK)
1100 if add:
1101 irow = list(self.materials_data.keys()).index(name)
1102 self.owids['materials'].DeleteItem(irow)
1103 if add:
1104 add_material(name, formula, density)
1105 self.materials_data[name] = (formula, density)
1106 self.selected_material = name
1107 self.owids['materials'].AppendItem((name, formula,
1108 "%9.6f"%density,
1109 False))
1111 def onElemSelect(self, event=None, elem=None):
1112 self.ptable.tsym.SetLabel('')
1113 self.ptable.title.SetLabel('%d elements selected' %
1114 len(self.ptable.selected))
1116 def onUsePileupEscape(self, event=None):
1117 puse = self.wids['pileup_use'].IsChecked()
1118 self.wids['pileup_amp'].Enable(puse)
1119 self.wids['pileup_amp_vary'].Enable(puse)
1121 puse = self.wids['escape_use'].IsChecked()
1122 self.wids['escape_amp'].Enable(puse)
1123 self.wids['escape_amp_vary'].Enable(puse)
1126 def onUsePeak(self, event=None, name=None, value=None):
1127 if value is None and event is not None:
1128 value = event.IsChecked()
1129 if name is None:
1130 return
1131 for a in ('cen', 'step', 'tail', 'sigma', 'beta'):
1132 self.wids['%s_%s'%(name, a)].Enable(value)
1133 varwid = self.wids.get('%s_%s_vary'%(name, a), None)
1134 if varwid is not None:
1135 varwid.Enable(value)
1137 def build_model(self, match_amplitudes=True):
1138 """build xrf_model from form settings"""
1139 vars = {'Vary':'True', 'Fix': 'False', 'True':True, 'False': False}
1140 opts = {}
1141 for key, wid in self.wids.items():
1142 val = None
1143 if hasattr(wid, 'GetValue'):
1144 val = wid.GetValue()
1145 elif hasattr(wid, 'IsChecked'):
1146 val = wid.IsChecked()
1147 elif isinstance(wid, Choice):
1148 val = wid.GetStringSelection()
1149 elif hasattr(wid, 'GetStringSelection'):
1150 val = wid.GetStringSelection()
1151 elif hasattr(wid, 'GetLabel'):
1152 val = wid.GetLabel()
1153 if isinstance(val, str) and val.title() in vars:
1154 val = vars[val.title()]
1155 opts[key] = val
1156 opts['count_time'] = getattr(self.mca, 'real_time', 1.0)
1157 if opts['count_time'] is None:
1158 opts['count_time'] = 1.0
1159 opts['datetime'] = time.ctime()
1160 opts['mca_label'] = self.mca_label
1161 opts['mcagroup'] = self.mcagroup
1162 opts['XRFGROUP'] = XRFGROUP
1163 script = [xrfmod_setup.format(**opts)]
1165 for peakname in ('Elastic', 'Compton1', 'Compton2'):
1166 t = peakname.lower()
1167 if opts['%s_use'% t]:
1168 d = {'peakname': t}
1169 d['_cen'] = opts['%s_cen'%t]
1170 d['vcen'] = opts['%s_cen_vary'%t]
1171 d['_step'] = opts['%s_step'%t]
1172 d['vstep'] = opts['%s_step_vary'%t]
1173 d['_tail'] = opts['%s_tail'%t]
1174 d['vtail'] = opts['%s_tail_vary'%t]
1175 d['_beta'] = opts['%s_beta'%t]
1176 d['vbeta'] = opts['%s_beta_vary'%t]
1177 d['_sigma'] = opts['%s_sigma'%t]
1178 d['vsigma'] = opts['%s_sigma_vary'%t]
1179 script.append(xrfmod_scattpeak.format(**d))
1181 for i in range(NFILTERS):
1182 t = 'filter%d' % (i+1)
1183 f_mat = opts['%s_mat'%t]
1184 if f_mat not in (None, 'None') and int(1e6*opts['%s_thk'%t]) > 1:
1185 script.append(xrfmod_filter.format(name=f_mat,
1186 thick=opts['%s_thk'%t],
1187 vary=opts['%s_var'%t]))
1189 m_mat = opts['matrix_mat'].strip()
1190 if len(m_mat) > 0 and int(1e6*opts['matrix_thk']) > 1:
1191 script.append(xrfmod_matrix.format(name=m_mat,
1192 thick=opts['matrix_thk'],
1193 density=opts['matrix_den']))
1195 if opts['pileup_use'] in ('True', True):
1196 script.append(xrfmod_pileup.format(scale=opts['pileup_amp'],
1197 vary=opts['pileup_amp_vary']))
1199 if opts['escape_use'] in ('True', True):
1200 script.append(xrfmod_escape.format(scale=opts['escape_amp'],
1201 vary=opts['escape_amp_vary']))
1203 # sort elements selected on Periodic Table by Z
1204 elemz = []
1205 for elem in self.ptable.selected:
1206 elemz.append( 1 + self.ptable.syms.index(elem))
1207 elemz.sort()
1208 opts['elements'] = elemz
1210 xrfgroup = self._larch.symtable.get_group(XRFGROUP)
1211 setattr(xrfgroup, 'fitconfig', opts)
1212 json_dump(opts, os.path.join(user_larchdir, 'xrf_fitconfig.json'))
1215 syms = ["'%s'" % self.ptable.syms[iz-1] for iz in elemz]
1216 syms = '[%s]' % (', '.join(syms))
1217 script.append(xrfmod_elems.format(elemlist=syms))
1219 script.append("# set initial estimate of xrf intensity")
1220 script.append("{XRFGROUP}.workmca.xrf_init = _xrfmodel.calc_spectrum({XRFGROUP}.workmca.energy)")
1221 script = '\n'.join(script)
1222 self.model_script = script.format(group=self.mcagroup, XRFGROUP=XRFGROUP)
1224 self._larch.eval(self.model_script)
1226 cmds = []
1227 self._larch.symtable.get_symbol('_xrfmodel')
1228 self.xrfmod = self._larch.symtable.get_symbol('_xrfmodel')
1229 floor = 1.e-12*max(self.mca.counts)
1230 if match_amplitudes:
1231 total = 0.0 * self.mca.counts
1232 for name, parr in self.xrfmod.comps.items():
1233 nam = name.lower()
1234 try:
1235 imax = np.where(parr > 0.99*parr.max())[0][0]
1236 except: # probably means all counts are zero
1237 imax = int(len(parr)/2.0)
1238 scale = self.mca.counts[imax] / (parr[imax]+1.00)
1239 ampname = 'amp_%s' % nam
1240 if nam in ('elastic', 'compton1', 'compton2', 'compton',
1241 'background', 'pileup', 'escape'):
1242 ampname = f'{nam}_amp'
1243 if nam in ('background', 'pileup', 'escape'):
1244 scale = 1.0
1245 if nam in ('compton2',):
1246 scale /= 5.0
1248 paramval = self.xrfmod.params[ampname].value
1249 s = f"_xrfmodel.params['{ampname}'].value = {paramval*scale:.5f}"
1250 cmds.append(s)
1251 parr *= scale
1252 parr[np.where(parr<floor)] = floor
1253 total += parr
1254 self.xrfmod.current_model = total
1255 script = '\n'.join(cmds)
1256 self._larch.eval(script)
1257 self.model_script = f"{self.model_script}\n{script}"
1259 s = f"{XRFGROUP}.workmca.xrf_init = _xrfmodel.calc_spectrum({XRFGROUP}.workmca.energy)"
1260 self._larch.eval(s)
1262 def plot_model(self, model_spectrum=None, init=False, with_comps=False,
1263 label=None):
1264 conf = self.parent.conf
1266 plotkws = {'linewidth': 2.5, 'delay_draw': True, 'grid': False,
1267 'ylog_scale': self.parent.ylog_scale, 'show_legend': False,
1268 'fullbox': False}
1270 ppanel = self.parent.panel
1271 ppanel.conf.reset_trace_properties()
1272 self.parent.plot(self.mca.energy, self.mca.counts, mca=self.mca,
1273 xlabel='E (keV)', xmin=0, with_rois=False, **plotkws)
1275 if model_spectrum is None:
1276 model_spectrum = self.xrfmod.current_model if init else self.xrfmod.best_fit
1277 if label is None:
1278 label = 'model' if init else 'best fit'
1280 self.parent.oplot(self.mca.energy, model_spectrum,
1281 label=label, color=conf.fit_color, **plotkws)
1283 comp_traces = []
1284 plotkws.update({'fill': True, 'alpha':0.35, 'show_legend': True})
1285 for label, arr in self.xrfmod.comps.items():
1286 ppanel.oplot(self.mca.energy, arr, label=label, **plotkws)
1287 comp_traces.append(ppanel.conf.ntrace - 1)
1290 yscale = {False:'linear', True:'log'}[self.parent.ylog_scale]
1291 ppanel.set_logscale(yscale=yscale)
1292 ppanel.set_viewlimits()
1293 ppanel.conf.auto_margins = False
1294 ppanel.conf.set_margins(0.1, 0.02, 0.20, 0.1)
1295 ppanel.conf.set_legend_location('upper right', False)
1296 ppanel.conf.show_legend_frame = True
1297 ppanel.conf.draw_legend(show=True, delay_draw=False)
1299 if not with_comps:
1300 for obj, data in ppanel.conf.legend_map.items():
1301 line, trace, legline, legtext = data
1302 if trace in comp_traces:
1303 legline.set_alpha(0.50)
1304 legtext.set_alpha(0.50)
1305 line.set_visible(False)
1306 ppanel.conf.fills[trace].set_visible(False)
1307 ppanel.draw()
1309 def onShowModel(self, event=None):
1310 self.build_model()
1311 self.plot_model(init=True, with_comps=False)
1313 def onFitIteration(self, iter=0, pars=None):
1314 print("XRF Fit iteration %d" % iter)
1315 # self.wids['fit_message'].SetLabel("Fit iteration %d" % iter)
1317 def onFitModel(self, event=None):
1318 self.build_model()
1319 xrfmod = self._larch.symtable.get_symbol('_xrfmodel')
1320 xrfmod.iter_callback = self.onFitIteration
1322 fit_tol = float(self.wids['fit_toler'].GetStringSelection())
1323 fit_step = float(self.wids['fit_step'].GetStringSelection())
1324 max_nfev = int(self.wids['fit_maxnfev'].GetStringSelection())
1325 emin = float(self.wids['en_min'].GetValue())
1326 emax = float(self.wids['en_max'].GetValue())
1328 fit_script = xrfmod_fitscript.format(group=self.mcagroup,
1329 XRFGROUP=XRFGROUP,
1330 emin=emin, emax=emax,
1331 fit_toler=fit_tol,
1332 fit_step=fit_step,
1333 max_nfev=max_nfev)
1334 # print("-- > ", fit_script)
1336 self._larch.eval(fit_script)
1337 dgroup = self._larch.symtable.get_group(self.mcagroup)
1338 self.xrfresults = self._larch.symtable.get_symbol(XRFRESULTS_GROUP)
1340 xrfresult = self.xrfresults[0]
1341 xrfresult.script = "%s\n%s" % (self.model_script, fit_script)
1342 xrfresult.label = "fit %d" % (len(self.xrfresults))
1343 self.plot_model(init=True, with_comps=False)
1344 for i in range(len(self.nb.pagelist)):
1345 if self.nb.GetPageText(i).strip().startswith('Fit R'):
1346 self.nb.SetSelection(i)
1347 time.sleep(0.002)
1348 self.show_results()
1350 def onClose(self, event=None):
1351 self.Destroy()
1353 def onSaveFitResult(self, event=None):
1354 result = self.get_fitresult()
1355 deffile = self.mca.label + '_' + result.label
1356 deffile = fix_filename(deffile.replace('.', '_')) + '.xrfmodel'
1357 ModelWcards = "XRF Models(*.xrfmodel)|*.xrfmodel|All files (*.*)|*.*"
1358 sfile = FileSave(self, 'Save XRF Model', default_file=deffile,
1359 wildcard=ModelWcards)
1360 if sfile is not None:
1361 self._larch.eval(xrfmod_savejs.format(group=self.mcagroup,
1362 nfit=self.nfit,
1363 filename=sfile))
1365 def onExportFitResult(self, event=None):
1366 result = self.get_fitresult()
1367 deffile = self.mca.label + '_' + result.label
1368 deffile = fix_filename(deffile.replace('.', '_')) + '_xrf.txt'
1369 wcards = 'All files (*.*)|*.*'
1370 outfile = FileSave(self, 'Export Fit Result', default_file=deffile)
1371 if outfile is not None:
1372 buff = ['# XRF Fit %s: %s' % (self.mca.label, result.label),
1373 '## Fit Script:']
1374 for a in result.script.split('\n'):
1375 buff.append('# %s' % a)
1376 buff.append('## Fit Report:')
1377 for a in result.fit_report.split('\n'):
1378 buff.append('# %s' % a)
1380 buff.append('#')
1381 buff.append('########################################')
1383 labels = ['energy', 'counts', 'best_fit',
1384 'best_energy', 'fit_window',
1385 'fit_weight', 'attenuation']
1386 labels.extend(list(result.comps.keys()))
1388 buff.append('# %s' % (' '.join(labels)))
1390 npts = len(self.mca.energy)
1391 for i in range(npts):
1392 dline = [gformat(self.mca.energy[i]),
1393 gformat(self.mca.counts[i]),
1394 gformat(result.best_fit[i]),
1395 gformat(result.best_en[i]),
1396 gformat(result.fit_window[i]),
1397 gformat(result.fit_weight[i]),
1398 gformat(result.atten[i])]
1399 for c in result.comps.values():
1400 dline.append(gformat(c[i]))
1401 buff.append(' '.join(dline))
1402 buff.append('\n')
1403 with open(outfile, 'w', encoding=sys.getdefaultencoding()) as fh:
1404 fh.write('\n'.join(buff))
1406 def get_fitresult(self, nfit=None):
1407 if nfit is None:
1408 nfit = self.nfit
1410 self.xrfresults = self._larch.symtable.get_symbol(XRFRESULTS_GROUP)
1411 self.nfit = max(0, nfit)
1412 self.nfit = min(self.nfit, len(self.xrfresults)-1)
1413 return self.xrfresults[self.nfit]
1415 def onChangeFitLabel(self, event=None):
1416 label = self.owids['fitlabel_txt'].GetValue()
1417 result = self.get_fitresult()
1418 result.label = label
1419 self.show_results()
1421 def onPlot(self, event=None):
1422 result = self.get_fitresult()
1423 xrfmod = self._larch.symtable.get_symbol('_xrfmodel')
1424 with_comps = self.owids['plot_comps'].IsChecked()
1425 spect = xrfmod.calc_spectrum(self.mca.energy,
1426 params=result.params)
1427 self.plot_model(model_spectrum=spect, with_comps=with_comps,
1428 label=result.label)
1430 def onSelectFit(self, evt=None):
1431 if self.owids['stats'] is None:
1432 return
1433 item = self.owids['stats'].GetSelectedRow()
1434 if item > -1:
1435 self.show_fitresult(nfit=item)
1437 def onSelectParameter(self, evt=None):
1438 if self.owids['params'] is None:
1439 return
1440 if not self.owids['params'].HasSelection():
1441 return
1442 item = self.owids['params'].GetSelectedRow()
1443 pname = self.owids['paramsdata'][item]
1445 cormin= self.owids['min_correl'].GetValue()
1446 self.owids['correl'].DeleteAllItems()
1448 result = self.get_fitresult()
1449 this = result.params[pname]
1450 if this.correl is not None:
1451 sort_correl = sorted(this.correl.items(), key=lambda it: abs(it[1]))
1452 for name, corval in reversed(sort_correl):
1453 if abs(corval) > cormin:
1454 self.owids['correl'].AppendItem((pname, name, "% .4f" % corval))
1456 def onAllCorrel(self, evt=None):
1457 result = self.get_fitresult()
1458 params = result.params
1459 parnames = list(params.keys())
1461 cormin= self.owids['min_correl'].GetValue()
1462 correls = {}
1463 for i, name in enumerate(parnames):
1464 par = params[name]
1465 if not par.vary:
1466 continue
1467 if hasattr(par, 'correl') and par.correl is not None:
1468 for name2 in parnames[i+1:]:
1469 if (name != name2 and name2 in par.correl and
1470 abs(par.correl[name2]) > cormin):
1471 correls["%s$$%s" % (name, name2)] = par.correl[name2]
1473 sort_correl = sorted(correls.items(), key=lambda it: abs(it[1]))
1474 sort_correl.reverse()
1476 self.owids['correl'].DeleteAllItems()
1478 for namepair, corval in sort_correl:
1479 name1, name2 = namepair.split('$$')
1480 self.owids['correl'].AppendItem((name1, name2, "% .4f" % corval))
1482 def show_results(self):
1483 cur = self.get_fitresult()
1484 self.owids['stats'].DeleteAllItems()
1485 for i, res in enumerate(self.xrfresults):
1486 args = [res.label]
1487 for attr in ('nvarys', 'nfev', 'chisqr', 'redchi', 'aic'):
1488 val = getattr(res, attr)
1489 if isinstance(val, int):
1490 val = '%d' % val
1491 else:
1492 val = gformat(val, 11)
1493 args.append(val)
1494 self.owids['stats'].AppendItem(tuple(args))
1495 self.owids['data_title'].SetLabel("%s: %.3f sec" % (self.mca.label, cur.count_time))
1496 self.owids['data_title2'].SetLabel("%s: %.3f sec" % (self.mca.label, cur.count_time))
1497 self.owids['fitlabel_txt'].SetValue(cur.label)
1498 self.show_fitresult(nfit=self.nfit)
1500 def show_fitresult(self, nfit=0, mca=None):
1501 if mca is not None:
1502 self.mca = mca
1503 result = self.get_fitresult(nfit=nfit)
1505 self.owids['data_title'].SetLabel("%s: %.3f sec" % (self.mca.label, result.count_time))
1506 self.owids['data_title2'].SetLabel("%s: %.3f sec" % (self.mca.label, result.count_time))
1507 self.result = result
1508 self.owids['fitlabel_txt'].SetValue(result.label)
1509 self.owids['params'].DeleteAllItems()
1510 self.owids['paramsdata'] = []
1511 for param in reversed(result.params.values()):
1512 pname = param.name
1513 try:
1514 val = gformat(param.value, 10)
1515 except (TypeError, ValueError):
1516 val = ' ??? '
1517 serr, perr = ' N/A ', ' N/A '
1518 if param.stderr is not None:
1519 serr = gformat(param.stderr, 10)
1520 try:
1521 perr = '{:.3f}'.format(100.0*abs(param.stderr/param.value))
1522 except ZeroDivisionError:
1523 perr = '?'
1524 extra = ' '
1525 if param.expr is not None:
1526 extra = ' = %s ' % param.expr
1527 elif not param.vary:
1528 extra = ' (fixed)'
1529 elif param.init_value is not None:
1530 extra = gformat(param.init_value, 10)
1532 self.owids['params'].AppendItem((pname, val, serr, perr, extra))
1533 self.owids['paramsdata'].append(pname)
1534 self.Refresh()