Coverage for /Users/Newville/Codes/xraylarch/larch/wxxrd/xrd1d_display.py: 13%
662 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 pythonw
2'''
3GUI for displaying 1D XRD images
5'''
6import os
7import sys
8import time
10from functools import partial
12import numpy as np
13from numpy.polynomial.chebyshev import chebfit, chebval
15from pyFAI.azimuthalIntegrator import AzimuthalIntegrator
16import pyFAI.units
17pyFAI.use_opencl = False
19import wx
20import wx.lib.scrolledpanel as scrolled
21from wxmplot import PlotPanel
23from lmfit.lineshapes import gaussian
25import larch
26from larch.larchlib import read_workdir, save_workdir
27from larch.utils import nativepath, get_cwd, gformat, fix_filename
28from larch.utils.physical_constants import PLANCK_HC
29from larch.xray import XrayBackground
30from larch.wxxas import RemoveDialog
31from larch.io import tifffile
33from larch.xrd import (d_from_q,twth_from_q,q_from_twth,
34 d_from_twth,twth_from_d,q_from_d,
35 lambda_from_E, E_from_lambda, calc_broadening,
36 instrumental_fit_uvw,peaklocater,peakfitter,
37 xrd1d, peakfinder_methods, save1D, read_poni)
39from larch.wxlib import (ReportFrame, BitmapButton, FloatCtrl, FloatSpin,
40 SetTip, GridPanel, get_icon, SimpleText, pack,
41 Button, HLine, Choice, Check, MenuItem, COLORS,
42 set_color, CEN, RIGHT, LEFT, FRAMESTYLE, Font,
43 FONTSIZE, FONTSIZE_FW, FileSave, FileOpen,
44 flatnotebook, Popup, FileCheckList,
45 EditableListBox, ExceptionPopup, CIFFrame,
46 LarchFrame, LarchWxApp)
48MAXVAL = 2**32 - 2**15
49MAXVAL_INT16 = 2**16 - 8
51XYWcards = "XY Data File(*.xy)|*.xy|All files (*.*)|*.*"
52TIFFWcards = "TIFF Files|*.tif;*.tiff|All files (*.*)|*.*"
53PlotWindowChoices = ['1', '2', '3', '4', '5', '6', '7', '8', '9']
55X_SCALES = [u'q (\u212B\u207B\u00B9)', u'2\u03B8 (\u00B0)', u'd (\u212B)']
56Y_SCALES = ['linear', 'log']
58PLOT_TYPES = {'Raw Data': 'raw',
59 'Raw Data + Background' : 'raw+bkg',
60 'Background-subtracted Data': 'sub'}
62PLOT_CHOICES = list(PLOT_TYPES.keys())
63PLOT_CHOICES_MULTI = [PLOT_CHOICES[0], PLOT_CHOICES[2]]
65SCALE_METHODS = {'Max Raw Intensity': 'raw_max',
66 'Max Background-Subtracted Intensity': 'sub_max',
67 'Max Background Intensity': 'bkg_max',
68 'Mean Raw Intensity': 'raw_mean',
69 'Mean Background-Subtracted Intensity': 'sub_mean',
70 'Mean Background Intensity': 'bkg_mean'}
72def keyof(dictlike, value, default):
73 "dict reverse lookup"
74 if value not in dictlike.values():
75 value = default
76 defout = None
77 for key, val in dictlike.items():
78 if defout is None:
79 defout = key
80 if val == value:
81 return key
82 return defout
85def smooth_bruckner(y, smooth_points, iterations):
86 y_original = y
87 N_data = y.size
88 N = smooth_points
89 N_float = float(N)
90 y = np.empty(N_data + N + N)
92 y[0:N].fill(y_original[0])
93 y[N:N + N_data] = y_original[0:N_data]
94 y[N + N_data:N_data + N + N].fill(y_original[-1])
96 y_avg = np.average(y)
97 y_min = np.min(y)
99 y_c = y_avg + 2. * (y_avg - y_min)
100 y[y > y_c] = y_c
102 window_size = N_float*2+1
104 for j in range(0, iterations):
105 window_avg = np.average(y[0: 2*N + 1])
106 for i in range(N, N_data - 1 - N - 1):
107 if y[i]>window_avg:
108 y_new = window_avg
109 #updating central value in average (first bracket)
110 #and shifting average by one index (second bracket)
111 window_avg += ((window_avg-y[i]) + (y[i+N+1]-y[i - N]))/window_size
112 y[i] = y_new
113 else:
114 #shifting average by one index
115 window_avg += (y[i+N+1]-y[i - N])/window_size
116 return y[N:N + N_data]
118def extract_background(x, y, smooth_width=0.1, iterations=40, cheb_order=40):
119 """DIOPTAS
120 Performs a background subtraction using bruckner smoothing and a chebyshev polynomial.
121 Standard parameters are found to be optimal for synchrotron XRD.
122 :param x: x-data of pattern
123 :param y: y-data of pattern
124 :param smooth_width: width of the window in x-units used for bruckner smoothing
125 :param iterations: number of iterations for the bruckner smoothing
127 :param cheb_order: order of the fitted chebyshev polynomial
128 :return: vector of extracted y background
129 """
130 smooth_points = int((float(smooth_width) / (x[1] - x[0])))
131 y_smooth = smooth_bruckner(y, abs(smooth_points), iterations)
132 # get cheb input parameters
133 x_cheb = 2. * (x - x[0]) / (x[-1] - x[0]) - 1.
134 cheb_params = chebfit(x_cheb, y_smooth, cheb_order)
135 return chebval(x_cheb, cheb_params)
137def calc_bgr(dset, qwid=0.1, nsmooth=40, cheb_order=40):
138 return extract_background(dset.q, dset.I, smooth_width=qwid,
139 iterations=nsmooth, cheb_order=cheb_order)
141class WavelengthDialog(wx.Dialog):
142 """dialog for wavelength/energy"""
143 def __init__(self, parent, wavelength, callback=None):
145 self.parent = parent
146 self.callback = callback
148 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
149 title="Set Wavelength / Energy")
150 self.SetFont(Font(FONTSIZE))
151 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
153 self.wids = wids = {}
155 opts = dict(size=(90, -1), act_on_losefocus=True)
156 wids['wavelength'] = FloatCtrl(panel, value=wavelength, precision=7,
157 minval=1.0e-4, maxval=100, **opts)
159 en_ev = PLANCK_HC/wavelength
160 wids['energy'] = FloatCtrl(panel, value=en_ev, precision=2,
161 minval=50, maxval=5.e5, **opts)
164 wids['wavelength'].SetAction(self.set_wavelength)
165 wids['energy'].SetAction(self.set_energy)
167 panel.Add(SimpleText(panel, 'Wavelength(\u212B): '),
168 dcol=1, newrow=False)
169 panel.Add(wids['wavelength'], dcol=1)
170 panel.Add(SimpleText(panel, 'Energy (eV): '),
171 dcol=1, newrow=True)
172 panel.Add(wids['energy'], dcol=1)
174 panel.Add((10, 10), newrow=True)
176 panel.Add(Button(panel, 'Done', size=(150, -1),
177 action=self.onDone), newrow=True)
178 panel.pack()
179 sizer = wx.BoxSizer(wx.VERTICAL)
180 sizer.Add(panel, 1, LEFT, 5)
181 pack(self, sizer)
182 self.Fit()
184 w0, h0 = self.GetSize()
185 w1, h1 = self.GetBestSize()
186 self.SetSize((max(w0, w1)+25, max(h0, h1)+25))
188 def onDone(self, event=None):
189 if callable(self.callback):
190 self.callback(self.wids['wavelength'].GetValue())
191 self.Destroy()
193 def set_wavelength(self, value=1, event=None):
194 w = self.wids['wavelength'].GetValue()
196 self.wids['energy'].SetValue(PLANCK_HC/w, act=False)
198 def set_energy(self, value=10000, event=None):
199 w = self.wids['energy'].GetValue()
200 self.wids['wavelength'].SetValue(PLANCK_HC/w, act=False)
202class RenameDialog(wx.Dialog):
203 """dialog for renaming a pattern"""
204 def __init__(self, parent, name, callback=None):
206 self.parent = parent
207 self.callback = callback
209 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
210 title="Rename dataset")
211 self.SetFont(Font(FONTSIZE))
212 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
214 self.wids = wids = {}
215 wids['newname'] = wx.TextCtrl(panel, value=name, size=(150, -1))
217 panel.Add(SimpleText(panel, 'New Name: '), dcol=1, newrow=False)
218 panel.Add(wids['newname'], dcol=1)
219 panel.Add((10, 10), newrow=True)
221 panel.Add(Button(panel, 'Done', size=(150, -1),
222 action=self.onDone), newrow=True)
223 panel.pack()
224 sizer = wx.BoxSizer(wx.VERTICAL)
225 sizer.Add(panel, 1, LEFT, 5)
226 pack(self, sizer)
227 self.Fit()
229 w0, h0 = self.GetSize()
230 w1, h1 = self.GetBestSize()
231 self.SetSize((max(w0, w1)+25, max(h0, h1)+25))
233 def onDone(self, event=None):
234 if callable(self.callback):
235 self.callback(self.wids['newname'].GetValue())
236 self.Destroy()
238class XRD1DFrame(wx.Frame):
239 """browse 1D XRD patterns"""
241 def __init__(self, parent=None, wavelength=1.0, ponifile=None,
242 _larch=None, **kws):
244 wx.Frame.__init__(self, None, -1, title='1D XRD Browser',
245 style=FRAMESTYLE, size=(600, 600), **kws)
246 self.parent = parent
247 self.wavelength = wavelength
248 self.poni = {'wavelength': 1.e-10*self.wavelength} # ! meters!
249 self.pyfai_integrator = None
251 self.larch = _larch
252 if self.larch is None:
253 self.larch_buffer = LarchFrame(_larch=None, parent=self,
254 is_standalone=False,
255 with_raise=False,
256 exit_on_close=False)
258 self.larch = self.larch_buffer.larchshell
260 self.current_label = None
261 self.cif_browser = None
262 self.img_display = None
263 self.plot_display = None
264 self.datasets = {}
265 self.form = {}
266 self.createMenus()
267 self.build()
268 self.set_wavelength(self.wavelength)
269 if ponifile is not None:
270 self.set_ponifile(ponifile)
272 def createMenus(self):
273 fmenu = wx.Menu()
274 cmenu = wx.Menu()
275 smenu = wx.Menu()
276 MenuItem(self, fmenu, "Read XY File",
277 "Read XRD 1D data from XY FIle",
278 self.onReadXY)
280 MenuItem(self, fmenu, "Save XY File",
281 "Save XRD 1D data to XY FIle",
282 self.onSaveXY)
283 self.tiff_reader = MenuItem(self, fmenu, "Read TIFF XRD Image",
284 "Read XRD 2D image to be integrated",
285 self.onReadTIFF)
286 self.tiff_reader.Enable(self.poni.get('dist', -1) > 0)
287 fmenu.AppendSeparator()
288 MenuItem(self, fmenu, "Change Label for Current Pattern",
289 "Rename Current Pattern",
290 self.onRenameDataset)
292 MenuItem(self, fmenu, "Remove Selected Patterns",
293 "Remove Selected Patterns",
294 self.remove_selected_datasets)
296 fmenu.AppendSeparator()
297 MenuItem(self, fmenu, "&Quit\tCtrl+Q", "Quit program", self.onClose)
299 MenuItem(self, smenu, "Browse AmMin Crystal Structures",
300 "Browse Structures from Am Min Database",
301 self.onCIFBrowse)
303 MenuItem(self, cmenu, "Read PONI Calibration File",
304 "Read PONI Calibration (pyFAI) FIle",
305 self.onReadPONI)
307 MenuItem(self, cmenu, "Set Energy / Wavelength",
308 "Set Energy and Wavelength",
309 self.onSetWavelength)
311 menubar = wx.MenuBar()
312 menubar.Append(fmenu, "&File")
313 menubar.Append(cmenu, "&Calibration")
314 menubar.Append(smenu, "&Search CIF Structures")
315 self.SetMenuBar(menubar)
318 def onClose(self, event=None):
319 try:
320 if self.panel is not None:
321 self.panel.win_config.Close(True)
322 if self.panel is not None:
323 self.panel.win_config.Destroy()
324 except:
325 pass
327 for attr in ('cif_browser', 'img_display', 'plot_display'):
328 winx = getattr(self, attr, None)
329 if winx is not None:
330 try:
331 winx.Destroy()
332 except:
333 pass
335 if hasattr(self.larch.symtable, '_plotter'):
336 wx.CallAfter(self.larch.symtable._plotter.close_all_displays)
338 self.Destroy()
340 def onSetWavelength(self, event=None):
341 WavelengthDialog(self, self.wavelength, self.set_wavelength).Show()
343 def onReadPONI(self, event=None):
344 sfile = FileOpen(self, 'Read PONI (pyFAI) calibration file',
345 default_file='XRD.poni',
346 default_dir=get_cwd(),
347 wildcard="PONI Files(*.poni)|*.poni|All files (*.*)|*.*")
349 if sfile is not None:
350 try:
351 self.poni.update(read_poni(sfile))
352 except:
353 title = "Could not read PONI File"
354 message = [f"Could not read PONI file {sfile}"]
355 ExceptionPopup(self, title, message)
357 top, xfile = os.path.split(sfile)
358 os.chdir(top)
360 try:
361 self.pyfai_integrator = AzimuthalIntegrator(**self.poni)
362 except:
363 self.pyfai_integrator = None
365 self.tiff_reader.Enable(self.pyfai_integrator is not None)
367 self.set_wavelength(self.poni['wavelength']*1.e10)
369 def onReadXY(self, event=None):
370 sfile = FileOpen(self, 'Read XY Data',
371 default_file='XRD.xy',
372 default_dir=get_cwd(),
373 wildcard=XYWcards)
374 if sfile is not None:
375 top, xfile = os.path.split(sfile)
376 os.chdir(top)
377 dxrd = xrd1d(file=sfile, wavelength=self.wavelength)
378 self.add_data(dxrd, label=xfile)
380 def onReadTIFF(self, event=None):
381 sfile = FileOpen(self, 'Read TIFF XRD Image',
382 default_file='XRD.tiff',
383 default_dir=get_cwd(),
384 wildcard=TIFFWcards)
385 if sfile is not None:
386 if self.pyfai_integrator is None:
387 try:
388 self.pyfai_integrator = AzimuthalIntegrator(**self.poni)
389 except:
390 title = "Could not create pyFAI integrator: bad PONI data?"
391 message = [f"Could not create pyFAI integrator"]
392 ExceptionPopup(self, title, message)
393 if self.pyfai_integrator is None:
394 return
396 img = tifffile.imread(sfile)
397 img = img[::-1, :]
398 if (img.max() > MAXVAL_INT16) and (img.max() < MAXVAL_INT16 + 64):
399 #probably really 16bit data
400 img[np.where(img>MAXVAL_INT16)] = 0
401 else:
402 img[np.where(img>MAXVAL)] = 0
403 img[np.where(img<-1)] = -1
404 # print("read tiff ", img.shape, img.min(), img.max())
406 imd = self.get_imdisplay()
407 imd.display(img, colomap='gray', auto_contrast=True)
409 integrate = self.pyfai_integrator.integrate1d
410 q, ix = integrate(img, 2048, method='csr', unit='q_A^-1',
411 correctSolidAngle=True,
412 polarization_factor=0.999)
414 top, fname = os.path.split(sfile)
415 dxrd = xrd1d(label=fname, x=q, I=ix, xtype='q', wavelength=self.wavelength)
416 dxrd.file = fname
417 self.add_data(dxrd, label=fname)
420 def onCIFBrowse(self, event=None):
421 shown = False
422 if self.cif_browser is not None:
423 try:
424 self.cif_browser.Raise()
425 shown = True
426 except:
427 del self.cif_browser
428 shown = False
429 if not shown:
430 self.cif_browser = CIFFrame(usecif_callback=self.onLoadCIF,
431 _larch=self.larch)
432 self.cif_browser.Raise()
434 def onLoadCIF(self, cif=None):
435 if cif is None:
436 return
437 t0 = time.time()
439 energy = E_from_lambda(self.wavelength)
441 sfact = cif.get_structure_factors(wavelength=self.wavelength)
442 try:
443 self.cif_browser.cifdb.set_hkls(self.current_cif.ams_id, sfact.hkls)
444 except:
445 pass
447 mineral = getattr(cif, 'mineral', None)
448 label = getattr(mineral, 'name', '')
449 if len(label) < 0:
450 label = getattr(cif, 'formula', '')
451 cifid = getattr(cif, 'ams_id', '')
452 if len(label) < 1 and len(cifid) > 0:
453 label = 'CIF:{cifid}'
454 else:
455 label = f'{label}, CIF:{cifid}'
457 try:
458 q = self.datasets[self.current_label].q
459 except:
460 q = np.linspace(0, 10, 2048)
462 sigma = 2.5*(q[1] - q[0])
464 intensity = q*0.0
465 for cen, amp in zip(sfact.q, sfact.intensity):
466 intensity += gaussian(q, amplitude=amp, center=cen, sigma=sigma)
468 xdat = xrd1d(label=label, energy=energy, wavelength=self.wavelength)
469 xdat.set_xy_data(np.array([q, intensity/max(intensity)]), 'q')
470 self.add_data(xdat, label=label)
473 def onSaveXY(self, event=None):
474 fname = fix_filename(self.current_label.replace('.', '_') + '.xy')
475 sfile = FileSave(self, 'Save XY Data', default_file=fname)
476 if sfile is None:
477 return
479 label = self.current_label
480 dset = self.datasets[label]
482 xscale = self.wids['xscale'].GetSelection()
483 xlabel = self.wids['xscale'].GetStringSelection()
484 xdat = dset.twth
485 xlabel = pyFAI.units.TTH_DEG
486 if xscale == 0:
487 xdat = dset.q
488 xlabel = pyFAI.units.Q_A
490 ydat = 1.0*dset.I/dset.scale
491 wavelength = PLANCK_HC/(dset.energy*1000.0)
492 buff = [f"# XY data from {label}",
493 f"# wavelength (Ang) = {gformat(wavelength)}",
494 "# Calibration data from pyFAI:"]
495 for key, value in self.poni.items():
496 buff.append(f"# {key}: {value}")
497 buff.append("#-------------------------------------")
498 buff.append(f"# {xlabel} Intensity")
500 for x, y in zip(xdat, ydat):
501 buff.append(f" {gformat(x, 13)} {gformat(y, 13)}")
502 buff.append('')
503 with open(sfile, 'w') as fh:
504 fh.write('\n'.join(buff))
507 def build(self):
508 sizer = wx.GridBagSizer(3, 3)
509 sizer.SetVGap(3)
510 sizer.SetHGap(3)
512 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
513 splitter.SetMinimumPaneSize(220)
515 # left side: list of XRD 1D patterns
516 lpanel = wx.Panel(splitter)
517 lpanel.SetMinSize((275, 350))
518 # rpanel = scrolled.ScrolledPanel(splitter)
519 rpanel = wx.Panel(splitter)
520 rpanel.SetMinSize((400, 350))
521 rpanel.SetSize((750, 550))
523 ltop = wx.Panel(lpanel)
525 def Btn(msg, x, act):
526 b = Button(ltop, msg, size=(x, 30), action=act)
527 b.SetFont(Font(FONTSIZE))
528 return b
530 sel_none = Btn('Select None', 130, self.onSelNone)
531 sel_all = Btn('Select All', 130, self.onSelAll)
533 self.filelist = FileCheckList(lpanel, main=self,
534 select_action=self.show_dataset,
535 remove_action=self.remove_dataset)
536 set_color(self.filelist, 'list_fg', bg='list_bg')
538 tsizer = wx.BoxSizer(wx.HORIZONTAL)
539 tsizer.Add(sel_all, 1, LEFT|wx.GROW, 1)
540 tsizer.Add(sel_none, 1, LEFT|wx.GROW, 1)
541 pack(ltop, tsizer)
543 sizer = wx.BoxSizer(wx.VERTICAL)
544 sizer.Add(ltop, 0, LEFT|wx.GROW, 1)
545 sizer.Add(self.filelist, 1, LEFT|wx.GROW|wx.ALL, 1)
546 pack(lpanel, sizer)
548 # right side: parameters controlling display
549 panel = GridPanel(rpanel, ncols=6, nrows=10, pad=3, itemstyle=LEFT)
550 panel.sizer.SetVGap(3)
551 panel.sizer.SetHGap(3)
553 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.BOLD)
555 # title row
556 self.wids = wids = {}
557 title = SimpleText(panel, '1D XRD Data Display', font=Font(FONTSIZE+2),
558 colour=COLORS['title'], style=LEFT)
560 self.last_plot_type = 'one'
561 self.plotone = Button(panel, 'Plot Current ', size=(125, -1),
562 action=self.onPlotOne)
563 self.plotsel = Button(panel, 'Plot Selected ', size=(125, -1),
564 action=self.onPlotSel)
565 wids['plotone'] = Choice(panel, choices=PLOT_CHOICES, default=0,
566 action=self.onPlotOne, size=(200, -1))
567 wids['plotsel'] = Choice(panel, choices=PLOT_CHOICES_MULTI, default=0,
568 action=self.onPlotSel, size=(200, -1))
569 wids['xscale'] = Choice(panel, choices=X_SCALES, default=0,
570 action=self.onPlotEither, size=(100, -1))
572 opts = dict(default=False, size=(200, -1), action=self.onPlotEither)
573 wids['plot_win'] = Choice(panel, size=(100, -1), choices=PlotWindowChoices,
574 action=self.onPlotEither)
575 wids['plot_win'].SetStringSelection('1')
577 wids['auto_scale'] = Check(panel, default=True, label='auto?',
578 action=self.auto_scale)
579 wids['scale_method'] = Choice(panel, choices=list(SCALE_METHODS.keys()),
580 size=(250, -1), action=self.auto_scale, default=0)
582 wids['scale'] = FloatCtrl(panel, value=1.0, size=(90, -1), precision=2,
583 action=self.set_scale)
584 wids['wavelength'] = SimpleText(panel, label="%.6f" % (self.wavelength), size=(100, -1))
585 wids['energy_ev'] = SimpleText(panel, label="%.1f" % (PLANCK_HC/self.wavelength), size=(100, -1))
587 wids['bkg_qwid'] = FloatSpin(panel, value=0.1, size=(90, -1), digits=2,
588 increment=0.01,
589 min_val=0.001, max_val=5, action=self.on_bkg)
590 wids['bkg_nsmooth'] = FloatSpin(panel, value=30, size=(90, -1),
591 digits=0, min_val=2, max_val=100, action=self.on_bkg)
592 wids['bkg_porder'] = FloatSpin(panel, value=40, size=(90, -1),
593 digits=0, min_val=2, max_val=100, action=self.on_bkg)
595 def CopyBtn(name):
596 return Button(panel, 'Copy to Seleceted', size=(150, -1),
597 action=partial(self.onCopyAttr, name))
599 wids['bkg_copy'] = CopyBtn('bkg')
600 wids['scale_copy'] = CopyBtn('scale_method')
602 def slabel(txt):
603 return wx.StaticText(panel, label=txt)
605 panel.Add(title, style=LEFT, dcol=5)
606 panel.Add(self.plotsel, newrow=True)
607 panel.Add(wids['plotsel'], dcol=2)
608 panel.Add(slabel(' X scale: '), dcol=2, style=LEFT)
609 panel.Add(wids['xscale'], style=RIGHT)
611 panel.Add(self.plotone, newrow=True)
612 panel.Add(wids['plotone'], dcol=2)
613 panel.Add(slabel(' Plot Window: '), dcol=2)
614 panel.Add(wids['plot_win'], style=RIGHT)
616 panel.Add((5, 5))
617 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True)
618 panel.Add((5, 5))
621 panel.Add(slabel(' Scaling Factor: '), style=LEFT, newrow=True)
622 panel.Add(wids['scale'])
623 panel.Add(wids['auto_scale'])
624 panel.Add(slabel(' Scaling Method: '), style=LEFT, newrow=True)
625 panel.Add(wids['scale_method'], dcol=3)
626 panel.Add(wids['scale_copy'], dcol=2, style=RIGHT)
628 panel.Add((5, 5))
629 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True)
630 panel.Add((5, 5))
632 panel.Add(slabel(' Background Subtraction Parameters: '), dcol=3, style=LEFT, newrow=True)
633 panel.Add(wids['bkg_copy'], dcol=3, style=RIGHT)
635 panel.Add(slabel(' Q width (\u212B\u207B\u00B9): '), style=LEFT, newrow=True)
636 panel.Add(wids['bkg_qwid'])
637 panel.Add(slabel(' Smoothing Steps: '), style=LEFT, newrow=True)
638 panel.Add(wids['bkg_nsmooth'], dcol=2)
639 panel.Add(slabel(' Polynomial Order: '), style=LEFT, newrow=True)
640 panel.Add(wids['bkg_porder'])
642 panel.Add((5, 5))
643 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True)
644 panel.Add((5, 5))
646 panel.Add(slabel(' Calibration, Calculated XRD Patterns: '), dcol=6, style=LEFT, newrow=True)
647 panel.Add(slabel(' X-ray Energy (eV): '), style=LEFT, newrow=True)
648 panel.Add(wids['energy_ev'], dcol=1)
649 panel.Add(slabel(' Wavelength (\u212B): '), style=LEFT, newrow=False)
650 panel.Add(wids['wavelength'], dcol=2)
653 panel.Add((5, 5))
655 panel.pack()
657 sizer = wx.BoxSizer(wx.VERTICAL)
658 sizer.Add((5, 5), 0, LEFT, 3)
659 sizer.Add(panel, 0, LEFT, 3)
660 sizer.Add((5, 5), 0, LEFT, 3)
661 pack(rpanel, sizer)
663 # rpanel.SetupScrolling()
665 splitter.SplitVertically(lpanel, rpanel, 1)
666 mainsizer = wx.BoxSizer(wx.VERTICAL)
667 mainsizer.Add(splitter, 1, wx.GROW|wx.ALL, 5)
668 pack(self, mainsizer)
669 self.SetSize((875, 450))
671 self.Show()
672 self.Raise()
674 def set_ponifile(self, ponifile):
675 "set poni from datafile"
676 try:
677 self.set_poni(read_poni(ponifile))
678 except:
679 pass
681 def set_poni(self, poni):
682 "set poni from dict"
683 try:
684 self.poni.update(poni)
685 self.set_wavelength(self.poni['wavelength']*1.e10)
686 self.tiff_reader.Enable(self.poni.get('dist', -1) > 0)
687 except:
688 pass
690 try:
691 self.pyfai_integrator = AzimuthalIntegrator(**self.poni)
692 except:
693 self.pyfai_integrator = None
696 def set_wavelength(self, value):
697 self.wavelength = value
698 self.wids['wavelength'].SetLabel("%.6f" % value)
699 self.wids['energy_ev'].SetLabel("%.1f" % (PLANCK_HC/value))
700 for key, dset in self.datasets.items():
701 dset.set_wavelength(value)
703 def onCopyAttr(self, name=None, event=None):
704 # print("Copy ", name, event)
705 if name == 'bkg':
706 qwid = self.wids['bkg_qwid'].GetValue()
707 nsmooth = int(self.wids['bkg_nsmooth'].GetValue())
708 cheb_order = int(self.wids['bkg_porder'].GetValue())
710 for label in self.filelist.GetCheckedStrings():
711 dset = self.datasets.get(label, None)
712 if dset is not None:
713 dset.bkg_qwid = qwid
714 dset.bkg_nsmooth = nsmooth
715 dset.bkg_porder = cheb_order
716 # print("redo bkg for ", label)
717 dset.bkgd = calc_bgr(dset, qwid=qwid, nsmooth=nsmooth,
718 cheb_order=cheb_order)
720 elif name == 'scale_method':
721 for label in self.filelist.GetCheckedStrings():
722 dset = self.datasets.get(label, None)
723 if dset is not None:
724 # print("redo scale for ", label)
725 self.scale_data(dset, with_plot=False)
728 def onSelNone(self, event=None):
729 self.filelist.select_none()
731 def onSelAll(self, event=None):
732 self.filelist.select_all()
734 def on_bkg(self, event=None, value=None):
735 try:
736 qwid = self.wids['bkg_qwid'].GetValue()
737 nsmooth = int(self.wids['bkg_nsmooth'].GetValue())
738 cheb_order = int(self.wids['bkg_porder'].GetValue())
739 except:
740 return
741 label = self.current_label
742 if label not in self.datasets:
743 return
744 dset = self.datasets[label]
745 dset.bkgd = calc_bgr(dset, qwid=qwid, nsmooth=nsmooth,
746 cheb_order=cheb_order)
747 if 'back' not in self.wids['plotone'].GetStringSelection().lower():
748 self.wids['plotone'].SetSelection(1)
749 else:
750 self.onPlotOne()
752 def show_dataset(self, event=None, label=None):
753 # print('show xd1d ', event, label)
754 if label is None and event is not None:
755 label = str(event.GetString())
756 if label not in self.datasets:
757 return
759 self.current_label = label
760 dset = self.datasets[label]
762 if not hasattr(dset, 'scale'):
763 dset.scale = dset.I.max()
764 dset.scale_method = 'raw_max'
765 dset.auto_scale = True
766 dset.bkg_qwid = 0.1
767 dset.bkg_nsmooth = 30
768 dset.bkg_porder = 40
770 bkgd = getattr(dset, 'bkgd', None)
771 if (bkgd is None
772 or (isinstance(bkgd, np.ndarray)
773 and (bkgd.sum() < 0.5/len(bkgd)))):
774 dset.bkgd = calc_bgr(dset)
776 meth_desc = keyof(SCALE_METHODS, dset.scale_method, 'raw_max')
778 self.wids['scale_method'].SetStringSelection(meth_desc)
779 self.wids['auto_scale'].SetValue(dset.auto_scale)
780 self.wids['scale'].SetValue(dset.scale)
781 self.wids['energy_ev'].SetLabel("%.1f" % (dset.energy*1000.0))
782 self.wids['wavelength'].SetLabel("%.6f" % (PLANCK_HC/(dset.energy*1000.0)))
784 self.wids['bkg_qwid'].SetValue(dset.bkg_qwid)
785 self.wids['bkg_nsmooth'].SetValue(dset.bkg_nsmooth)
786 self.wids['bkg_porder'].SetValue(dset.bkg_porder)
788 self.onPlotOne(label=label)
790 def set_scale(self, event=None, value=-1.0):
791 label = self.current_label
792 if label not in self.datasets:
793 return
794 if value < 0:
795 value = self.wids['scale'].GetValue()
796 self.datasets[label].scale = value # self.wids['scale'].GetValue()
798 def auto_scale(self, event=None):
799 label = self.current_label
800 if label not in self.datasets:
801 return
802 dset = self.datasets[label]
803 dset.auto_scale = self.wids['auto_scale'].IsChecked()
804 self.wids['scale_method'].Enable(dset.auto_scale)
806 if dset.auto_scale:
807 self.scale_data(dset, with_plot=True)
809 def scale_data(self, dset, with_plot=True):
810 meth_name = self.wids['scale_method'].GetStringSelection()
812 meth = dset.scale_method = SCALE_METHODS[meth_name]
814 # if not meth.startswith('raw'):
815 qwid = self.wids['bkg_qwid'].GetValue()
816 nsmooth = int(self.wids['bkg_nsmooth'].GetValue())
817 cheb_order = int(self.wids['bkg_porder'].GetValue())
818 dset.bkgd = calc_bgr(dset, qwid=qwid, nsmooth=nsmooth,
819 cheb_order=cheb_order)
820 dset.bkg_qwid = qwid
821 dset.bkg_nmsooth = nsmooth
822 dset.bkg_porder = cheb_order
824 scale = -1
825 if meth == 'raw_max':
826 scale = dset.I.max()
827 elif meth == 'raw_mean':
828 scale = dset.I.mean()
829 elif meth == 'sub_max':
830 scale = (dset.I - dset.bkgd).max()
831 elif meth == 'sub_mean':
832 scale = (dset.I - dset.bkgd).mean()
833 elif meth == 'bkg_max':
834 scale = (dset.bkgd).max()
835 elif meth == 'bkg_mean':
836 scale = (dset.bkgd).mean()
838 if scale > 0:
839 self.wids['scale'].SetValue(scale)
840 if with_plot:
841 self.onPlotOne()
843 def rename_dataset(self, newlabel):
844 dset = self.datasets.pop(self.current_label)
845 dset.label = newlabel
846 self.datasets[newlabel] = dset
847 self.current_label = newlabel
849 self.filelist.Clear()
850 for name in self.datasets:
851 self.filelist.Append(name)
854 def onRenameDataset(self, event=None):
855 RenameDialog(self, self.current_label, self.rename_dataset).Show()
858 def remove_dataset(self, dname=None, event=None):
859 if dname in self.datasets:
860 self.datasets.pop(dname)
862 self.filelist.Clear()
863 for name in self.datasets:
864 self.filelist.Append(name)
867 def remove_selected_datasets(self, event=None):
868 sel = []
869 for checked in self.filelist.GetCheckedStrings():
870 sel.append(str(checked))
871 if len(sel) < 1:
872 return
874 dlg = RemoveDialog(self, sel)
875 res = dlg.GetResponse()
876 dlg.Destroy()
878 if res.ok:
879 all = self.filelist.GetItems()
880 for dname in sel:
881 self.datasets.pop(dname)
882 all.remove(dname)
884 self.filelist.Clear()
885 for name in all:
886 self.filelist.Append(name)
888 def get_imdisplay(self, win=1):
889 wintitle='XRD Image Window %i' % win
890 opts = dict(wintitle=wintitle, win=win, image=True)
891 self.img_display = self.larch.symtable._plotter.get_display(**opts)
892 return self.img_display
894 def get_display(self, win=1, stacked=False):
895 wintitle='XRD Plot Window %i' % win
896 opts = dict(wintitle=wintitle, stacked=stacked, win=win, linewidth=3)
897 self.plot_display = self.larch.symtable._plotter.get_display(**opts)
898 return self.plot_display
900 def plot_dset(self, dset, plottype, newplot=True):
901 win = int(self.wids['plot_win'].GetStringSelection())
902 xscale = self.wids['xscale'].GetSelection()
903 opts = {'show_legend': True, 'xmax': None,
904 'xlabel': self.wids['xscale'].GetStringSelection(),
905 'ylabel':'Scaled Intensity',
906 'label': dset.label}
908 xdat = dset.q
909 if xscale == 2:
910 xdat = dset.d
911 opts['xmax'] = min(12.0, max(xdat))
912 elif xscale == 1:
913 xdat = dset.twth
915 ydat = 1.0*dset.I/dset.scale
916 if plottype == 'sub':
917 ydat = 1.0*(dset.I-dset.bkgd)/dset.scale
918 opts['ylabel'] = 'Scaled (Intensity - Background)'
920 pframe = self.get_display(win=win)
921 plot = pframe.plot if newplot else pframe.oplot
922 plot(xdat, ydat, **opts)
923 if plottype == 'raw+bkg':
924 y2dat = 1.0*dset.bkgd/dset.scale
925 opts['ylabel'] = 'Scaled Intensity with Background'
926 opts['label'] = 'background'
927 pframe.oplot(xdat, y2dat, **opts)
929 def onPlotOne(self, event=None, label=None):
930 if label is None:
931 label = self.current_label
932 if label not in self.datasets:
933 return
934 dset = self.datasets[label]
935 self.last_plot_type = 'one'
936 plottype = PLOT_TYPES.get(self.wids['plotone'].GetStringSelection(), 'raw')
937 self.plot_dset(dset, plottype, newplot=True)
938 wx.CallAfter(self.SetFocus)
940 def onPlotSel(self, event=None):
941 labels = self.filelist.GetCheckedStrings()
942 if len(labels) < 1:
943 return
944 self.last_plot_type = 'multi'
945 plottype = PLOT_TYPES.get(self.wids['plotsel'].GetStringSelection(), 'raw')
946 newplot = True
947 for label in labels:
948 dset = self.datasets.get(label, None)
949 if dset is not None:
950 self.plot_dset(dset, plottype, newplot=newplot)
951 newplot = False
952 wx.CallAfter(self.SetFocus)
954 def onPlotEither(self, event=None):
955 if self.last_plot_type == 'multi':
956 self.onPlotSel(event=event)
957 else:
958 self.onPlotOne(event=event)
960 def add_data(self, dataset, label=None, **kws):
961 if label is None:
962 label = 'XRD pattern'
963 if label in self.datasets:
964 print('label already in datasets: ', label )
965 else:
966 self.filelist.Append(label)
967 self.datasets[label] = dataset
968 self.show_dataset(label=label)
970class XRD1DApp(LarchWxApp):
971 def __init__(self, **kws):
972 LarchWxApp.__init__(self)
974 def createApp(self):
975 frame = XRD1DFrame()
976 frame.Show()
977 self.SetTopWindow(frame)
978 return True