xrdPlanner 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: xrdPlanner
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A tool to project X-ray diffraction cones on a detector screen at different geometries (tilt, rotation, offset) and X-ray energies
5
5
  Author-email: Lennard Krause <lkrause@chem.au.dk>
6
6
  Project-URL: Homepage, https://github.com/LennardKrause/xrdPlanner
@@ -1,6 +1,6 @@
1
- __version__ = '2.2.0'
1
+ __version__ = '2.3.0'
2
2
  __author__ = 'Lennard Krause'
3
3
  __authors__ = ['Lennard Krause', 'Frederik H. Gjørup', 'Mads R. V. Jørgensen']
4
4
  __email__ = 'lkrause@chem.au.dk'
5
- __date__ = '08.11.2024'
6
- __year__ = '2024'
5
+ __date__ = '10.01.2025'
6
+ __year__ = '2025'
@@ -4,15 +4,13 @@ import json
4
4
  import glob
5
5
  import shutil
6
6
  import numpy as np
7
+ from scipy.optimize import curve_fit
7
8
  import pyqtgraph as pg
8
9
  import Dans_Diffraction as dif
9
10
  from PyQt6 import QtWidgets, QtCore, QtGui
10
11
  from pyFAI import calibrant
11
12
  import xrdPlanner.resources
12
13
 
13
- # make cifs added to menu in init similar to calibrants
14
- # store only the path and calculate the rest on load
15
-
16
14
  class MainWindow(QtWidgets.QMainWindow):
17
15
  def __init__(self, *args, **kwargs):
