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

1#!/usr/bin/env pythonw 

2''' 

3GUI for displaying 1D XRD images 

4 

5''' 

6import os 

7import sys 

8import time 

9 

10from functools import partial 

11 

12import numpy as np 

13from numpy.polynomial.chebyshev import chebfit, chebval 

14 

15from pyFAI.azimuthalIntegrator import AzimuthalIntegrator 

16import pyFAI.units 

17pyFAI.use_opencl = False 

18 

19import wx 

20import wx.lib.scrolledpanel as scrolled 

21from wxmplot import PlotPanel 

22 

23from lmfit.lineshapes import gaussian 

24 

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 

32 

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) 

38 

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) 

47 

48MAXVAL = 2**32 - 2**15 

49MAXVAL_INT16 = 2**16 - 8 

50 

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

54 

55X_SCALES = [u'q (\u212B\u207B\u00B9)', u'2\u03B8 (\u00B0)', u'd (\u212B)'] 

56Y_SCALES = ['linear', 'log'] 

57 

58PLOT_TYPES = {'Raw Data': 'raw', 

59 'Raw Data + Background' : 'raw+bkg', 

60 'Background-subtracted Data': 'sub'} 

61 

62PLOT_CHOICES = list(PLOT_TYPES.keys()) 

63PLOT_CHOICES_MULTI = [PLOT_CHOICES[0], PLOT_CHOICES[2]] 

64 

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

71 

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 

83 

84 

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) 

91 

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

95 

96 y_avg = np.average(y) 

97 y_min = np.min(y) 

98 

99 y_c = y_avg + 2. * (y_avg - y_min) 

100 y[y > y_c] = y_c 

101 

102 window_size = N_float*2+1 

103 

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] 

117 

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 

126 

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) 

136 

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 

146 

147 

148class WavelengthDialog(wx.Dialog): 

149 """dialog for wavelength/energy""" 

150 def __init__(self, parent, wavelength, callback=None): 

151 

152 self.parent = parent 

153 self.callback = callback 

154 

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) 

159 

160 self.wids = wids = {} 

161 

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) 

165 

166 en_ev = PLANCK_HC/wavelength 

167 wids['energy'] = FloatCtrl(panel, value=en_ev, precision=2, 

168 minval=50, maxval=5.e5, **opts) 

169 

170 

171 wids['wavelength'].SetAction(self.set_wavelength) 

172 wids['energy'].SetAction(self.set_energy) 

173 

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) 

180 

181 panel.Add((10, 10), newrow=True) 

182 

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

190 

191 w0, h0 = self.GetSize() 

192 w1, h1 = self.GetBestSize() 

193 self.SetSize((max(w0, w1)+25, max(h0, h1)+25)) 

194 

195 def onDone(self, event=None): 

196 if callable(self.callback): 

197 self.callback(self.wids['wavelength'].GetValue()) 

198 self.Destroy() 

199 

200 def set_wavelength(self, value=1, event=None): 

201 w = self.wids['wavelength'].GetValue() 

202 

203 self.wids['energy'].SetValue(PLANCK_HC/w, act=False) 

204 

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) 

208 

209class RenameDialog(wx.Dialog): 

210 """dialog for renaming a pattern""" 

211 def __init__(self, parent, name, callback=None): 

212 

213 self.parent = parent 

214 self.callback = callback 

215 

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) 

220 

221 self.wids = wids = {} 

222 wids['newname'] = wx.TextCtrl(panel, value=name, size=(150, -1)) 

223 

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) 

227 

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

235 

236 w0, h0 = self.GetSize() 

237 w1, h1 = self.GetBestSize() 

238 self.SetSize((max(w0, w1)+25, max(h0, h1)+25)) 

239 

240 def onDone(self, event=None): 

241 if callable(self.callback): 

242 self.callback(self.wids['newname'].GetValue()) 

243 self.Destroy() 

244 

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) 

252 

253 self.wids = wids = {} 

254 

255 warn_msg = 'This will remove the current mask!' 

256 

257 panel.Add(SimpleText(panel, warn_msg), dcol=2) 

258 

259 panel.Add(OkCancel(panel), dcol=2, newrow=True) 

260 panel.pack() 

261 

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

269 

270 def GetResponse(self): 

271 self.Raise() 

272 return (self.ShowModal() == wx.ID_OK) 

