Coverage for larch/wxxrd/xrd1d_display.py: 12%
729 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
1#!/usr/bin/env 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 get_cwd, gformat, fix_filename
28from larch.utils.physical_constants import PLANCK_HC
29from larch.xray import XrayBackground
30from larch.wxxas.xas_dialogs 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, 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, OkCancel,
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 try:
139 bgr = extract_background(dset.q, dset.I,
140 smooth_width=qwid,
141 iterations=nsmooth,
142 cheb_order=cheb_order)
143 except:
144 bgr = 0.0*dset.I
145 return bgr
148class WavelengthDialog(wx.Dialog):
149 """dialog for wavelength/energy"""
150 def __init__(self, parent, wavelength, callback=None):
152 self.parent = parent
153 self.callback = callback
155 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
156 title="Set Wavelength / Energy")
157 self.SetFont(Font(FONTSIZE))
158 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
160 self.wids = wids = {}
162 opts = dict(size=(90, -1), act_on_losefocus=True)
163 wids['wavelength'] = FloatCtrl(panel, value=wavelength, precision=7,
164 minval=1.0e-4, maxval=100, **opts)
166 en_ev = PLANCK_HC/wavelength
167 wids['energy'] = FloatCtrl(panel, value=en_ev, precision=2,
168 minval=50, maxval=5.e5, **opts)
171 wids['wavelength'].SetAction(self.set_wavelength)
172 wids['energy'].SetAction(self.set_energy)
174 panel.Add(SimpleText(panel, 'Wavelength(\u212B): '),
175 dcol=1, newrow=False)
176 panel.Add(wids['wavelength'], dcol=1)
177 panel.Add(SimpleText(panel, 'Energy (eV): '),
178 dcol=1, newrow=True)
179 panel.Add(wids['energy'], dcol=1)
181 panel.Add((10, 10), newrow=True)
183 panel.Add(Button(panel, 'Done', size=(150, -1),
184 action=self.onDone), newrow=True)
185 panel.pack()
186 sizer = wx.BoxSizer(wx.VERTICAL)
187 sizer.Add(panel, 1, LEFT, 5)
188 pack(self, sizer)
189 self.Fit()
191 w0, h0 = self.GetSize()
192 w1, h1 = self.GetBestSize()
193 self.SetSize((max(w0, w1)+25, max(h0, h1)+25))
195 def onDone(self, event=None):
196 if callable(self.callback):
197 self.callback(self.wids['wavelength'].GetValue())
198 self.Destroy()
200 def set_wavelength(self, value=1, event=None):
201 w = self.wids['wavelength'].GetValue()
203 self.wids['energy'].SetValue(PLANCK_HC/w, act=False)
205 def set_energy(self, value=10000, event=None):
206 w = self.wids['energy'].GetValue()
207 self.wids['wavelength'].SetValue(PLANCK_HC/w, act=False)
209class RenameDialog(wx.Dialog):
210 """dialog for renaming a pattern"""
211 def __init__(self, parent, name, callback=None):
213 self.parent = parent
214 self.callback = callback
216 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(550, 400),
217 title="Rename dataset")
218 self.SetFont(Font(FONTSIZE))
219 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
221 self.wids = wids = {}
222 wids['newname'] = wx.TextCtrl(panel, value=name, size=(150, -1))
224 panel.Add(SimpleText(panel, 'New Name: '), dcol=1, newrow=False)
225 panel.Add(wids['newname'], dcol=1)
226 panel.Add((10, 10), newrow=True)
228 panel.Add(Button(panel, 'Done', size=(150, -1),
229 action=self.onDone), newrow=True)
230 panel.pack()
231 sizer = wx.BoxSizer(wx.VERTICAL)
232 sizer.Add(panel, 1, LEFT, 5)
233 pack(self, sizer)
234 self.Fit()
236 w0, h0 = self.GetSize()
237 w1, h1 = self.GetBestSize()
238 self.SetSize((max(w0, w1)+25, max(h0, h1)+25))
240 def onDone(self, event=None):
241 if callable(self.callback):
242 self.callback(self.wids['newname'].GetValue())
243 self.Destroy()
245class ResetMaskDialog(wx.Dialog):
246 """dialog for wavelength/energy"""
247 def __init__(self, parent):
248 wx.Dialog.__init__(self, parent, wx.ID_ANY, size=(350, 300),
249 title="Unset Mask?")
250 self.SetFont(Font(FONTSIZE))
251 panel = GridPanel(self, ncols=3, nrows=4, pad=4, itemstyle=LEFT)
253 self.wids = wids = {}
255 warn_msg = 'This will remove the current mask!'
257 panel.Add(SimpleText(panel, warn_msg), dcol=2)
259 panel.Add(OkCancel(panel), dcol=2, newrow=True)
260 panel.pack()
262 sizer = wx.BoxSizer(wx.VERTICAL)
263 sizer.Add(panel, 1, LEFT, 5)
264 pack(self, sizer)
265 self.Fit()
266 w0, h0 = self.GetSize()
267 w1, h1 = self.GetBestSize()
268 self.SetSize((max(w0, w1)+25, max(h0, h1)+25))
270 def GetResponse(self):
271 self.Raise()
272 return (self.ShowModal() == wx.ID_OK)
274class XRD1DFrame(wx.Frame):
275 """browse 1D XRD patterns"""
277 def __init__(self, parent=None, wavelength=1.0, ponifile=None,
278 _larch=None, **kws):
280 wx.Frame.__init__(self, None, -1, title='1D XRD Browser',
281 style=FRAMESTYLE, size=(600, 600), **kws)
282 self.parent = parent
283 self.wavelength = wavelength
284 self.poni = {'wavelength': 1.e-10*self.wavelength} # ! meters!
285 self.pyfai_integrator = None
287 self.larch = _larch
288 if self.larch is None:
289 self.larch_buffer = LarchFrame(_larch=None, parent=self,
290 is_standalone=False,
291 with_raise=False,
292 exit_on_close=False)
294 self.larch = self.larch_buffer.larchshell
296 self.current_label = None
297 self.cif_browser = None
298 self.img_display = None
299 self.plot_display = None
300 self.mask = None
301 self.datasets = {}
302 self.form = {}
303 self.createMenus()
304 self.build()
305 self.set_wavelength(self.wavelength)
306 if ponifile is not None:
307 self.set_ponifile(ponifile)
309 def createMenus(self):
310 fmenu = wx.Menu()
311 cmenu = wx.Menu()
312 smenu = wx.Menu()
313 MenuItem(self, fmenu, "Read XY File",
314 "Read XRD 1D data from XY FIle",
315 self.onReadXY)
317 MenuItem(self, fmenu, "Save XY File",
318 "Save XRD 1D data to XY FIle",
319 self.onSaveXY)
320 self.tiff_reader = MenuItem(self, fmenu, "Read TIFF XRD Image",
321 "Read XRD 2D image to be integrated",
322 self.onReadTIFF)
323 self.tiff_reader.Enable(self.poni.get('dist', -1) > 0)
324 fmenu.AppendSeparator()
325 MenuItem(self, fmenu, "Change Label for Current Pattern",
326 "Rename Current Pattern",
327 self.onRenameDataset)
329 MenuItem(self, fmenu, "Remove Selected Patterns",
330 "Remove Selected Patterns",
331 self.remove_selected_datasets)
333 fmenu.AppendSeparator()
334 MenuItem(self, fmenu, "&Quit\tCtrl+Q", "Quit program", self.onClose)
336 MenuItem(self, smenu, "Browse AmMin Crystal Structures",
337 "Browse Structures from American Mineralogical Database",
338 self.onCIFBrowse)
340 MenuItem(self, cmenu, "Read PONI Calibration File",
341 "Read PONI Calibration (pyFAI) File",
342 self.onReadPONI)
344 MenuItem(self, cmenu, "Set Energy/Wavelength",
345 "Set Energy and Wavelength",
346 self.onSetWavelength)
348 MenuItem(self, cmenu, "Set Mask for imported Images",
349 "Read Mask for Imported TIFF XRD Images", self.onReadMask)
351 m = MenuItem(self, cmenu, "Unset Mask",
352 "Reset to use no mask for Imported TIFF XRD Images",
353 self.onUnsetMask)
354 self.unset_mask_menu = m
355 m.Enable(False)
357 m = MenuItem(self, cmenu, "Show Mask Image",
358 "Show image of mask", self.onShowMask)
359 self.show_mask_menu = m
360 m.Enable(False)
362 menubar = wx.MenuBar()
363 menubar.Append(fmenu, "&File")
364 menubar.Append(cmenu, "&Calibration and Mask")
365 menubar.Append(smenu, "&Search CIF Structures")
366 self.SetMenuBar(menubar)
369 def onClose(self, event=None):
370 try:
371 if self.panel is not None:
372 self.panel.win_config.Close(True)
373 if self.panel is not None:
374 self.panel.win_config.Destroy()
375 except:
376 pass
378 for attr in ('cif_browser', 'img_display', 'plot_display'):
379 winx = getattr(self, attr, None)
380 if winx is not None:
381 try:
382 winx.Destroy()
383 except:
384 pass
386 if hasattr(self.larch.symtable, '_plotter'):
387 wx.CallAfter(self.larch.symtable._plotter.close_all_displays)
389 self.Destroy()
391 def onSetWavelength(self, event=None):
392 WavelengthDialog(self, self.wavelength, self.set_wavelength).Show()
394 def onReadPONI(self, event=None):
395 sfile = FileOpen(self, 'Read PONI (pyFAI) calibration file',
396 default_file='XRD.poni',
397 default_dir=get_cwd(),
398 wildcard="PONI Files(*.poni)|*.poni|All files (*.*)|*.*")
400 if sfile is not None:
401 try:
402 self.set_poni(read_poni(sfile), with_pyfai=True)
403 except:
404 title = "Could not read PONI File"
405 message = [f"Could not read PONI file {sfile}"]
406 ExceptionPopup(self, title, message)
408 top, xfile = os.path.split(sfile)
409 os.chdir(top)
411 if self.pyfai_integrator is None:
412 try:
413 self.pyfai_integrator = AzimuthalIntegrator(**self.poni)
414 except:
415 self.pyfai_integrator = None
417 self.tiff_reader.Enable(self.pyfai_integrator is not None)
419 self.set_wavelength(self.poni['wavelength']*1.e10)
421 def onReadXY(self, event=None):
422 sfile = FileOpen(self, 'Read XY Data',
423 default_file='XRD.xy',
424 default_dir=get_cwd(),
425 wildcard=XYWcards)
426 if sfile is not None:
427 top, xfile = os.path.split(sfile)
428 os.chdir(top)
429 dxrd = xrd1d(file=sfile, wavelength=self.wavelength)
430 self.add_data(dxrd, label=xfile)
432 def onUnsetMask(self, event=None):
433 if self.mask is not None:
434 dlg = ResetMaskDialog(self)
435 if dlg.GetResponse():
436 self.mask = None
437 self.unset_mask_menu.Enable(False)
438 self.show_mask_menu.Enable(False)
440 def onReadMask(self, event=None):
441 sfile = FileOpen(self, 'Read Mask Image File',
442 default_file='XRD.mask',
443 default_dir=get_cwd(),
444 wildcard="Mask Files(*.mask)|*.mask|All files (*.*)|*.*")
446 if sfile is not None:
447 valid_mask = False
448 try:
449 img = tifffile.imread(sfile)
450 valid_mask = len(img.shape)==2 and img.max() == 1 and img.min() == 0
451 except:
452 valid_mask = False
453 if valid_mask:
454 self.mask = (1 - img[::-1, :]).astype(img.dtype)
455 self.unset_mask_menu.Enable(True)
456 self.show_mask_menu.Enable(True)
457 else:
458 title = "Could not use mask file"
459 message = [f"Could not use {sfile:s} as a mask file"]
460 o = ExceptionPopup(self, title, message)
462 def onShowMask(self, event=None):
463 if self.mask is not None:
464 imd = self.get_imdisplay()
465 imd.display(self.mask, colomap='gray', auto_contrast=True)
467 def onReadTIFF(self, event=None):
468 sfile = FileOpen(self, 'Read TIFF XRD Image',
469 default_file='XRD.tiff',
470 default_dir=get_cwd(),
471 wildcard=TIFFWcards)
472 if sfile is not None:
473 top, fname = os.path.split(sfile)
475 if self.pyfai_integrator is None:
476 try:
477 self.pyfai_integrator = AzimuthalIntegrator(**self.poni)
478 except:
479 title = "Could not create pyFAI integrator: bad PONI data?"
480 message = [f"Could not create pyFAI integrator"]
481 ExceptionPopup(self, title, message)
482 if self.pyfai_integrator is None:
483 return
485 img = tifffile.imread(sfile)
486 self.display_xrd_image(img, label=fname)
488 def display_xrd_image(self, img, label='Image'):
489 if self.mask is not None:
490 if (self.mask.shape == img.shape):
491 img = img*self.mask
492 else:
493 title = "Could not apply current mask"
494 message = [f"Could not apply current mask [shape={self.mask.shape}]",
495 f"to this XRD image [shape={img.shape}]"]
496 o = ExceptionPopup(self, title, message)
498 if (img.max() > MAXVAL_INT16) and (img.max() < MAXVAL_INT16 + 64):
499 #probably really 16bit data
500 img[np.where(img>MAXVAL_INT16)] = 0
501 else:
502 img[np.where(img>MAXVAL)] = 0
503 img[np.where(img<-1)] = -1
504 img = img[::-1, :]
506 imd = self.get_imdisplay()
507 imd.display(img, colomap='gray', auto_contrast=True)
509 integrate = self.pyfai_integrator.integrate1d
510 q, ix = integrate(img, 2048, method='csr', unit='q_A^-1',
511 correctSolidAngle=True,
512 polarization_factor=0.999)
514 dxrd = xrd1d(label=label, x=q, I=ix, xtype='q',
515 wavelength=self.wavelength)
516 self.add_data(dxrd, label=label)
519 def onCIFBrowse(self, event=None):
520 shown = False
521 if self.cif_browser is not None:
522 try:
523 self.cif_browser.Raise()
524 shown = True
525 except:
526 del self.cif_browser
527 shown = False
528 if not shown:
529 self.cif_browser = CIFFrame(usecif_callback=self.onLoadCIF,
530 _larch=self.larch)
531 self.cif_browser.Raise()
533 def onLoadCIF(self, cif=None):
534 if cif is None:
535 return
536 t0 = time.time()
538 energy = E_from_lambda(self.wavelength)
540 sfact = cif.get_structure_factors(wavelength=self.wavelength)
541 try:
542 self.cif_browser.cifdb.set_hkls(self.current_cif.ams_id, sfact.hkls)
543 except:
544 pass
546 mineral = getattr(cif, 'mineral', None)
547 label = getattr(mineral, 'name', '')
548 if len(label) < 0:
549 label = getattr(cif, 'formula', '')
550 cifid = getattr(cif, 'ams_id', '')
551 if len(label) < 1 and len(cifid) > 0:
552 label = 'CIF:{cifid}'
553 else:
554 label = f'{label}, CIF:{cifid}'
556 try:
557 q = self.datasets[self.current_label].q
558 except:
559 q = np.linspace(0, 10, 2048)
561 sigma = 2.5*(q[1] - q[0])
563 intensity = q*0.0
564 for cen, amp in zip(sfact.q, sfact.intensity):
565 intensity += gaussian(q, amplitude=amp, center=cen, sigma=sigma)
567 xdat = xrd1d(label=label, energy=energy, wavelength=self.wavelength)
568 xdat.set_xy_data(np.array([q, intensity/max(intensity)]), 'q')
569 self.add_data(xdat, label=label)
572 def onSaveXY(self, event=None):
573 fname = fix_filename(self.current_label.replace('.', '_') + '.xy')
574 sfile = FileSave(self, 'Save XY Data', default_file=fname)
575 if sfile is None:
576 return
578 label = self.current_label
579 dset = self.datasets[label]
581 xscale = self.wids['xscale'].GetSelection()
582 xlabel = self.wids['xscale'].GetStringSelection()
583 xdat = dset.twth
584 xlabel = pyFAI.units.TTH_DEG
585 if xscale == 0:
586 xdat = dset.q
587 xlabel = pyFAI.units.Q_A
589 ydat = 1.0*dset.I/dset.scale
590 wavelength = PLANCK_HC/(dset.energy*1000.0)
591 buff = [f"# XY data from {label}",
592 f"# wavelength (Ang) = {gformat(wavelength)}",
593 "# Calibration data from pyFAI:"]
594 for key, value in self.poni.items():
595 buff.append(f"# {key}: {value}")
596 buff.append("#-------------------------------------")
597 buff.append(f"# {xlabel} Intensity")
599 for x, y in zip(xdat, ydat):
600 buff.append(f" {gformat(x, 13)} {gformat(y, 13)}")
601 buff.append('')
602 with open(sfile, 'w') as fh:
603 fh.write('\n'.join(buff))
606 def build(self):
607 sizer = wx.GridBagSizer(3, 3)
608 sizer.SetVGap(3)
609 sizer.SetHGap(3)
611 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
612 splitter.SetMinimumPaneSize(220)
614 # left side: list of XRD 1D patterns
615 lpanel = wx.Panel(splitter)
616 lpanel.SetMinSize((275, 350))
617 # rpanel = scrolled.ScrolledPanel(splitter)
618 rpanel = wx.Panel(splitter)
619 rpanel.SetMinSize((400, 350))
620 rpanel.SetSize((750, 550))
622 ltop = wx.Panel(lpanel)
624 def Btn(msg, x, act):
625 b = Button(ltop, msg, size=(x, 30), action=act)
626 b.SetFont(Font(FONTSIZE))
627 return b
629 sel_none = Btn('Select None', 130, self.onSelNone)
630 sel_all = Btn('Select All', 130, self.onSelAll)
632 self.filelist = FileCheckList(lpanel, main=self,
633 select_action=self.show_dataset,
634 remove_action=self.remove_dataset)
635 set_color(self.filelist, 'list_fg', bg='list_bg')
637 tsizer = wx.BoxSizer(wx.HORIZONTAL)
638 tsizer.Add(sel_all, 1, LEFT|wx.GROW, 1)
639 tsizer.Add(sel_none, 1, LEFT|wx.GROW, 1)
640 pack(ltop, tsizer)
642 sizer = wx.BoxSizer(wx.VERTICAL)
643 sizer.Add(ltop, 0, LEFT|wx.GROW, 1)
644 sizer.Add(self.filelist, 1, LEFT|wx.GROW|wx.ALL, 1)
645 pack(lpanel, sizer)
647 # right side: parameters controlling display
648 panel = GridPanel(rpanel, ncols=6, nrows=10, pad=3, itemstyle=LEFT)
649 panel.sizer.SetVGap(3)
650 panel.sizer.SetHGap(3)
652 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.BOLD)
654 # title row
655 self.wids = wids = {}
656 title = SimpleText(panel, '1D XRD Data Display', font=Font(FONTSIZE+2),
657 colour=COLORS['title'], style=LEFT)
659 self.last_plot_type = 'one'
660 self.plotone = Button(panel, 'Plot Current ', size=(125, -1),
661 action=self.onPlotOne)
662 self.plotsel = Button(panel, 'Plot Selected ', size=(125, -1),
663 action=self.onPlotSel)
664 wids['plotone'] = Choice(panel, choices=PLOT_CHOICES, default=0,
665 action=self.onPlotOne, size=(200, -1))
666 wids['plotsel'] = Choice(panel, choices=PLOT_CHOICES_MULTI, default=0,
667 action=self.onPlotSel, size=(200, -1))
668 wids['xscale'] = Choice(panel, choices=X_SCALES, default=0,
669 action=self.onPlotEither, size=(100, -1))
671 opts = dict(default=False, size=(200, -1), action=self.onPlotEither)
672 wids['plot_win'] = Choice(panel, size=(100, -1), choices=PlotWindowChoices,
673 action=self.onPlotEither)
674 wids['plot_win'].SetStringSelection('1')
676 wids['auto_scale'] = Check(panel, default=True, label='auto?',
677 action=self.auto_scale)
678 wids['scale_method'] = Choice(panel, choices=list(SCALE_METHODS.keys()),
679 size=(250, -1), action=self.auto_scale, default=0)
681 wids['scale'] = FloatCtrl(panel, value=1.0, size=(90, -1), precision=2,
682 action=self.set_scale)
683 wids['wavelength'] = SimpleText(panel, label="%.6f" % (self.wavelength), size=(100, -1))
684 wids['energy_ev'] = SimpleText(panel, label="%.1f" % (PLANCK_HC/self.wavelength), size=(100, -1))
686 wids['bkg_qwid'] = FloatSpin(panel, value=0.1, size=(90, -1), digits=2,
687 increment=0.01,
688 min_val=0.001, max_val=5, action=self.on_bkg)
689 wids['bkg_nsmooth'] = FloatSpin(panel, value=30, size=(90, -1),
690 digits=0, min_val=2, max_val=100, action=self.on_bkg)
691 wids['bkg_porder'] = FloatSpin(panel, value=40, size=(90, -1),
692 digits=0, min_val=2, max_val=100, action=self.on_bkg)
694 def CopyBtn(name):
695 return Button(panel, 'Copy to Seleceted', size=(150, -1),
696 action=partial(self.onCopyAttr, name))
698 wids['bkg_copy'] = CopyBtn('bkg')
699 wids['scale_copy'] = CopyBtn('scale_method')
701 def slabel(txt):
702 return wx.StaticText(panel, label=txt)
704 panel.Add(title, style=LEFT, dcol=5)
705 panel.Add(self.plotsel, newrow=True)
706 panel.Add(wids['plotsel'], dcol=2)
707 panel.Add(slabel(' X scale: '), dcol=2, style=LEFT)
708 panel.Add(wids['xscale'], style=RIGHT)
710 panel.Add(self.plotone, newrow=True)
711 panel.Add(wids['plotone'], dcol=2)
712 panel.Add(slabel(' Plot Window: '), dcol=2)
713 panel.Add(wids['plot_win'], style=RIGHT)
715 panel.Add((5, 5))
716 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True)
717 panel.Add((5, 5))
720 panel.Add(slabel(' Scaling Factor: '), style=LEFT, newrow=True)
721 panel.Add(wids['scale'])
722 panel.Add(wids['auto_scale'])
723 panel.Add(slabel(' Scaling Method: '), style=LEFT, newrow=True)
724 panel.Add(wids['scale_method'], dcol=3)
725 panel.Add(wids['scale_copy'], dcol=2, style=RIGHT)
727 panel.Add((5, 5))
728 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True)
729 panel.Add((5, 5))
731 panel.Add(slabel(' Background Subtraction Parameters: '), dcol=3, style=LEFT, newrow=True)
732 panel.Add(wids['bkg_copy'], dcol=3, style=RIGHT)
734 panel.Add(slabel(' Q width (\u212B\u207B\u00B9): '), style=LEFT, newrow=True)
735 panel.Add(wids['bkg_qwid'])
736 panel.Add(slabel(' Smoothing Steps: '), style=LEFT, newrow=True)
737 panel.Add(wids['bkg_nsmooth'], dcol=2)
738 panel.Add(slabel(' Polynomial Order: '), style=LEFT, newrow=True)
739 panel.Add(wids['bkg_porder'])
741 panel.Add((5, 5))
742 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True)
743 panel.Add((5, 5))
745 panel.Add(slabel(' Calibration, Calculated XRD Patterns: '), dcol=6, style=LEFT, newrow=True)
746 panel.Add(slabel(' X-ray Energy (eV): '), style=LEFT, newrow=True)
747 panel.Add(wids['energy_ev'], dcol=1)
748 panel.Add(slabel(' Wavelength (\u212B): '), style=LEFT, newrow=False)
749 panel.Add(wids['wavelength'], dcol=2)
752 panel.Add((5, 5))
754 panel.pack()
756 sizer = wx.BoxSizer(wx.VERTICAL)
757 sizer.Add((5, 5), 0, LEFT, 3)
758 sizer.Add(panel, 0, LEFT, 3)
759 sizer.Add((5, 5), 0, LEFT, 3)
760 pack(rpanel, sizer)
762 # rpanel.SetupScrolling()
764 splitter.SplitVertically(lpanel, rpanel, 1)
765 mainsizer = wx.BoxSizer(wx.VERTICAL)
766 mainsizer.Add(splitter, 1, wx.GROW|wx.ALL, 5)
767 pack(self, mainsizer)
768 self.SetSize((875, 450))
770 self.Show()
771 self.Raise()
773 def set_ponifile(self, ponifile, with_pyfai=True):
774 "set poni from datafile"
775 try:
776 self.set_poni(read_poni(ponifile), with_pyfai=with_pyfai)
777 except:
778 pass
780 def set_poni(self, poni, with_pyfai=True):
781 "set poni from dict"
782 try:
783 self.poni.update(poni)
784 self.set_wavelength(self.poni['wavelength']*1.e10)
785 self.tiff_reader.Enable(self.poni.get('dist', -1) > 0)
786 except:
787 pass
789 if with_pyfai:
790 try:
791 self.pyfai_integrator = AzimuthalIntegrator(**self.poni)
792 except:
793 self.pyfai_integrator = None
796 def set_wavelength(self, value):
797 self.wavelength = value
798 self.wids['wavelength'].SetLabel("%.6f" % value)
799 self.wids['energy_ev'].SetLabel("%.1f" % (PLANCK_HC/value))
800 for key, dset in self.datasets.items():
801 dset.set_wavelength(value)
803 def onCopyAttr(self, name=None, event=None):
804 # print("Copy ", name, event)
805 if name == 'bkg':
806 qwid = self.wids['bkg_qwid'].GetValue()
807 nsmooth = int(self.wids['bkg_nsmooth'].GetValue())
808 cheb_order = int(self.wids['bkg_porder'].GetValue())
810 for label in self.filelist.GetCheckedStrings():
811 dset = self.datasets.get(label, None)
812 if dset is not None:
813 dset.bkg_qwid = qwid
814 dset.bkg_nsmooth = nsmooth
815 dset.bkg_porder = cheb_order
816 # print("redo bkg for ", label)
817 dset.bkgd = calc_bgr(dset, qwid=qwid, nsmooth=nsmooth,
818 cheb_order=cheb_order)
820 elif name == 'scale_method':
821 for label in self.filelist.GetCheckedStrings():
822 dset = self.datasets.get(label, None)
823 if dset is not None:
824 # print("redo scale for ", label)
825 self.scale_data(dset, with_plot=False)
828 def onSelNone(self, event=None):
829 self.filelist.select_none()
831 def onSelAll(self, event=None):
832 self.filelist.select_all()
834 def on_bkg(self, event=None, value=None):
835 try:
836 qwid = self.wids['bkg_qwid'].GetValue()
837 nsmooth = int(self.wids['bkg_nsmooth'].GetValue())
838 cheb_order = int(self.wids['bkg_porder'].GetValue())
839 except:
840 return
841 label = self.current_label
842 if label not in self.datasets:
843 return
844 dset = self.datasets[label]
845 dset.bkgd = calc_bgr(dset, qwid=qwid, nsmooth=nsmooth,
846 cheb_order=cheb_order)
847 if 'back' not in self.wids['plotone'].GetStringSelection().lower():
848 self.wids['plotone'].SetSelection(1)
849 else:
850 self.onPlotOne()
852 def show_dataset(self, event=None, label=None):
853 # print('show xd1d ', event, label)
854 if label is None and event is not None:
855 label = str(event.GetString())
856 if label not in self.datasets:
857 return
859 self.current_label = label
860 dset = self.datasets[label]
862 if not hasattr(dset, 'scale'):
863 dset.scale = dset.I.max()
864 dset.scale_method = 'raw_max'
865 dset.auto_scale = True
866 dset.bkg_qwid = 0.1
867 dset.bkg_nsmooth = 30
868 dset.bkg_porder = 40
870 bkgd = getattr(dset, 'bkgd', None)
871 if (bkgd is None
872 or (isinstance(bkgd, np.ndarray)
873 and (bkgd.sum() < 0.5/len(bkgd)))):
874 dset.bkgd = calc_bgr(dset)
876 meth_desc = keyof(SCALE_METHODS, dset.scale_method, 'raw_max')
878 self.wids['scale_method'].SetStringSelection(meth_desc)
879 self.wids['auto_scale'].SetValue(dset.auto_scale)
880 self.wids['scale'].SetValue(dset.scale)
881 self.wids['energy_ev'].SetLabel("%.1f" % (dset.energy*1000.0))
882 self.wids['wavelength'].SetLabel("%.6f" % (PLANCK_HC/(dset.energy*1000.0)))
884 self.wids['bkg_qwid'].SetValue(dset.bkg_qwid)
885 self.wids['bkg_nsmooth'].SetValue(dset.bkg_nsmooth)
886 self.wids['bkg_porder'].SetValue(dset.bkg_porder)
888 self.onPlotOne(label=label)
890 def set_scale(self, event=None, value=-1.0):
891 label = self.current_label
892 if label not in self.datasets:
893 return
894 if value < 0:
895 value = self.wids['scale'].GetValue()
896 self.datasets[label].scale = value # self.wids['scale'].GetValue()
898 def auto_scale(self, event=None):
899 label = self.current_label
900 if label not in self.datasets:
901 return
902 dset = self.datasets[label]
903 dset.auto_scale = self.wids['auto_scale'].IsChecked()
904 self.wids['scale_method'].Enable(dset.auto_scale)
906 if dset.auto_scale:
907 self.scale_data(dset, with_plot=True)
909 def scale_data(self, dset, with_plot=True):
910 meth_name = self.wids['scale_method'].GetStringSelection()
912 meth = dset.scale_method = SCALE_METHODS[meth_name]
914 # if not meth.startswith('raw'):
915 qwid = self.wids['bkg_qwid'].GetValue()
916 nsmooth = int(self.wids['bkg_nsmooth'].GetValue())
917 cheb_order = int(self.wids['bkg_porder'].GetValue())
918 dset.bkgd = calc_bgr(dset, qwid=qwid, nsmooth=nsmooth,
919 cheb_order=cheb_order)
920 dset.bkg_qwid = qwid
921 dset.bkg_nmsooth = nsmooth
922 dset.bkg_porder = cheb_order
924 scale = -1
925 if meth == 'raw_max':
926 scale = dset.I.max()
927 elif meth == 'raw_mean':
928 scale = dset.I.mean()
929 elif meth == 'sub_max':
930 scale = (dset.I - dset.bkgd).max()
931 elif meth == 'sub_mean':
932 scale = (dset.I - dset.bkgd).mean()
933 elif meth == 'bkg_max':
934 scale = (dset.bkgd).max()
935 elif meth == 'bkg_mean':
936 scale = (dset.bkgd).mean()
938 if scale > 0:
939 self.wids['scale'].SetValue(scale)
940 if with_plot:
941 self.onPlotOne()
943 def rename_dataset(self, newlabel):
944 dset = self.datasets.pop(self.current_label)
945 dset.label = newlabel
946 self.datasets[newlabel] = dset
947 self.current_label = newlabel
949 self.filelist.Clear()
950 for name in self.datasets:
951 self.filelist.Append(name)
954 def onRenameDataset(self, event=None):
955 RenameDialog(self, self.current_label, self.rename_dataset).Show()
958 def remove_dataset(self, dname=None, event=None):
959 if dname in self.datasets:
960 self.datasets.pop(dname)
962 self.filelist.Clear()
963 for name in self.datasets:
964 self.filelist.Append(name)
967 def remove_selected_datasets(self, event=None):
968 sel = []
969 for checked in self.filelist.GetCheckedStrings():
970 sel.append(str(checked))
971 if len(sel) < 1:
972 return
974 dlg = RemoveDialog(self, sel)
975 res = dlg.GetResponse()
976 dlg.Destroy()
978 if res.ok:
979 all = self.filelist.GetItems()
980 for dname in sel:
981 self.datasets.pop(dname)
982 all.remove(dname)
984 self.filelist.Clear()
985 for name in all:
986 self.filelist.Append(name)
988 def get_imdisplay(self, win=1):
989 wintitle='XRD Image Window %i' % win
990 opts = dict(wintitle=wintitle, win=win, image=True)
991 self.img_display = self.larch.symtable._plotter.get_display(**opts)
992 return self.img_display
994 def get_display(self, win=1, stacked=False):
995 wintitle='XRD Plot Window %i' % win
996 opts = dict(wintitle=wintitle, stacked=stacked, win=win, linewidth=3)
997 self.plot_display = self.larch.symtable._plotter.get_display(**opts)
998 return self.plot_display
1000 def plot_dset(self, dset, plottype, newplot=True):
1001 win = int(self.wids['plot_win'].GetStringSelection())
1002 xscale = self.wids['xscale'].GetSelection()
1003 opts = {'show_legend': True, 'xmax': None,
1004 'xlabel': self.wids['xscale'].GetStringSelection(),
1005 'ylabel':'Scaled Intensity',
1006 'label': dset.label}
1008 xdat = dset.q
1009 if xscale == 2:
1010 xdat = dset.d
1011 opts['xmax'] = min(12.0, max(xdat))
1012 elif xscale == 1:
1013 xdat = dset.twth
1015 ydat = 1.0*dset.I/dset.scale
1016 if plottype == 'sub':
1017 ydat = 1.0*(dset.I-dset.bkgd)/dset.scale
1018 opts['ylabel'] = 'Scaled (Intensity - Background)'
1020 pframe = self.get_display(win=win)
1021 plot = pframe.plot if newplot else pframe.oplot
1022 plot(xdat, ydat, **opts)
1023 if plottype == 'raw+bkg':
1024 y2dat = 1.0*dset.bkgd/dset.scale
1025 opts['ylabel'] = 'Scaled Intensity with Background'
1026 opts['label'] = 'background'
1027 pframe.oplot(xdat, y2dat, **opts)
1029 def onPlotOne(self, event=None, label=None):
1030 if label is None:
1031 label = self.current_label
1032 if label not in self.datasets:
1033 return
1034 dset = self.datasets[label]
1035 self.last_plot_type = 'one'
1036 plottype = PLOT_TYPES.get(self.wids['plotone'].GetStringSelection(), 'raw')
1037 self.plot_dset(dset, plottype, newplot=True)
1038 wx.CallAfter(self.SetFocus)
1040 def onPlotSel(self, event=None):
1041 labels = self.filelist.GetCheckedStrings()
1042 if len(labels) < 1:
1043 return
1044 self.last_plot_type = 'multi'
1045 plottype = PLOT_TYPES.get(self.wids['plotsel'].GetStringSelection(), 'raw')
1046 newplot = True
1047 for label in labels:
1048 dset = self.datasets.get(label, None)
1049 if dset is not None:
1050 self.plot_dset(dset, plottype, newplot=newplot)
1051 newplot = False
1052 wx.CallAfter(self.SetFocus)
1054 def onPlotEither(self, event=None):
1055 if self.last_plot_type == 'multi':
1056 self.onPlotSel(event=event)
1057 else:
1058 self.onPlotOne(event=event)
1060 def add_data(self, dataset, label=None, **kws):
1061 if label is None:
1062 label = 'XRD pattern'
1063 if label in self.datasets:
1064 print('label already in datasets: ', label )
1065 else:
1066 self.filelist.Append(label)
1067 self.datasets[label] = dataset
1068 self.show_dataset(label=label)
1070class XRD1DApp(LarchWxApp):
1071 def __init__(self, **kws):
1072 LarchWxApp.__init__(self)
1074 def createApp(self):
1075 frame = XRD1DFrame()
1076 frame.Show()
1077 self.SetTopWindow(frame)
1078 return True