18
16
  """
@@ -163,6 +161,7 @@ class MainWindow(QtWidgets.QMainWindow):
163
161
 
164
162
  # disable/reset delta_d/d toggles
165
163
  self.action_funct_fwhm_show.setEnabled(False)
164
+ self.action_funct_fwhm_export.setEnabled(False)
166
165
 
167
166
  # add the slider frame
168
167
  # this calls draw_conics(),
@@ -350,6 +349,8 @@ class MainWindow(QtWidgets.QMainWindow):
350
349
  'bs_label':None,
351
350
  'conic':[],
352
351
  'reference':[],
352
+ 'ref_hl_label':None,
353
+ 'ref_hl_curve':None,
353
354
  'labels':[],
354
355
  'polar_grid':[]}
355
356
 
@@ -367,12 +368,27 @@ class MainWindow(QtWidgets.QMainWindow):
367
368
 
368
369
  # add empty plot per reference contour line
369
370
  for i in range(self.plo.conic_ref_num):
370
- ref = pg.PlotCurveItem(useCache=True)
371
+ ref = HoverableCurveItem(self, useCache=True)
371
372
  self.ax.addItem(ref)
372
373
  self.patches['reference'].append(ref)
373
- self.patches['reference'][i].setClickable(True, width=self.plo.conic_ref_linewidth*2)
374
- self.patches['reference'][i].sigClicked.connect(self.tooltip_hkl_show)
375
374
  self.patches['reference'][i].name = None
375
+ self.patches['reference'][i].index = None
376
+
377
+ # add ref highlight curve
378
+ ref_hkl_curve = pg.PlotCurveItem(useCache=True,
379
+ pen=pg.mkPen(color=self.conic_highlight,
380
+ width=self.plo.conic_ref_linewidth*2))
381
+ ref_hkl_curve.setVisible(False)
382
+ self.patches['ref_hl_curve'] = ref_hkl_curve
383
+ self.ax.addItem(ref_hkl_curve)
384
+
385
+ # add hkl annotation label
386
+ ref_hkl_label = pg.TextItem(anchor=(0.5,1.0), color=self.unit_label_color)
387
+ ref_hkl_label.setFont(font)
388
+ ref_hkl_label.setVisible(False)
389
+ ref_hkl_label.setZValue(1) # place label on top of all other items
390
+ self.patches['ref_hl_label'] = ref_hkl_label
391
+ self.ax.addItem(ref_hkl_label)
376
392
 
377
393
  # add empty plot per contour line
378
394
  for i in range(self.plo.conic_tth_num):
@@ -393,17 +409,17 @@ class MainWindow(QtWidgets.QMainWindow):
393
409
  self.ax.addItem(bs_label)
394
410
 
395
411
  # add poni scatter plot
396
- self.patches['poni'] = pg.ScatterPlotItem(symbol = self.plo.poni_marker,
397
- size = self.plo.poni_size,
398
- brush = pg.mkBrush(self.cont_cmap.map(0, mode='qcolor')),
399
- pen = pg.mkPen(None))
412
+ self.patches['poni'] = pg.ScatterPlotItem(symbol=self.plo.poni_marker,
413
+ size=self.plo.poni_size,
414
+ brush=pg.mkBrush(self.cont_cmap.map(0, mode='qcolor')),
415
+ pen=pg.mkPen(None))
400
416
  self.ax.addItem(self.patches['poni'])
401
417
 
402
418
  # add beam center scatter plot
403
- self.patches['beamcenter'] = pg.ScatterPlotItem(symbol = self.plo.beamcenter_marker,
404
- size = self.plo.beamcenter_size,
405
- brush = pg.mkBrush(self.cont_cmap.map(0, mode='qcolor')),
406
- pen = pg.mkPen(None))
419
+ self.patches['beamcenter'] = pg.ScatterPlotItem(symbol=self.plo.beamcenter_marker,
420
+ size=self.plo.beamcenter_size,
421
+ brush=pg.mkBrush(self.cont_cmap.map(0, mode='qcolor')),
422
+ pen=pg.mkPen(None))
407
423
  self.ax.addItem(self.patches['beamcenter'])
408
424
 
409
425
  self.patches['overlay'] = pg.ImageItem()
@@ -651,6 +667,7 @@ class MainWindow(QtWidgets.QMainWindow):
651
667
  # reference contour
652
668
  self.conic_label_fill = QtGui.QColor(self.thm.dark_conic_label_fill)
653
669
  self.conic_ref_color = QtGui.QColor(self.thm.dark_conic_ref_color)
670
+ self.conic_highlight = QtGui.QColor(self.thm.dark_conic_highlight)
654
671
  self.det_module_color = QtGui.QColor(self.thm.dark_det_module_color)
655
672
  self.det_module_fill = QtGui.QColor(self.thm.dark_det_module_fill)
656
673
  # general
@@ -688,6 +705,7 @@ class MainWindow(QtWidgets.QMainWindow):
688
705
  # reference contour
689
706
  self.conic_label_fill = QtGui.QColor(self.thm.light_conic_label_fill)
690
707
  self.conic_ref_color = QtGui.QColor(self.thm.light_conic_ref_color)
708
+ self.conic_highlight = QtGui.QColor(self.thm.light_conic_highlight)
691
709
  self.det_module_color = QtGui.QColor(self.thm.light_det_module_color)
692
710
  self.det_module_fill = QtGui.QColor(self.thm.light_det_module_fill)
693
711
  # general
@@ -1004,7 +1022,11 @@ class MainWindow(QtWidgets.QMainWindow):
1004
1022
  else:
1005
1023
  self.action_funct_fwhm_show.setChecked(False)
1006
1024
  menu_functions.addAction(self.action_funct_fwhm_show)
1007
-
1025
+ #export fwhm toggle
1026
+ self.action_funct_fwhm_export = QtGui.QAction('Export FWHM', self)
1027
+ self.menu_set_action(self.action_funct_fwhm_export, self.export_fwhm_grid)
1028
+ menu_functions.addAction(self.action_funct_fwhm_export)
1029
+
1008
1030
  # PXRD pattern
1009
1031
  self.action_pxrd_pattern = QtGui.QAction('P&XRD pattern', self)
1010
1032
  self.menu_set_action(self.action_pxrd_pattern, self.win_pxrd_plot)
@@ -1408,7 +1430,7 @@ class MainWindow(QtWidgets.QMainWindow):
1408
1430
 
1409
1431
  # calculate the maximum resolution for the given geometry
1410
1432
  if self.plo.conic_tth_auto:
1411
- theta_max = np.rad2deg(self.calc_max_resolution(m=0.90))
1433
+ theta_max = np.rad2deg(self.calc_tth_max(scale=0.90))
1412
1434
  # make new array of 2theta values
1413
1435
  self.cont_geom_num = np.linspace(theta_max/self.plo.conic_tth_num, theta_max, self.plo.conic_tth_num)
1414
1436
 
@@ -1502,6 +1524,9 @@ class MainWindow(QtWidgets.QMainWindow):
1502
1524
  - self.conic_ref_color: Color for the reference contour lines.
1503
1525
  - self.patches['reference']: Dictionary to store the reference contour line patches.
1504
1526
  """
1527
+ # remove highlighted curve and hkl label
1528
+ self.patches['ref_hl_curve'].setVisible(False)
1529
+ self.patches['ref_hl_label'].setVisible(False)
1505
1530
  # plot reference contour lines
1506
1531
  # standard contour lines are to be drawn
1507
1532
  for _n in range(self.plo.conic_ref_num):
@@ -1549,10 +1574,13 @@ class MainWindow(QtWidgets.QMainWindow):
1549
1574
  #
1550
1575
  # self.setStyleSheet('''QToolTip {... ) didn't work
1551
1576
  self.patches['reference'][_n].name = f'({h: 2.0f} {k: 2.0f} {l: 2.0f})<br>{round(itot, 0):,.0f}'
1577
+ self.patches['reference'][_n].index = _n
1552
1578
  else:
1553
1579
  self.patches['reference'][_n].name = f'({h: 2.0f} {k: 2.0f} {l: 2.0f})'
1580
+ self.patches['reference'][_n].index = _n
1554
1581
  else:
1555
1582
  self.patches['reference'][_n].name = None
1583
+ self.patches['reference'][_n].index = None
1556
1584
 
1557
1585
  # current fraction for colormap