273 

274class XRD1DFrame(wx.Frame): 

275 """browse 1D XRD patterns""" 

276 

277 def __init__(self, parent=None, wavelength=1.0, ponifile=None, 

278 _larch=None, **kws): 

279 

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 

286 

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) 

293 

294 self.larch = self.larch_buffer.larchshell 

295 

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) 

308 

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) 

316 

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) 

328 

329 MenuItem(self, fmenu, "Remove Selected Patterns", 

330 "Remove Selected Patterns", 

331 self.remove_selected_datasets) 

332 

333 fmenu.AppendSeparator() 

334 MenuItem(self, fmenu, "&Quit\tCtrl+Q", "Quit program", self.onClose) 

335 

336 MenuItem(self, smenu, "Browse AmMin Crystal Structures", 

337 "Browse Structures from American Mineralogical Database", 

338 self.onCIFBrowse) 

339 

340 MenuItem(self, cmenu, "Read PONI Calibration File", 

341 "Read PONI Calibration (pyFAI) File", 

342 self.onReadPONI) 

343 

344 MenuItem(self, cmenu, "Set Energy/Wavelength", 

345 "Set Energy and Wavelength", 

346 self.onSetWavelength) 

347 

348 MenuItem(self, cmenu, "Set Mask for imported Images", 

349 "Read Mask for Imported TIFF XRD Images", self.onReadMask) 

350 

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) 

356 

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) 

361 

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) 

367 

368 

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 

377 

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 

385 

386 if hasattr(self.larch.symtable, '_plotter'): 

387 wx.CallAfter(self.larch.symtable._plotter.close_all_displays) 

388 

389 self.Destroy() 

390 

391 def onSetWavelength(self, event=None): 

392 WavelengthDialog(self, self.wavelength, self.set_wavelength).Show() 

393 

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 (*.*)|*.*") 

399 

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) 

407 

408 top, xfile = os.path.split(sfile) 

409 os.chdir(top) 

410 

411 if self.pyfai_integrator is None: 

412 try: 

413 self.pyfai_integrator = AzimuthalIntegrator(**self.poni) 

414 except: 

415 self.pyfai_integrator = None 

416 

417 self.tiff_reader.Enable(self.pyfai_integrator is not None) 

418 

419 self.set_wavelength(self.poni['wavelength']*1.e10) 

420 

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) 

431 

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) 

439 

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 (*.*)|*.*") 

445 

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) 

461 

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) 

466 

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) 

474 

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 

484 

485 img = tifffile.imread(sfile) 

486 self.display_xrd_image(img, label=fname) 

487 

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) 

497 

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

505 

506 imd = self.get_imdisplay() 

507 imd.display(img, colomap='gray', auto_contrast=True) 

508 

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) 

513 

514 dxrd = xrd1d(label=label, x=q, I=ix, xtype='q', 

515 wavelength=self.wavelength) 

516 self.add_data(dxrd, label=label) 

517 

518 

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

532 

533 def onLoadCIF(self, cif=None): 

534 if cif is None: 

535 return 

536 t0 = time.time() 

537 

538 energy = E_from_lambda(self.wavelength) 

539 

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 

545 

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

555 

556 try: 

557 q = self.datasets[self.current_label].q 

558 except: 

559 q = np.linspace(0, 10, 2048) 

560 

561 sigma = 2.5*(q[1] - q[0]) 

562 

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) 

566 

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) 

570 

571 

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 

577 

578 label = self.current_label 

579 dset = self.datasets[label] 

580 

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 

588 

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

598 

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

604 

605 

606 def build(self): 

607 sizer = wx.GridBagSizer(3, 3) 

608 sizer.SetVGap(3) 

609 sizer.SetHGap(3) 

610 

611 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) 

612 splitter.SetMinimumPaneSize(220) 

613 

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

621 

622 ltop = wx.Panel(lpanel) 

623 

624 def Btn(msg, x, act): 

625 b = Button(ltop, msg, size=(x, 30), action=act) 

626 b.SetFont(Font(FONTSIZE)) 

627 return b 

628 

629 sel_none = Btn('Select None', 130, self.onSelNone) 

630 sel_all = Btn('Select All', 130, self.onSelAll) 

631 

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

636 

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) 

641 

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) 

646 

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) 

651 

