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

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

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, peakfinder_methods, 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, 

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 return extract_background(dset.q, dset.I, smooth_width=qwid, 

139 iterations=nsmooth, cheb_order=cheb_order) 

140 

141class WavelengthDialog(wx.Dialog): 

142 """dialog for wavelength/energy""" 

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

144 

145 self.parent = parent 

146 self.callback = callback 

147 

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) 

152 

153 self.wids = wids = {} 

154 

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) 

158 

159 en_ev = PLANCK_HC/wavelength 

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

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

162 

163 

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

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

166 

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) 

173 

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

175 

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

183 

184 w0, h0 = self.GetSize() 

185 w1, h1 = self.GetBestSize() 

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

187 

188 def onDone(self, event=None): 

189 if callable(self.callback): 

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

191 self.Destroy() 

192 

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

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

195 

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

197 

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) 

201 

202class RenameDialog(wx.Dialog): 

203 """dialog for renaming a pattern""" 

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

205 

206 self.parent = parent 

207 self.callback = callback 

208 

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) 

213 

214 self.wids = wids = {} 

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

216 

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) 

220 

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

228 

229 w0, h0 = self.GetSize() 

230 w1, h1 = self.GetBestSize() 

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

232 

233 def onDone(self, event=None): 

234 if callable(self.callback): 

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

236 self.Destroy() 

237 

238class XRD1DFrame(wx.Frame): 

239 """browse 1D XRD patterns""" 

240 

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

242 _larch=None, **kws): 

243 

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 

250 

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) 

257 

258 self.larch = self.larch_buffer.larchshell 

259 

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) 

271 

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) 

279 

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) 

291 

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

293 "Remove Selected Patterns", 

294 self.remove_selected_datasets) 

295 

296 fmenu.AppendSeparator() 

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

298 

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

300 "Browse Structures from Am Min Database", 

301 self.onCIFBrowse) 

302 

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

304 "Read PONI Calibration (pyFAI) FIle", 

305 self.onReadPONI) 

306 

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

308 "Set Energy and Wavelength", 

309 self.onSetWavelength) 

310 

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) 

316 

317 

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 

326 

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 

334 

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

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

337 

338 self.Destroy() 

339 

340 def onSetWavelength(self, event=None): 

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

342 

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

348 

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) 

356 

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

358 os.chdir(top) 

359 

360 try: 

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

362 except: 

363 self.pyfai_integrator = None 

364 

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

366 

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

368 

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) 

379 

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 

395 

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

405 

406 imd = self.get_imdisplay() 

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

408 

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) 

413 

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) 

418 

419 

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

433 

434 def onLoadCIF(self, cif=None): 

435 if cif is None: 

436 return 

437 t0 = time.time() 

438 

439 energy = E_from_lambda(self.wavelength) 

440 

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 

446 

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

456 

457 try: 

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

459 except: 

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

461 

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

463 

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) 

467 

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) 

471 

472 

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 

478 

479 label = self.current_label 

480 dset = self.datasets[label] 

481 

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 

489 

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

499 

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

505 

506 

507 def build(self): 

508 sizer = wx.GridBagSizer(3, 3) 

509 sizer.SetVGap(3) 

510 sizer.SetHGap(3) 

511 

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

513 splitter.SetMinimumPaneSize(220) 

514 

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

522 

523 ltop = wx.Panel(lpanel) 

524 

525 def Btn(msg, x, act): 

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

527 b.SetFont(Font(FONTSIZE)) 

528 return b 

529 

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

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

532 

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

537 

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) 

542 

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) 

547 

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) 

552 

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

554 

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) 

559 

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

571 

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

576 

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) 

581 

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

586 

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) 

594 

595 def CopyBtn(name): 

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

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

598 

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

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

601 

602 def slabel(txt): 

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

604 

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) 

610 

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) 

615 

616 panel.Add((5, 5)) 

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

618 panel.Add((5, 5)) 

619 

620 

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) 

627 

628 panel.Add((5, 5)) 

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

630 panel.Add((5, 5)) 

631 

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

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

634 

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

641 

642 panel.Add((5, 5)) 

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

644 panel.Add((5, 5)) 

645 

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) 

651 

652 

653 panel.Add((5, 5)) 

654 

655 panel.pack() 

656 

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) 

662 

663 # rpanel.SetupScrolling() 

664 

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

670 

671 self.Show() 

672 self.Raise() 

673 

674 def set_ponifile(self, ponifile): 

675 "set poni from datafile" 

676 try: 

677 self.set_poni(read_poni(ponifile)) 

678 except: 

679 pass 

680 

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 

689 

690 try: 

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

692 except: 

693 self.pyfai_integrator = None 

694 

695 

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) 

702 

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

709 

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) 

719 

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) 

726 

727 

728 def onSelNone(self, event=None): 

729 self.filelist.select_none() 

730 

731 def onSelAll(self, event=None): 

732 self.filelist.select_all() 

733 

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

751 

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 

758 

759 self.current_label = label 

760 dset = self.datasets[label] 

761 

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 

769 

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) 

775 

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

777 

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

783 

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) 

787 

788 self.onPlotOne(label=label) 

789 

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

797 

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) 

805 

806 if dset.auto_scale: 

807 self.scale_data(dset, with_plot=True) 

808 

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

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

811 

812 meth = dset.scale_method = SCALE_METHODS[meth_name] 

813 

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 

823 

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

837 

838 if scale > 0: 

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

840 if with_plot: 

841 self.onPlotOne() 

842 

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 

848 

849 self.filelist.Clear() 

850 for name in self.datasets: 

851 self.filelist.Append(name) 

852 

853 

854 def onRenameDataset(self, event=None): 

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

856 

857 

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

859 if dname in self.datasets: 

860 self.datasets.pop(dname) 

861 

862 self.filelist.Clear() 

863 for name in self.datasets: 

864 self.filelist.Append(name) 

865 

866 

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 

873 

874 dlg = RemoveDialog(self, sel) 

875 res = dlg.GetResponse() 

876 dlg.Destroy() 

877 

878 if res.ok: 

879 all = self.filelist.GetItems() 

880 for dname in sel: 

881 self.datasets.pop(dname) 

882 all.remove(dname) 

883 

884 self.filelist.Clear() 

885 for name in all: 

886 self.filelist.Append(name) 

887 

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 

893 

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 

899 

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} 

907 

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 

914 

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

919 

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) 

928 

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) 

939 

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) 

953 

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) 

959 

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) 

969 

970class XRD1DApp(LarchWxApp): 

971 def __init__(self, **kws): 

972 LarchWxApp.__init__(self) 

973 

974 def createApp(self): 

975 frame = XRD1DFrame() 

976 frame.Show() 

977 self.SetTopWindow(frame) 

978 return True