1558
1586
  if self.plo.colored_reference:
@@ -1826,13 +1854,13 @@ class MainWindow(QtWidgets.QMainWindow):
1826
1854
  plo.show_polarisation = True # [bool] Show polarisation overlay
1827
1855
  plo.show_solidangle = False # [bool] Show solid angle overlay
1828
1856
  plo.show_unit_hover = True # [bool] Show unit value on hover
1829
- plo.show_grid = False # [bool] Show azimuthal grid
1857
+ plo.show_grid = False # [bool] Show azimuthal grid
1830
1858
  plo.azimuth_num = 13 # [int] Number of azimuthal grid lines
1831
1859
  plo.overlay_resolution = 300 # [int] Overlay resolution
1832
1860
  plo.overlay_toggle_warn = True # [bool] Overlay warn color threshold
1833
1861
  # - pxrd plot -
1834
1862
  plo.pxrd_marker_symbol = 'arrow_up' # [marker] Symbol to mark peaks
1835
- plo.pxrd_marker_offset = 0.02 # [float] offset of marker from x-axis
1863
+ plo.pxrd_marker_offset = 0.05 # [float] offset of marker from x-axis
1836
1864
  # - extra functions -
1837
1865
  plo.show_fwhm = False # [bool] Show delta_d/d function
1838
1866
  plo.sensor_thickness = 1000e-6 # [float] Detector sensor thickness [m]
@@ -1900,6 +1928,7 @@ class MainWindow(QtWidgets.QMainWindow):
1900
1928
  # light mode
1901
1929
  thm.light_conic_label_fill = '#FFFFFF' # [color] Contour label fill color
1902
1930
  thm.light_conic_ref_color = '#DCDCDC' # [color] Reference contour color
1931
+ thm.light_conic_highlight = '#FF0000' # [color] Reference contour highlight color
1903
1932
  thm.light_beamstop_color = '#80FF0000' # [color] Beamstop color
1904
1933
  thm.light_beamstop_edge_color = '#FF0000' # [color] Beamstop edge color
1905
1934
  thm.light_det_module_color = '#404040' # [color] Detector module border color
@@ -1916,6 +1945,7 @@ class MainWindow(QtWidgets.QMainWindow):
1916
1945
  # dark mode
1917
1946
  thm.dark_conic_label_fill = '#000000' # [color] Contour label fill color
1918
1947
  thm.dark_conic_ref_color = '#303030' # [color] Reference contour color
1948
+ thm.dark_conic_highlight = '#FF0000' # [color] Reference contour highlight color
1919
1949
  thm.dark_beamstop_color = '#AAFF0000' # [color] Beamstop color
1920
1950
  thm.dark_beamstop_edge_color = '#FF0000' # [color] Beamstop edge color
1921
1951
  thm.dark_det_module_color = '#EEEEEE' # [color] Detector module border color
@@ -2595,66 +2625,6 @@ class MainWindow(QtWidgets.QMainWindow):
2595
2625
  rect_item.setOpacity(self.plo.det_module_alpha)
2596
2626
  self.ax.addItem(rect_item)
2597
2627
 
2598
- ##################
2599
- # HKL TOOLTIPS #
2600
- ##################
2601
- def tooltip_hkl_show(self, widget, event):
2602
- """
2603
- Displays a tooltip with the hkl value at the position of the given widget and highlights the widget.
2604
-
2605
- Parameters:
2606
- widget (QWidget): The widget for which the tooltip is to be shown. It should have a 'name' attribute and 'opts' dictionary.
2607
- event (QEvent): The event that triggered the tooltip display. It should have a 'screenPos' method.
2608
-
2609
- Behavior:
2610
- - If the widget does not have a name or the contour reference hkl is not set, the event is ignored.
2611
- - Otherwise, a tooltip is shown at the event's screen position with the widget's name.
2612
- - The widget is highlighted by changing its pen width.
2613
- - A timer is started to remove the highlight after a specified timeout.
2614
- - The timer is added to a list to keep the reference alive until it times out.
2615
-
2616
- Note:
2617
- - The tooltip is displayed using QTextEdit to allow HTML formatting.
2618
- - The highlight is removed by the 'tooltip_hkl_highlight' method.
2619
- """
2620
- if not widget.name or not self.cont_ref_hkl:
2621
- event.ignore()
2622
- else:
2623
- # show hkl tooltip at position
2624
- text = QtWidgets.QTextEdit(str(widget.name))
2625
- text.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
2626
- pos = QtCore.QPoint(*map(int, event.screenPos()))# - QtCore.QPoint(10,20)
2627
- QtWidgets.QToolTip.showText(pos, text.toHtml())
2628
- # highlight the contour
2629
- # store current pen as 'name' in widget.opts
2630
- pen = widget.opts['pen']
2631
- width = widget.opts['pen'].width()
2632
-
2633
- timer = QtCore.QTimer()
2634
- timer.setSingleShot(True)
2635
- timer.timeout.connect(lambda: self.tooltip_hkl_highlight(widget, pen, timer))
2636
- widget.setPen(pg.mkPen(self.beamstop_color, width=width))
2637
- #widget.setPen(pg.mkPen(self.cont_cmap.map(0.0, mode='qcolor'), width=width))
2638
- # add timer to self.list to keep pointers alive
2639
- # remove -> tooltip_hkl_highlight
2640
- self.highlight_timers.append(timer)
2641
- timer.start(self.plo.conic_ref_timeout)
2642
-
2643
- def tooltip_hkl_highlight(self, widget, pen, timer):
2644
- """
2645
- Resets the contour color of the given widget and removes the specified timer from the highlight timers list.
2646
-
2647
- Args:
2648
- widget (QGraphicsItem): The widget whose contour color needs to be reset.
2649
- width (int): The width of the pen to be used for resetting the contour color.
2650
- timer (QTimer): The timer to be removed from the highlight timers list.
2651
- """
2652
- # reset contour color
2653
- widget.setPen(pen)
2654
- # remove timer
2655
- if timer in self.highlight_timers:
2656
- self.highlight_timers.remove(timer)
2657
-
2658
2628
  #######