652 self.font_fixedwidth = wx.Font(FONTSIZE_FW, wx.MODERN, wx.NORMAL, wx.BOLD) 

653 

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) 

658 

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

670 

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

675 

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) 

680 

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

685 

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) 

693 

694 def CopyBtn(name): 

695 return Button(panel, 'Copy to Seleceted', size=(150, -1), 

696 action=partial(self.onCopyAttr, name)) 

697 

698 wids['bkg_copy'] = CopyBtn('bkg') 

699 wids['scale_copy'] = CopyBtn('scale_method') 

700 

701 def slabel(txt): 

702 return wx.StaticText(panel, label=txt) 

703 

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) 

709 

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) 

714 

715 panel.Add((5, 5)) 

716 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True) 

717 panel.Add((5, 5)) 

718 

719 

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) 

726 

727 panel.Add((5, 5)) 

728 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True) 

729 panel.Add((5, 5)) 

730 

731 panel.Add(slabel(' Background Subtraction Parameters: '), dcol=3, style=LEFT, newrow=True) 

732 panel.Add(wids['bkg_copy'], dcol=3, style=RIGHT) 

733 

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

740 

741 panel.Add((5, 5)) 

742 panel.Add(HLine(panel, size=(550, 3)), dcol=6, newrow=True) 

743 panel.Add((5, 5)) 

744 

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) 

750 

751 

752 panel.Add((5, 5)) 

753 

754 panel.pack() 

755 

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) 

761 

762 # rpanel.SetupScrolling() 

763 

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

769 

770 self.Show() 

771 self.Raise() 

772 

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 

779 

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 

788 

789 if with_pyfai: 

790 try: 

791 self.pyfai_integrator = AzimuthalIntegrator(**self.poni) 

792 except: 

793 self.pyfai_integrator = None 

794 

795 

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) 

802 

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

809 

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) 

819 

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) 

826 

827 

828 def onSelNone(self, event=None): 

829 self.filelist.select_none() 

830 

831 def onSelAll(self, event=None): 

832 self.filelist.select_all() 

833 

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

851 

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 

858 

859 self.current_label = label 

860 dset = self.datasets[label] 

861 

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 

869 

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) 

875 

876 meth_desc = keyof(SCALE_METHODS, dset.scale_method, 'raw_max') 

877 

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

883 

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) 

887 

888 self.onPlotOne(label=label) 

889 

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

897 

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) 

905 

906 if dset.auto_scale: 

907 self.scale_data(dset, with_plot=True) 

908 

909 def scale_data(self, dset, with_plot=True): 

910 meth_name = self.wids['scale_method'].GetStringSelection() 

911 

912 meth = dset.scale_method = SCALE_METHODS[meth_name] 

913 

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 

923 

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

937 

938 if scale > 0: 

939 self.wids['scale'].SetValue(scale) 

940 if with_plot: 

941 self.onPlotOne() 

942 

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 

948 

949 self.filelist.Clear() 

950 for name in self.datasets: 

951 self.filelist.Append(name) 

952 

953 

954 def onRenameDataset(self, event=None): 

955 RenameDialog(self, self.current_label, self.rename_dataset).Show() 

956 

957 

958 def remove_dataset(self, dname=None, event=None): 

959 if dname in self.datasets: 

960 self.datasets.pop(dname) 

961 

962 self.filelist.Clear() 

963 for name in self.datasets: 

964 self.filelist.Append(name) 

965 

966 

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 

973 

974 dlg = RemoveDialog(self, sel) 

975 res = dlg.GetResponse() 

976 dlg.Destroy() 

977 

978 if res.ok: 

979 all = self.filelist.GetItems() 

980 for dname in sel: 

981 self.datasets.pop(dname) 

982 all.remove(dname) 

983 

984 self.filelist.Clear() 

985 for name in all: 

986 self.filelist.Append(name) 

987 

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 

993 

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 

999 

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} 

1007 

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 

1014 

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

1019 

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) 

1028 

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) 

1039 

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) 

1053 

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) 

1059 

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) 

1069 

1070class XRD1DApp(LarchWxApp): 

1071 def __init__(self, **kws): 

1072 LarchWxApp.__init__(self) 

1073 

1074 def createApp(self): 

1075 frame = XRD1DFrame() 

1076 frame.Show() 

1077 self.SetTopWindow(frame) 

1078 return True