2659
2629
  # CIF #
2660
2630
  #######
@@ -3194,9 +3164,35 @@ class MainWindow(QtWidgets.QMainWindow):
3194
3164
  self.fwhm_plot.setLabel(axis='left', text='FWHM [\u00B0]', color=self.palette().text().color())
3195
3165
  self.fwhm_plot.scale(sx=2, sy=3)
3196
3166
  self.fwhm_plot.addItem(self.fwhm_curve)
3167
+ # Thomas-Cox-Hastings curve
3168
+ self.tch_curve = pg.PlotCurveItem()
3169
+ self.tch_curve.setPen(pg.mkPen(color=self.palette().highlight().color(), width=6))
3170
+ self.fwhm_plot.addItem(self.tch_curve)
3197
3171
  preview_box_layout.addWidget(self.fwhm_plot)
3198
3172
  layout.addWidget(preview_box)
3199
-
3173
+
3174
+ # Thomas-Cox-Hastings
3175
+ tch_box = QtWidgets.QGroupBox('Estimate Thomas-Cox-Hastings parameters')
3176
+ tch_box_layout = QtWidgets.QHBoxLayout()
3177
+ tch_box.setLayout(tch_box_layout)
3178
+ tch_btn_est = QtWidgets.QPushButton('Estimate')
3179
+ tch_btn_est.clicked.connect(self.estimate_tch_parameters)
3180
+ tch_box_layout.addWidget(tch_btn_est)
3181
+ tch_box_layout.addSpacing(20)
3182
+ tch_label_U = QtWidgets.QLabel('U:', alignment=QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignVCenter)
3183
+ tch_box_layout.addWidget(tch_label_U)
3184
+ self.tch_value_U = QtWidgets.QLabel()
3185
+ tch_box_layout.addWidget(self.tch_value_U)
3186
+ tch_label_V = QtWidgets.QLabel('V:', alignment=QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignVCenter)
3187
+ tch_box_layout.addWidget(tch_label_V)
3188
+ self.tch_value_V = QtWidgets.QLabel()
3189
+ tch_box_layout.addWidget(self.tch_value_V)
3190
+ tch_label_W = QtWidgets.QLabel('W:', alignment=QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignVCenter)
3191
+ tch_box_layout.addWidget(tch_label_W)
3192
+ self.tch_value_W = QtWidgets.QLabel()
3193
+ tch_box_layout.addWidget(self.tch_value_W)
3194
+ layout.addWidget(tch_box)
3195
+
3200
3196
  # add description
3201
3197
  description = QtWidgets.QLabel('This feature is currently in <b>test phase</b>, feedback is very welcome!<br>\
3202
3198
  The estimated FWHM (H, in degrees) is shown in the bottom right corner.')
@@ -3291,7 +3287,7 @@ class MainWindow(QtWidgets.QMainWindow):
3291
3287
  dist = self.geo.dist * 1e-3
3292
3288
  tth = np.linspace(0, np.pi/2, 180)
3293
3289
  # or use:
3294
- # _current_tth_max = self.calc_max_resolution()
3290
+ # _current_tth_max = self.calc_tth_max()
3295
3291
  # to get max tth for the current setup
3296
3292
 
3297
3293
  # H2, FWHM
@@ -3350,10 +3346,45 @@ class MainWindow(QtWidgets.QMainWindow):
3350
3346
  self.plo.scattering_diameter = round(udict[4][0].value() * udict[4][1], 6)
3351
3347
  #self.plo.funct_fwhm_thresh = round(udict[5][0].value() * udict[5][1], 6)
3352
3348
  self.action_funct_fwhm_show.setEnabled(True)
3349
+ self.action_funct_fwhm_export.setEnabled(True)
3353
3350
  self.plo.show_fwhm = False
3354
3351
  self.toggle_fwhm()
3355
3352
  win.close()
3356
3353
 
3354
+ def export_fwhm_grid(self):
3355
+ # calculate the FWHM grid
3356
+ # res = None -> use the current detector pixel dimensions
3357
+ _fwhm_grid = self.calc_overlays(omega=0, res=None, pol=self.plo.polarisation_fac)[-1]
3358
+ # find target file
3359
+ default_path = os.path.join(os.path.expanduser('~'), f'{self.det.name.replace(' ', '_')}_FWHM')
3360
+ target, filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Export FWHM grid', default_path, "Compressed numpy array (*.npz)")
3361
+ if not target:
3362
+ return
3363
+ # save compressed array to target
3364
+ np.savez_compressed(target, fwhm=np.flipud(_fwhm_grid))
3365
+
3366
+ def estimate_tch_parameters(self):
3367
+ tth_max_rad = self.calc_tth_max()
3368
+ tth_min = 0.0
3369
+ tth_max = np.rad2deg(tth_max_rad)
3370
+ tth, fwhm = self.fwhm_curve.getData()
3371
+ tch_cond = (tth > tth_min) & (tth < tth_max)
3372
+ tch_tth = tth[tch_cond]
3373
+ instr_fwhm = fwhm[tch_cond]
3374
+
3375
+ def TCH_pv_fit(tth,*UVW):
3376
+ """parser for the TCH pseudo-Voigt function"""
3377
+ fwhm,_ = self.calc_TCH_pV(tth,*UVW,0,0)
3378
+ return fwhm
3379
+
3380
+ UVW = 0, 0, instr_fwhm[0]**2
3381
+ [u,v,w], _ = curve_fit(TCH_pv_fit, tch_tth, instr_fwhm, p0=UVW, maxfev=10000)
3382
+ tch_fwhm,_ = self.calc_TCH_pV(tch_tth, u, v, w, 0, 0)
3383
+ self.tch_curve.setData(tch_tth, tch_fwhm)
3384
+ self.tch_value_U.setText(f'{u:.6f}')
3385
+ self.tch_value_V.setText(f'{v:.6f}')
3386
+ self.tch_value_W.setText(f'{w:.6f}')
3387
+
3357
3388
  ##########
3358
3389
  # PXRD #
3359
3390
  ##########
@@ -3377,6 +3408,7 @@ class MainWindow(QtWidgets.QMainWindow):
3377
3408
  self.pxrd_win.close()
3378
3409
  return
3379
3410
  self.win_pxrd_update()
3411
+ self.pxrd_win.adjustSize()
3380
3412
  self.pxrd_win.show()
3381
3413
  return
3382
3414
 
@@ -3393,10 +3425,19 @@ class MainWindow(QtWidgets.QMainWindow):
3393
3425
 
3394
3426
  self.pxrd_curve = pg.PlotCurveItem()
3395
3427
  self.pxrd_curve.setPen(pg.mkPen(color=self.palette().text().color()))
3396
- self.pxrd_scatt = pg.ScatterPlotItem(size=15)
3428
+ self.pxrd_scatt = ClickableScatterPlotItem(size=20, tip=None)
3397
3429
  self.pxrd_scatt.setPen(pg.mkPen(color=self.palette().highlight().color()))
3430
+ self.pxrd_scatt.setBrush(pg.mkBrush(color=self.palette().highlight().color()))
3398
3431
  self.pxrd_scatt.setSymbol(self.plo.pxrd_marker_symbol)
3399
- self.pxrd_scatt.sigClicked.connect(self.win_pxrd_show_hkl)
3432
+ self.pxrd_scatt.sigClicked.connect(self.win_pxrd_hkl_clicked)
3433
+ self.pxrd_scatt_highlighted = []
3434
+
3435
+ font = QtGui.QFont()
3436
+ font.setPixelSize(self.plo.conic_hkl_label_size)
3437
+ font.setBold(True)
3438
+
3439
+ self.pxrd_label = pg.TextItem(anchor=(0.5,0.9))
3440
+ self.pxrd_label.setFont(font)
3400
3441
  self.pxrd_regio = pg.LinearRegionItem(pen=pg.mkPen(None))
3401
3442
  self.pxrd_regio.setMovable(False)
3402
3443
 
@@ -3405,9 +3446,19 @@ class MainWindow(QtWidgets.QMainWindow):
3405
3446
  self.pxrd_plot.setMenuEnabled(False)
3406
3447
  self.pxrd_plot.setLabel(axis='left', text='Intensity [arb. units]', color=self.palette().text().color())
3407
3448
  self.pxrd_plot.showGrid(x=True, y=True, alpha=0.5)
3449
+ self.pxrd_plot.getPlotItem().getViewBox().setDefaultPadding(0.0)
3408
3450
  self.pxrd_plot.addItem(self.pxrd_curve)
3409
- self.pxrd_plot.addItem(self.pxrd_scatt)
3410
- self.pxrd_plot.addItem(self.pxrd_regio)
3451
+ # if points are not visible, e.g. outside of currentrange, the plot size will
3452
+ # not be adjusted properly and stays larger than expected.
3453
+ # ignoreBound allows the plot to autorange disregarding the 'invisble' points
3454
+ # however we need to increase the min y padding to make the scatterplot visible!
3455
+ self.pxrd_plot.addItem(self.pxrd_scatt, ignoreBounds=True)
3456
+ self.pxrd_plot.addItem(self.pxrd_label)
3457
+ # same as above, allow the plot to disregard the beamstop if out of bounds
3458
+ self.pxrd_plot.addItem(self.pxrd_regio, ignoreBounds=True)
3459
+ # this invisible line will do the padding for us -> thanks Frederik!
3460
+ self.pxrd_line = pg.InfiniteLine(pos=0, angle=0, pen=pg.mkPen(None))
3461
+ self.pxrd_plot.addItem(self.pxrd_line)
3411
3462
 
3412
3463
  powder_box_layout.addWidget(self.pxrd_plot)
3413
3464
  layout.addWidget(powder_box)
@@ -3450,6 +3501,9 @@ class MainWindow(QtWidgets.QMainWindow):
3450
3501
  if self.pxrd_win is None:
3451
3502
  return
3452
3503
 
3504
+ # remove highlighting of hkl on update
3505
+ self.win_pxrd_lowlight()
3506
+
3453
3507
  # change color if theme changed
3454
3508
  self.pxrd_curve.setPen(self.palette().text().color())
3455
3509
  self.pxrd_plot.setBackground(self.palette().base().color())
@@ -3506,7 +3560,7 @@ class MainWindow(QtWidgets.QMainWindow):
3506
3560
  max_res_r = np.nanmax(self._tth)
3507
3561
  min_res_r = max(np.nanmin(self._tth), fwhm.mean()/10)
3508
3562
  else:
3509
- max_res_r = self.calc_max_resolution()
3563
+ max_res_r = self.calc_tth_max()
3510
3564
  min_res_r = fwhm.mean()/10
3511
3565
 
3512
3566
  # add beamstop shadow
@@ -3529,37 +3583,62 @@ class MainWindow(QtWidgets.QMainWindow):
3529
3583
  self.pxrd_curve.setData(x=xval, y=gauss)
3530
3584
 
3531
3585
  peak_xval = self.calc_unit(peak_ttr)
3586
+ offset = (gauss.max() + gauss.min()) * self.plo.pxrd_marker_offset
3587
+ self.pxrd_scatt.setData(x=peak_xval,
3588
+ y=np.zeros(len(peak_xval))-offset,
3589
+ data=hkl)
3532
3590
  # remove peaks that are ouside of the detector screen
3533
- peak_idx = np.nonzero(peak_xval <= xval.max())[0]
3534
- offset = (gauss.max() - gauss.min())*self.plo.pxrd_marker_offset
3535
- self.pxrd_scatt.setData(x=peak_xval[peak_idx], y=np.zeros(len(peak_xval[peak_idx]))-offset, data=[hkl[i] for i in peak_idx])
3591
+ cond = (peak_xval <= xval.max()) & (peak_xval >= xval.min())
3592
+ self.pxrd_scatt.setPointsVisible(cond)
3593
+ self.pxrd_line.setPos(-offset*2)
3536
3594
 
3595
+ # add axis label
3537
3596
  self.pxrd_plot.setLabel(axis='bottom', text=self.unit_names[self.geo.unit], color=self.palette().text().color())
3597
+
3538
3598
  # flip axis for d-spacing
3539
3599
  if self.geo.unit == 1:
3540
3600
  self.pxrd_plot.getPlotItem().getViewBox().invertX(True)
3541
3601
  else:
3542
3602
  self.pxrd_plot.getPlotItem().getViewBox().invertX(False)
3543
3603
 
3544
- def win_pxrd_show_hkl(self, widget, points, event):
3545
- """
3546
- Displays the HKL (Miller indices) information in a tooltip when a point is clicked.
3547
-
3548
- Parameters:
3549
- widget (QtWidgets.QWidget): The widget that triggered the event.
3550
- points (list): A list of points where the event occurred.
3551
- event (QtGui.QMouseEvent): The mouse event that triggered the display.
3552
-
3553
- Returns:
3554
- None
3555
- """
3604
+ def win_pxrd_hkl_clicked(self, widget, points, event):
3556
3605
  if not widget.name:
3557
3606
  return
3558
3607
  elif len(points) > 0:
3559
- text = QtWidgets.QTextEdit(str(points[0].data()))
3560
- text.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
3561
- pos = QtCore.QPoint(*map(int, event.screenPos()))
3562
- QtWidgets.QToolTip.showText(pos, text.toHtml())
3608
+ point = points[0]
3609
+ if event.buttons() == QtCore.Qt.MouseButton.RightButton:
3610
+ self.patches['reference'][point.index()].lowlight()
3611
+ return
3612
+ self.win_pxrd_lowlight()
3613
+ point.setPen(pg.mkPen(color=self.conic_highlight))
3614
+ point.setBrush(pg.mkBrush(color=self.conic_highlight))
3615
+ self.pxrd_scatt_highlighted.append(point)
3616
+ self.win_pxrd_set_hkl_label(point)
3617
+ # highlight in main window
3618
+ self.patches['reference'][point.index()].highlight()
3619
+
3620
+ def win_pxrd_set_hkl_label(self, point):
3621
+ self.pxrd_label.setText(str(point.data()))
3622
+ self.pxrd_label.setPos(point.pos())
3623
+ self.pxrd_label.setVisible(True)
3624
+
3625
+ def win_pxrd_highlight(self, index):
3626
+ # highlight from main window
3627
+ self.win_pxrd_lowlight()
3628
+ point = self.pxrd_scatt.points()[index]
3629
+ point.setPen(pg.mkPen(self.conic_highlight))
3630
+ point.setBrush(pg.mkBrush(self.conic_highlight))
3631
+ self.pxrd_scatt_highlighted.append(point)
3632
+ self.win_pxrd_set_hkl_label(point)
3633
+
3634
+ def win_pxrd_lowlight(self):
3635
+ _pen = pg.mkPen(color=self.palette().highlight().color())
3636
+ _brush = pg.mkBrush(color=self.palette().highlight().color())
3637
+ for p in self.pxrd_scatt_highlighted:
3638
+ #p.resetPen()
3639
+ p.setPen(_pen)
3640
+ p.setBrush(_brush)
3641
+ self.pxrd_label.setVisible(False)
3563
3642
 
3564
3643
  #############
3565
3644
  # EXPORT #
@@ -4808,7 +4887,7 @@ class MainWindow(QtWidgets.QMainWindow):
4808
4887
  1.05395590e-03, 1.07113920e-03, 1.08845123e-03, 1.10583974e-03,
4809
4888
  1.12333211e-03, 1.14094222e-03]}
4810
4889
 
4811
- def calc_max_resolution(self, m=1.0):
4890
+ def calc_tth_max(self, scale=1.0):
4812
4891
  """
4813
4892
  Calculate the maximum 2theta angle for the given geometry
4814
4893
  - used to generate 2theta values to draw conics
@@ -4820,8 +4899,8 @@ class MainWindow(QtWidgets.QMainWindow):
4820
4899
  float, maximum 2-theta in radians
4821
4900
  """
4822
4901
  # make screen grid
4823
- size_h = np.array([-self.xdim, self.xdim])*m
4824
- size_v = np.array([-self.ydim, self.ydim])*m
4902
+ size_h = np.array([-self.xdim, self.xdim])*scale
4903
+ size_v = np.array([-self.ydim, self.ydim])*scale
4825
4904
  _gx, _gy = np.meshgrid(size_h, size_v, sparse=True)
4826
4905
  # build vector -> 3 x n x m
4827
4906
  _vec = np.full((3, 2, 2), self.geo.dist)
@@ -4839,7 +4918,7 @@ class MainWindow(QtWidgets.QMainWindow):
4839
4918
  D_a = _res[2]
4840
4919
  # 2theta - Angle between pixel, sample, and POBI
4841
4920
  tth = np.arctan2(R_a, D_a)
4842
- # 2theta max
4921
+ # 2theta max
4843
4922
  return tth.max()
4844
4923
 
4845
4924
  def rot_100(self, a, cc=1):
@@ -5167,8 +5246,13 @@ class MainWindow(QtWidgets.QMainWindow):
5167
5246
  The full width at half maximum (FWHM) for the detector.
5168
5247
  """
5169
5248
  # scale overlay to detector dimensions
5170
- _res_scale = self.ydim/self.xdim
5171
- _res_v = int(round(_res_scale * res, 0))
5249
+ if res is not None and res > 0:
5250
+ _res_scale = self.ydim/self.xdim
5251
+ _res_v = int(round(_res_scale * res, 0))
5252
+ else:
5253
+ res = (self.det.hmp * self.det.hmn + self.det.hgp * (self.det.hmn-1) + self.det.cbh)
5254
+ _res_v = (self.det.vmp * self.det.vmn + self.det.vgp * (self.det.vmn-1) + self.det.cbh)
5255
+
5172
5256
  # neutral overlay grid
5173
5257
  grd = np.ones((_res_v, res))
5174
5258
 
@@ -5373,6 +5457,26 @@ class MainWindow(QtWidgets.QMainWindow):
5373
5457
  """
5374
5458
  return 1/(np.sqrt(2*np.pi)*s)*np.exp(-np.square((x - m)/s)/2)
5375
5459
 
5460
+ def calc_TCH_pV(self, tth, U, V, W, X, Y):
5461
+ """
5462
+ Thompson-Cox-Hastings pseudo-Voigt
5463
+ Gaussian FWHM: U*tan(theta)^2 + V*tan(theta) + W
5464
+ Lorentzian FWHM: X*tan(theta) + Y/cos(theta)
5465
+ return pseudo Voigt FWHM
5466
+ """
5467
+ theta = tth*np.pi/360
5468
+ tt = np.tan(theta)
5469
+ ct = np.cos(theta)
5470
+ G = np.sqrt(U*tt**2 + V*tt + W)
5471
+ L = X*tt + Y/ct
5472
+
5473
+ H = (G**5 + 2.69269*G**4*L + 2.42843*G**3*L**2 \
5474
+ + 4.47163*G**2*L**3 + 0.07842*G*L**4 + L**5)**(1/5)
5475
+ # notice that the eta is defined differently in the FullProf manual
5476
+ # such that eta = 1-eta_FullProf
5477
+ eta = 1-(1.36603*L/H - 0.47719*(L/H)**2 + 0.11116*(L/H)**3)
5478
+ return H, eta
5479
+
5376
5480
  #############
5377
5481
  # SETTINGS #
5378
5482
  #############
@@ -6384,4 +6488,71 @@ class Ref(object):
6384
6488
  Returns:
6385
6489
  bool: True if all required attributes are present, False otherwise.
6386
6490
  """
6387
- return np.all([self.has_cif, self.has_dsp, self.has_hkl])
6491
+ return np.all([self.has_cif, self.has_dsp, self.has_hkl])
6492
+
6493
+ class HoverableCurveItem(pg.PlotCurveItem):
6494
+ def __init__(self, parent=None, hoverable=True, *args, **kwargs):
6495
+ super().__init__(*args, **kwargs)
6496
+ self.parent = parent
6497
+ self.basePen = self.opts['pen']
6498
+ self.hoverable = hoverable
6499
+ self.setAcceptHoverEvents(True)
6500
+
6501
+ def setData(self, *args, **kargs):
6502
+ # added the pen re-assignment to the function.
6503
+ if 'pen' in kargs:
6504
+ self.basePen = kargs['pen']
6505
+ return super().setData(*args, **kargs)
6506
+
6507
+ def highlight(self, pos=None):
6508
+ self.parent.patches['ref_hl_curve'].setData(self.xData, self.yData)
6509
+ self.parent.patches['ref_hl_curve'].setVisible(True)
6510
+ self.parent.patches['ref_hl_label'].setText(str(self.name))
6511
+ self.parent.patches['ref_hl_label'].setVisible(True)
6512
+ if pos is None:
6513
+ self.parent.patches['ref_hl_label'].setPos(self.xData[0], self.yData[0])
6514
+ else:
6515
+ self.parent.patches['ref_hl_label'].setPos(pos)
6516
+
6517
+ if self.parent.pxrd_win is not None and self.parent.pxrd_win.isVisible():
6518
+ self.parent.win_pxrd_highlight(self.index)
6519
+
6520
+ def lowlight(self):
6521
+ self.setPen(self.basePen)
6522
+ self.parent.patches['ref_hl_label'].setVisible(False)
6523
+ self.parent.patches['ref_hl_curve'].setVisible(False)
6524
+ self.parent.win_pxrd_lowlight()
6525
+
6526
+ def hoverEvent(self, ev):
6527
+ if self.hoverable and not ev.isExit() and ev.buttons() == QtCore.Qt.MouseButton.LeftButton:
6528
+ if self.mouseShape().contains(ev.pos()):
6529
+ self.highlight(ev.pos())
6530
+
6531
+ def mouseClickEvent(self, ev):
6532
+ if ev.buttons() == QtCore.Qt.MouseButton.LeftButton:
6533
+ if self.mouseShape().contains(ev.pos()):
6534
+ self.highlight(ev.pos())
6535
+ else:
6536
+ self.lowlight()
6537
+ return super().mouseClickEvent(ev)
6538
+
6539
+ class ClickableScatterPlotItem(pg.ScatterPlotItem):
6540
+ """
6541
+ Reimplementation of the ScatterPlotItem class with added click event handling.
6542
+ The original class is limited to left mouse button click events.
6543
+ """
6544
+ def __init__(self, *args, **kwargs):
6545
+ super().__init__(*args, **kwargs)
6546
+
6547
+ def mouseClickEvent(self, ev):
6548
+ if not ev.button() == QtCore.Qt.MouseButton.NoButton:
6549
+ pts = self.pointsAt(ev.pos())
6550
+ if len(pts) > 0:
6551
+ self.ptsClicked = pts
6552
+ ev.accept()
6553
+ self.sigClicked.emit(self, self.ptsClicked, ev)
6554
+ else:
6555
+ #print "no spots"
6556
+ ev.ignore()
6557
+ else:
6558
+ ev.ignore()
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: xrdPlanner
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A tool to project X-ray diffraction cones on a detector screen at different geometries (tilt, rotation, offset) and X-ray energies
5
5
  Author-email: Lennard Krause <lkrause@chem.au.dk>
6
6
  Project-URL: Homepage, https://github.com/LennardKrause/xrdPlanner
File without changes
File without changes
File without changes
File without changes