advisor-scattering 0.9.1__tar.gz → 0.9.5__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.
Files changed (79) hide show
  1. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/PKG-INFO +4 -2
  2. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/README.md +3 -1
  3. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/orientation_calculator.py +3 -4
  4. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/domain/brillouin_calculator.py +31 -1
  5. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/ui/components/angles_to_hkl_components.py +10 -18
  6. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/ui/components/hk_angles_components.py +16 -29
  7. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/ui/components/hkl_scan_components.py +14 -25
  8. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/ui/components/hkl_to_angles_components.py +18 -29
  9. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/ui/scattering_geometry_tab.py +9 -1
  10. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/dialogs/diffraction_test_dialog.py +29 -6
  11. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/init_window.py +11 -20
  12. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/visualizers/HKLScan2DVisualizer.py +37 -2
  13. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor_scattering.egg-info/PKG-INFO +4 -2
  14. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/pyproject.toml +1 -1
  15. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/MANIFEST.in +0 -0
  16. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/__init__.py +0 -0
  17. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/__main__.py +0 -0
  18. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/app.py +0 -0
  19. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/controllers/__init__.py +0 -0
  20. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/controllers/app_controller.py +0 -0
  21. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/controllers/feature_controller.py +0 -0
  22. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/__init__.py +0 -0
  23. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/core/__init__.py +0 -0
  24. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/core/lab.py +0 -0
  25. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/core/lattice.py +0 -0
  26. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/core/sample.py +0 -0
  27. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/geometry.py +0 -0
  28. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/orientation.py +0 -0
  29. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/domain/unit_converter.py +0 -0
  30. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/__init__.py +0 -0
  31. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/controllers/__init__.py +0 -0
  32. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/controllers/scattering_geometry_controller.py +0 -0
  33. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/domain/__init__.py +0 -0
  34. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/domain/core.py +0 -0
  35. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/ui/__init__.py +0 -0
  36. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/scattering_geometry/ui/components/__init__.py +0 -0
  37. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/controllers/__init__.py +0 -0
  38. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/controllers/structure_factor_controller.py +0 -0
  39. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/domain/__init__.py +0 -0
  40. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/domain/structure_factor_calculator.py +0 -0
  41. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/ui/__init__.py +0 -0
  42. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/ui/components/__init__.py +0 -0
  43. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/ui/components/customized_plane_components.py +0 -0
  44. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/ui/components/hkl_plane_components.py +0 -0
  45. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/features/structure_factor/ui/structure_factor_tab.py +0 -0
  46. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/__init__.py +0 -0
  47. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/config/app_config.json +0 -0
  48. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/config/tips.json +0 -0
  49. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/data/nacl.cif +0 -0
  50. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons/bz_caculator.jpg +0 -0
  51. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons/bz_calculator.png +0 -0
  52. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons/minus.svg +0 -0
  53. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons/placeholder.png +0 -0
  54. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons/plus.svg +0 -0
  55. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons/reset.png +0 -0
  56. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons/sf_calculator.jpg +0 -0
  57. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons/sf_calculator.png +0 -0
  58. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/icons.qrc +0 -0
  59. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/qss/styles.qss +0 -0
  60. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/resources/resources_rc.py +0 -0
  61. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/__init__.py +0 -0
  62. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/dialogs/__init__.py +0 -0
  63. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/main_window.py +0 -0
  64. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/tab_interface.py +0 -0
  65. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/tips.py +0 -0
  66. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/utils/__init__.py +0 -0
  67. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/utils/readcif.py +0 -0
  68. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/visualizers/__init__.py +0 -0
  69. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/visualizers/coordinate_visualizer.py +0 -0
  70. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/visualizers/scattering_visualizer.py +0 -0
  71. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/visualizers/structure_factor_visualizer.py +0 -0
  72. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/visualizers/structure_factor_visualizer_2d.py +0 -0
  73. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor/ui/visualizers/unitcell_visualizer.py +0 -0
  74. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor_scattering.egg-info/SOURCES.txt +0 -0
  75. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor_scattering.egg-info/dependency_links.txt +0 -0
  76. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor_scattering.egg-info/entry_points.txt +0 -0
  77. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor_scattering.egg-info/requires.txt +0 -0
  78. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/advisor_scattering.egg-info/top_level.txt +0 -0
  79. {advisor_scattering-0.9.1 → advisor_scattering-0.9.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: advisor-scattering
3
- Version: 0.9.1
3
+ Version: 0.9.5
4
4
  Summary: Advisor-Scattering: Advanced Visual X-ray Scattering Toolkit for Reciprocal-space visualization and calculation
5
5
  Author: Xunyang Hong
6
6
  License: MIT
@@ -37,6 +37,7 @@ or use the link below to view the demo video.
37
37
  - Visualize scattering geometry and unit cells
38
38
  - Compute and visualize structure factors in reciprocal space.
39
39
  - CIF file drop-in support
40
+ - Import crystal orientation from diffraction test data
40
41
 
41
42
 
42
43
  ## Install
@@ -75,7 +76,8 @@ python -m advisor
75
76
  ### 1. Initialization window
76
77
  - Enter lattice constants (a, b, c) and angles (alpha, beta, gamma); beam energy auto-updates wavelength/|k|.
77
78
  - Optional: drop a CIF to autofill lattice parameters and preview the unit cell.
78
- - Adjust Euler angles (roll, pitch, yaw) to orient the sample relative to the scattering plane;
79
+ - Adjust Euler angles (roll, pitch, yaw) to orient the lattice/sample relative to the goniometer.
80
+ - **New:** Use **Import from Diffraction Test** to automatically determine Euler angles from known diffraction measurements.
79
81
  - Click **Initialize** to load the main interface and pass parameters to all tabs.
80
82
 
81
83
  ### 2. Scattering Geometry tab
@@ -15,6 +15,7 @@ or use the link below to view the demo video.
15
15
  - Visualize scattering geometry and unit cells
16
16
  - Compute and visualize structure factors in reciprocal space.
17
17
  - CIF file drop-in support
18
+ - Import crystal orientation from diffraction test data
18
19
 
19
20
 
20
21
  ## Install
@@ -53,7 +54,8 @@ python -m advisor
53
54
  ### 1. Initialization window
54
55
  - Enter lattice constants (a, b, c) and angles (alpha, beta, gamma); beam energy auto-updates wavelength/|k|.
55
56
  - Optional: drop a CIF to autofill lattice parameters and preview the unit cell.
56
- - Adjust Euler angles (roll, pitch, yaw) to orient the sample relative to the scattering plane;
57
+ - Adjust Euler angles (roll, pitch, yaw) to orient the lattice/sample relative to the goniometer.
58
+ - **New:** Use **Import from Diffraction Test** to automatically determine Euler angles from known diffraction measurements.
57
59
  - Click **Initialize** to load the main interface and pass parameters to all tabs.
58
60
 
59
61
  ### 2. Scattering Geometry tab
@@ -14,11 +14,10 @@ from advisor.domain.core import Lab
14
14
 
15
15
 
16
16
  class OrientationCalculator:
17
- """Lightweight calculator for orientation fitting.
17
+ """Lightweight version of Brillouincalculator for orientation fitting (Set UB Matrix calculation).
18
18
 
19
- This class provides only the methods needed for fitting crystal orientation
20
- from diffraction test data. It uses the Lab class directly and avoids
21
- dependencies on feature-specific modules.
19
+ This class provides only the methods needed for fitting crystal orientation (UB matrix)
20
+ from diffraction data.
22
21
  """
23
22
 
24
23
  # Physical constants
@@ -446,7 +446,8 @@ class BrillouinCalculator:
446
446
  "error": None,
447
447
  "feasible": all_feasible,
448
448
  }
449
-
449
+
450
+
450
451
  def is_initialized(self):
451
452
  """Check if the calculator is initialized.
452
453
 
@@ -471,6 +472,35 @@ class BrillouinCalculator:
471
472
  "gamma": gamma,
472
473
  }
473
474
 
475
+
476
+ def get_max_hkl_values(self, tth):
477
+ """Get the maximum HKL values, valid only for orthorhombic crystals.
478
+
479
+ Returns:
480
+ tuple: Maximum HKL values
481
+ """
482
+ if not self.is_initialized():
483
+ raise ValueError("Calculator not initialized")
484
+ a, b, c, alpha, beta, gamma = self.lab.get_lattice_parameters()
485
+ k_magnitude = self.get_k_magnitude(tth)
486
+ h_max = k_magnitude / (2 * np.pi / a)
487
+ k_max = k_magnitude / (2 * np.pi / b)
488
+ l_max = k_magnitude / (2 * np.pi / c)
489
+ if alpha != 90.0 or beta != 90.0 or gamma != 90.0:
490
+ success = False
491
+ message = "The crystal is not orthorhombic,\nthe accesible area is just approximate."
492
+ else:
493
+ success = True
494
+ message = None
495
+ return {
496
+ "h_max": h_max,
497
+ "k_max": k_max,
498
+ "l_max": l_max,
499
+ "success": success,
500
+ "message": message,
501
+ }
502
+
503
+
474
504
  def get_real_space_vectors(self):
475
505
  """Get the real space vectors.
476
506
 
@@ -1,17 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  # pylint: disable=no-name-in-module, import-error
4
- from PyQt5.QtWidgets import (
5
- QWidget,
6
- QVBoxLayout,
7
- QFormLayout,
8
- QGroupBox,
9
- QLabel,
10
- QPushButton,
11
- QDoubleSpinBox,
12
- QLineEdit,
13
- )
14
4
  from PyQt5.QtCore import pyqtSignal
5
+ from PyQt5.QtWidgets import (QDoubleSpinBox, QFormLayout, QGroupBox, QLabel,
6
+ QLineEdit, QPushButton, QVBoxLayout, QWidget)
15
7
 
16
8
 
17
9
  class AnglesToHKLControls(QWidget):
@@ -48,14 +40,6 @@ class AnglesToHKLControls(QWidget):
48
40
  self.theta_input.valueChanged.connect(self.anglesChanged.emit)
49
41
  form_layout.addRow("θ:", self.theta_input)
50
42
 
51
- # phi input
52
- self.phi_input = QDoubleSpinBox()
53
- self.phi_input.setRange(-180.0, 180.0)
54
- self.phi_input.setValue(0.0)
55
- self.phi_input.setSuffix(" °")
56
- self.phi_input.valueChanged.connect(self.anglesChanged.emit)
57
- form_layout.addRow("φ:", self.phi_input)
58
-
59
43
  # chi input
60
44
  self.chi_input = QDoubleSpinBox()
61
45
  self.chi_input.setRange(-180.0, 180.0)
@@ -64,6 +48,14 @@ class AnglesToHKLControls(QWidget):
64
48
  self.chi_input.valueChanged.connect(self.anglesChanged.emit)
65
49
  form_layout.addRow("χ:", self.chi_input)
66
50
 
51
+ # phi input
52
+ self.phi_input = QDoubleSpinBox()
53
+ self.phi_input.setRange(-180.0, 180.0)
54
+ self.phi_input.setValue(0.0)
55
+ self.phi_input.setSuffix(" °")
56
+ self.phi_input.valueChanged.connect(self.anglesChanged.emit)
57
+ form_layout.addRow("φ:", self.phi_input)
58
+
67
59
  main_layout.addWidget(form_group)
68
60
 
69
61
  # Calculate button
@@ -1,26 +1,13 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  # pylint: disable=no-name-in-module, import-error
4
- from PyQt5.QtWidgets import (
5
- QWidget,
6
- QVBoxLayout,
7
- QHBoxLayout,
8
- QFormLayout,
9
- QGroupBox,
10
- QLabel,
11
- QPushButton,
12
- QDoubleSpinBox,
13
- QTableWidget,
14
- QTableWidgetItem,
15
- QHeaderView,
16
- QButtonGroup,
17
- QMessageBox,
18
- QDialog,
19
- QScrollArea,
20
- QFrame,
21
- )
22
4
  from PyQt5.QtCore import Qt, pyqtSignal
23
- from PyQt5.QtGui import QColor, QBrush, QFont
5
+ from PyQt5.QtGui import QBrush, QColor, QFont
6
+ from PyQt5.QtWidgets import (QButtonGroup, QDialog, QDoubleSpinBox,
7
+ QFormLayout, QFrame, QGroupBox, QHBoxLayout,
8
+ QHeaderView, QLabel, QMessageBox, QPushButton,
9
+ QScrollArea, QTableWidget, QTableWidgetItem,
10
+ QVBoxLayout, QWidget)
24
11
 
25
12
 
26
13
  class HKAnglesControls(QWidget):
@@ -369,7 +356,7 @@ class HKAnglesHistoryDialog(QDialog):
369
356
  # Create table for solutions
370
357
  table = QTableWidget()
371
358
  table.setColumnCount(5)
372
- table.setHorizontalHeaderLabels(["#", "tth (°)", "θ (°)", "φ (°)", "χ (°)"])
359
+ table.setHorizontalHeaderLabels(["#", "tth (°)", "θ (°)", "χ (°)", "φ (°)"])
373
360
  table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
374
361
  table.verticalHeader().setVisible(False)
375
362
  table.setMaximumHeight(100)
@@ -377,8 +364,8 @@ class HKAnglesHistoryDialog(QDialog):
377
364
  # Add rows for each solution
378
365
  tth_list = entry.get('tth', [])
379
366
  theta_list = entry.get('theta', [])
380
- phi_list = entry.get('phi', [])
381
367
  chi_list = entry.get('chi', [])
368
+ phi_list = entry.get('phi', [])
382
369
  feasible_list = entry.get('feasible', [])
383
370
 
384
371
  for i in range(len(tth_list)):
@@ -386,8 +373,8 @@ class HKAnglesHistoryDialog(QDialog):
386
373
  table.setItem(i, 0, QTableWidgetItem(f"{i + 1}"))
387
374
  table.setItem(i, 1, QTableWidgetItem(f"{tth_list[i]:.2f}"))
388
375
  table.setItem(i, 2, QTableWidgetItem(f"{theta_list[i]:.2f}"))
389
- table.setItem(i, 3, QTableWidgetItem(f"{phi_list[i]:.2f}"))
390
- table.setItem(i, 4, QTableWidgetItem(f"{chi_list[i]:.2f}"))
376
+ table.setItem(i, 3, QTableWidgetItem(f"{chi_list[i]:.2f}"))
377
+ table.setItem(i, 4, QTableWidgetItem(f"{phi_list[i]:.2f}"))
391
378
 
392
379
  # Color based on feasibility
393
380
  feasible = feasible_list[i] if i < len(feasible_list) else True
@@ -438,7 +425,7 @@ class HKAnglesResultsWidget(QWidget):
438
425
  # Current solutions display (table for up to 2 solutions)
439
426
  self.results_table = QTableWidget()
440
427
  self.results_table.setColumnCount(4)
441
- self.results_table.setHorizontalHeaderLabels(["tth (°)", "θ (°)", "φ (°)", "χ (°)"])
428
+ self.results_table.setHorizontalHeaderLabels(["tth (°)", "θ (°)", "χ (°)", "φ (°)"])
442
429
  self.results_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
443
430
  self.results_table.verticalHeader().setVisible(False)
444
431
  self.results_table.setMaximumHeight(90)
@@ -472,8 +459,8 @@ class HKAnglesResultsWidget(QWidget):
472
459
 
473
460
  tth_list = results.get('tth', [])
474
461
  theta_list = results.get('theta', [])
475
- phi_list = results.get('phi', [])
476
462
  chi_list = results.get('chi', [])
463
+ phi_list = results.get('phi', [])
477
464
  feasible_list = results.get('feasible', [])
478
465
 
479
466
  for i in range(len(tth_list)):
@@ -482,8 +469,8 @@ class HKAnglesResultsWidget(QWidget):
482
469
 
483
470
  self.results_table.setItem(row, 0, QTableWidgetItem(f"{tth_list[i]:.2f}"))
484
471
  self.results_table.setItem(row, 1, QTableWidgetItem(f"{theta_list[i]:.2f}"))
485
- self.results_table.setItem(row, 2, QTableWidgetItem(f"{phi_list[i]:.2f}"))
486
- self.results_table.setItem(row, 3, QTableWidgetItem(f"{chi_list[i]:.2f}"))
472
+ self.results_table.setItem(row, 2, QTableWidgetItem(f"{chi_list[i]:.2f}"))
473
+ self.results_table.setItem(row, 3, QTableWidgetItem(f"{phi_list[i]:.2f}"))
487
474
 
488
475
  # Color based on feasibility
489
476
  feasible = feasible_list[i] if i < len(feasible_list) else True
@@ -499,15 +486,15 @@ class HKAnglesResultsWidget(QWidget):
499
486
  if current_row >= 0 and self.current_result:
500
487
  tth_list = self.current_result.get('tth', [])
501
488
  theta_list = self.current_result.get('theta', [])
502
- phi_list = self.current_result.get('phi', [])
503
489
  chi_list = self.current_result.get('chi', [])
490
+ phi_list = self.current_result.get('phi', [])
504
491
 
505
492
  if current_row < len(tth_list):
506
493
  selected_solution = {
507
494
  'tth': tth_list[current_row],
508
495
  'theta': theta_list[current_row],
509
- 'phi': phi_list[current_row],
510
496
  'chi': chi_list[current_row],
497
+ 'phi': phi_list[current_row],
511
498
  'H': self.current_result.get('H'),
512
499
  'K': self.current_result.get('K'),
513
500
  'L': self.current_result.get('L'),
@@ -1,29 +1,18 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  # pylint: disable=no-name-in-module, import-error
4
- from PyQt5.QtWidgets import (
5
- QWidget,
6
- QVBoxLayout,
7
- QHBoxLayout,
8
- QFormLayout,
9
- QGroupBox,
10
- QLabel,
11
- QPushButton,
12
- QDoubleSpinBox,
13
- QSpinBox,
14
- QTableWidget,
15
- QTableWidgetItem,
16
- QHeaderView,
17
- QRadioButton,
18
- QButtonGroup,
19
- QFileDialog,
20
- QMessageBox,
21
- )
22
- from PyQt5.QtCore import Qt, pyqtSignal
23
- from PyQt5.QtGui import QColor, QBrush
24
4
  import csv
25
5
  import os
6
+
26
7
  import numpy as np
8
+ from PyQt5.QtCore import Qt, pyqtSignal
9
+ from PyQt5.QtGui import QBrush, QColor
10
+ from PyQt5.QtWidgets import (QButtonGroup, QDoubleSpinBox, QFileDialog,
11
+ QFormLayout, QGroupBox, QHBoxLayout, QHeaderView,
12
+ QLabel, QMessageBox, QPushButton, QRadioButton,
13
+ QSpinBox, QTableWidget, QTableWidgetItem,
14
+ QVBoxLayout, QWidget)
15
+
27
16
 
28
17
  class RangeInputWidget(QWidget):
29
18
  """Widget for input range (start, end, num_points)."""
@@ -434,8 +423,8 @@ class HKLScanResultsTable(QTableWidget):
434
423
  l_values = results["L"]
435
424
  tth_values = results["tth"]
436
425
  theta_values = results["theta"]
437
- phi_values = results["phi"]
438
426
  chi_values = results["chi"]
427
+ phi_values = results["phi"]
439
428
  feasible_values = results.get("feasible", [True] * len(h_values))
440
429
  solution_groups = results.get("solution_group", list(range(len(h_values))))
441
430
  solution_indices = results.get("solution_index", [1] * len(h_values))
@@ -456,8 +445,8 @@ class HKLScanResultsTable(QTableWidget):
456
445
 
457
446
  # Add angle values
458
447
  self.setItem(row_position, 4, QTableWidgetItem(f"{theta_values[i]:.1f}"))
459
- self.setItem(row_position, 5, QTableWidgetItem(f"{phi_values[i]:.1f}"))
460
- self.setItem(row_position, 6, QTableWidgetItem(f"{chi_values[i]:.1f}"))
448
+ self.setItem(row_position, 5, QTableWidgetItem(f"{chi_values[i]:.1f}"))
449
+ self.setItem(row_position, 6, QTableWidgetItem(f"{phi_values[i]:.1f}"))
461
450
  self.setItem(row_position, 7, QTableWidgetItem(f"{tth_values[0]-theta_values[i]:.1f}"))
462
451
 
463
452
  # Determine row color based on feasibility and group
@@ -510,8 +499,8 @@ class HKLScanResultsTable(QTableWidget):
510
499
  "Solution#",
511
500
  "tth (deg)",
512
501
  "theta (deg)",
513
- "phi (deg)",
514
502
  "chi (deg)",
503
+ "phi (deg)",
515
504
  "feasible",
516
505
  ]
517
506
  )
@@ -529,8 +518,8 @@ class HKLScanResultsTable(QTableWidget):
529
518
  f"{solution_indices[i]}",
530
519
  f"{self.last_results['tth'][i]:.6f}",
531
520
  f"{self.last_results['theta'][i]:.6f}",
532
- f"{self.last_results['phi'][i]:.6f}",
533
521
  f"{self.last_results['chi'][i]:.6f}",
522
+ f"{self.last_results['phi'][i]:.6f}",
534
523
  f"{feasible_values[i]}",
535
524
  ]
536
525
  )
@@ -1,26 +1,15 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  # pylint: disable=no-name-in-module, import-error
4
- from PyQt5.QtWidgets import (
5
- QWidget,
6
- QVBoxLayout,
7
- QHBoxLayout,
8
- QFormLayout,
9
- QGroupBox,
10
- QLabel,
11
- QPushButton,
12
- QDoubleSpinBox,
13
- QTableWidget,
14
- QTableWidgetItem,
15
- QHeaderView,
16
- QRadioButton,
17
- QButtonGroup,
18
- QDialog,
19
- QScrollArea,
20
- QFrame,
21
- )
22
4
  from PyQt5.QtCore import Qt, pyqtSignal
23
- from PyQt5.QtGui import QColor, QBrush, QFont
5
+ from PyQt5.QtGui import QBrush, QColor, QFont
6
+ from PyQt5.QtWidgets import (QButtonGroup, QDialog, QDoubleSpinBox,
7
+ QFormLayout, QFrame, QGroupBox, QHBoxLayout,
8
+ QHeaderView, QLabel, QPushButton, QRadioButton,
9
+ QScrollArea, QTableWidget, QTableWidgetItem,
10
+ QVBoxLayout, QWidget)
11
+
12
+
24
13
  class HKLToAnglesControls(QWidget):
25
14
  """Widget for HKL to Angles calculation controls."""
26
15
 
@@ -254,7 +243,7 @@ class HistoryDialog(QDialog):
254
243
  # Create table for solutions
255
244
  table = QTableWidget()
256
245
  table.setColumnCount(5)
257
- table.setHorizontalHeaderLabels(["#", "tth (°)", "θ (°)", "φ (°)", "χ (°)"])
246
+ table.setHorizontalHeaderLabels(["#", "tth (°)", "θ (°)", "χ (°)", "φ (°)"])
258
247
  table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
259
248
  table.verticalHeader().setVisible(False)
260
249
  table.setMaximumHeight(100)
@@ -262,8 +251,8 @@ class HistoryDialog(QDialog):
262
251
  # Add rows for each solution
263
252
  tth_list = entry.get('tth', [])
264
253
  theta_list = entry.get('theta', [])
265
- phi_list = entry.get('phi', [])
266
254
  chi_list = entry.get('chi', [])
255
+ phi_list = entry.get('phi', [])
267
256
  feasible_list = entry.get('feasible', [])
268
257
 
269
258
  for i in range(len(tth_list)):
@@ -271,8 +260,8 @@ class HistoryDialog(QDialog):
271
260
  table.setItem(i, 0, QTableWidgetItem(f"{i + 1}"))
272
261
  table.setItem(i, 1, QTableWidgetItem(f"{tth_list[i]:.2f}"))
273
262
  table.setItem(i, 2, QTableWidgetItem(f"{theta_list[i]:.2f}"))
274
- table.setItem(i, 3, QTableWidgetItem(f"{phi_list[i]:.2f}"))
275
- table.setItem(i, 4, QTableWidgetItem(f"{chi_list[i]:.2f}"))
263
+ table.setItem(i, 3, QTableWidgetItem(f"{chi_list[i]:.2f}"))
264
+ table.setItem(i, 4, QTableWidgetItem(f"{phi_list[i]:.2f}"))
276
265
 
277
266
  # Color based on feasibility
278
267
  feasible = feasible_list[i] if i < len(feasible_list) else True
@@ -323,7 +312,7 @@ class HKLToAnglesResultsWidget(QWidget):
323
312
  # Current solutions display (table for up to 2 solutions)
324
313
  self.results_table = QTableWidget()
325
314
  self.results_table.setColumnCount(4)
326
- self.results_table.setHorizontalHeaderLabels(["tth (°)", "θ (°)", "φ (°)", "χ (°)"])
315
+ self.results_table.setHorizontalHeaderLabels(["tth (°)", "θ (°)", "χ (°)", "φ (°)"])
327
316
  self.results_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
328
317
  self.results_table.verticalHeader().setVisible(False)
329
318
  self.results_table.setMaximumHeight(90)
@@ -357,8 +346,8 @@ class HKLToAnglesResultsWidget(QWidget):
357
346
 
358
347
  tth_list = results.get('tth', [])
359
348
  theta_list = results.get('theta', [])
360
- phi_list = results.get('phi', [])
361
349
  chi_list = results.get('chi', [])
350
+ phi_list = results.get('phi', [])
362
351
  feasible_list = results.get('feasible', [])
363
352
 
364
353
  for i in range(len(tth_list)):
@@ -367,8 +356,8 @@ class HKLToAnglesResultsWidget(QWidget):
367
356
 
368
357
  self.results_table.setItem(row, 0, QTableWidgetItem(f"{tth_list[i]:.2f}"))
369
358
  self.results_table.setItem(row, 1, QTableWidgetItem(f"{theta_list[i]:.2f}"))
370
- self.results_table.setItem(row, 2, QTableWidgetItem(f"{phi_list[i]:.2f}"))
371
- self.results_table.setItem(row, 3, QTableWidgetItem(f"{chi_list[i]:.2f}"))
359
+ self.results_table.setItem(row, 2, QTableWidgetItem(f"{chi_list[i]:.2f}"))
360
+ self.results_table.setItem(row, 3, QTableWidgetItem(f"{phi_list[i]:.2f}"))
372
361
 
373
362
  # Color based on feasibility
374
363
  feasible = feasible_list[i] if i < len(feasible_list) else True
@@ -384,15 +373,15 @@ class HKLToAnglesResultsWidget(QWidget):
384
373
  if current_row >= 0 and self.current_result:
385
374
  tth_list = self.current_result.get('tth', [])
386
375
  theta_list = self.current_result.get('theta', [])
387
- phi_list = self.current_result.get('phi', [])
388
376
  chi_list = self.current_result.get('chi', [])
377
+ phi_list = self.current_result.get('phi', [])
389
378
 
390
379
  if current_row < len(tth_list):
391
380
  selected_solution = {
392
381
  'tth': tth_list[current_row],
393
382
  'theta': theta_list[current_row],
394
- 'phi': phi_list[current_row],
395
383
  'chi': chi_list[current_row],
384
+ 'phi': phi_list[current_row],
396
385
  'H': self.current_result.get('H'),
397
386
  'K': self.current_result.get('K'),
398
387
  'L': self.current_result.get('L'),
@@ -437,7 +437,7 @@ class ScatteringGeometryTab(TabInterface):
437
437
  """Update the HKL scan visualization with auto-detected ranges and results."""
438
438
  try:
439
439
  # Structure factor calculator not needed for trajectory-only visualization
440
-
440
+
441
441
  # Use the provided scan results or the last stored results
442
442
  if scan_results is None:
443
443
  scan_results = getattr(self.hkl_scan_visualizer, 'last_scan_results', None)
@@ -456,6 +456,14 @@ class ScatteringGeometryTab(TabInterface):
456
456
 
457
457
  # Visualize results (ranges will be auto-detected)
458
458
  self.hkl_scan_visualizer.visualize_results(scan_results, plane_type)
459
+
460
+ # plot the accessible area in k-space
461
+ params = self.hkl_scan_controls.get_scan_parameters()
462
+ tth = params["tth"]
463
+ accessible_area = self.calculator.get_max_hkl_values(tth)
464
+ h_max, k_max, l_max = accessible_area["h_max"], accessible_area["k_max"], accessible_area["l_max"]
465
+ success, message = accessible_area["success"], accessible_area["message"]
466
+ self.hkl_scan_visualizer.plot_accessible_area(plane_type, h_max, k_max, l_max, message)
459
467
  else:
460
468
  # No scan results yet, just clear the plot
461
469
  self.hkl_scan_visualizer.clear_plot()
@@ -30,7 +30,7 @@ class DiffractionTestDialog(QDialog):
30
30
  self.lattice_params = lattice_params
31
31
  self.result = None # Will store (roll, pitch, yaw) on success
32
32
 
33
- self.setWindowTitle("Import Orientation from Diffraction Tests")
33
+ self.setWindowTitle("Import Orientation from UB Matrix Tests")
34
34
  self.setMinimumWidth(800)
35
35
  self.setMinimumHeight(550)
36
36
 
@@ -42,7 +42,7 @@ class DiffractionTestDialog(QDialog):
42
42
 
43
43
  # Instructions label
44
44
  instructions = QLabel(
45
- "Enter diffraction test data below. Each row represents a measurement "
45
+ "Enter UB matrix test (diffraction test) data below. Each row represents a measurement "
46
46
  "with known HKL indices and measured angles. At least one test is required."
47
47
  )
48
48
  instructions.setWordWrap(True)
@@ -52,7 +52,7 @@ class DiffractionTestDialog(QDialog):
52
52
  self.table = QTableWidget()
53
53
  self.table.setColumnCount(8)
54
54
  self.table.setHorizontalHeaderLabels(
55
- ["H", "K", "L", "Energy (eV)", "tth (°)", "θ (°)", "φ (°)", "χ (°)"]
55
+ ["H", "K", "L", "Energy (eV)", "tth (°)", "θ (°)", "χ (°)", "φ (°)"]
56
56
  )
57
57
 
58
58
  # Set column resize mode
@@ -116,6 +116,7 @@ class DiffractionTestDialog(QDialog):
116
116
 
117
117
  self.calculate_btn = QPushButton("Calculate Orientation")
118
118
  self.calculate_btn.clicked.connect(self._calculate_orientation)
119
+ self._set_button_highlighted(self.calculate_btn, True) # Highlight initially
119
120
  button_layout.addWidget(self.calculate_btn)
120
121
 
121
122
  self.apply_btn = QPushButton("Apply and Close")
@@ -129,12 +130,30 @@ class DiffractionTestDialog(QDialog):
129
130
 
130
131
  layout.addLayout(button_layout)
131
132
 
133
+ def _set_button_highlighted(self, button: QPushButton, highlighted: bool):
134
+ """Set button to highlighted (pastel blue) or normal style."""
135
+ if highlighted:
136
+ button.setStyleSheet(
137
+ "background-color: #a8d0f0; color: #2c5282; font-weight: bold;"
138
+ )
139
+ else:
140
+ button.setStyleSheet("") # Reset to default
141
+
142
+ def _set_button_success(self, button: QPushButton, success: bool):
143
+ """Set button to success (pastel green) or normal style."""
144
+ if success:
145
+ button.setStyleSheet(
146
+ "background-color: #a8e6c1; color: #2d5a3d; font-weight: bold;"
147
+ )
148
+ else:
149
+ button.setStyleSheet("") # Reset to default
150
+
132
151
  def _add_row(self):
133
152
  """Add a new empty row to the table."""
134
153
  row = self.table.rowCount()
135
154
  self.table.insertRow(row)
136
155
 
137
- # Set default values (H, K, L, energy, tth, theta, phi, chi)
156
+ # Set default values (H, K, L, energy, tth, theta, chi, phi)
138
157
  defaults = [0.0, 0.0, 0.0, 2200.0, 90.0, 45.0, 0.0, 0.0]
139
158
  for col, default in enumerate(defaults):
140
159
  item = QTableWidgetItem(str(default))
@@ -166,8 +185,8 @@ class DiffractionTestDialog(QDialog):
166
185
  "energy": float(self.table.item(row, 3).text()),
167
186
  "tth": float(self.table.item(row, 4).text()),
168
187
  "theta": float(self.table.item(row, 5).text()),
169
- "phi": float(self.table.item(row, 6).text()),
170
- "chi": float(self.table.item(row, 7).text()),
188
+ "chi": float(self.table.item(row, 6).text()),
189
+ "phi": float(self.table.item(row, 7).text()),
171
190
  }
172
191
  tests.append(test)
173
192
  except (ValueError, AttributeError) as e:
@@ -218,6 +237,10 @@ class DiffractionTestDialog(QDialog):
218
237
  self.results_group.setVisible(True)
219
238
  self.apply_btn.setEnabled(True)
220
239
 
240
+ # Update button styles: remove highlight from Calculate, add to Apply
241
+ self._set_button_highlighted(self.calculate_btn, False)
242
+ self._set_button_success(self.apply_btn, True)
243
+
221
244
  # Store result for later retrieval
222
245
  self.result = {
223
246
  "roll": result["roll"],
@@ -1,25 +1,16 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  # pylint: disable=no-name-in-module, import-error
4
- from PyQt5.QtWidgets import (
5
- QWidget,
6
- QGridLayout,
7
- QFormLayout,
8
- QLabel,
9
- QLineEdit,
10
- QPushButton,
11
- QDoubleSpinBox,
12
- QGroupBox,
13
- QMessageBox,
14
- QFileDialog,
15
- )
16
- from PyQt5.QtCore import pyqtSlot, pyqtSignal
4
+ from PyQt5.QtCore import pyqtSignal, pyqtSlot
17
5
  from PyQt5.QtGui import QDragEnterEvent, QDropEvent
6
+ from PyQt5.QtWidgets import (QDoubleSpinBox, QFileDialog, QFormLayout,
7
+ QGridLayout, QGroupBox, QLabel, QLineEdit,
8
+ QMessageBox, QPushButton, QWidget)
18
9
 
19
10
  from advisor.domain import UnitConverter
11
+ from advisor.ui.dialogs import DiffractionTestDialog
20
12
  from advisor.ui.utils import readcif
21
13
  from advisor.ui.visualizers import CoordinateVisualizer, UnitcellVisualizer
22
- from advisor.ui.dialogs import DiffractionTestDialog
23
14
 
24
15
 
25
16
  class DragDropLineEdit(QLineEdit):
@@ -189,8 +180,8 @@ class InitWindow(QWidget):
189
180
  # Add energy group to main layout at (0,1)
190
181
  layout.addWidget(energy_group, 0, 1)
191
182
 
192
- # Group box for Euler angles
193
- euler_group = QGroupBox("Euler Angles")
183
+ # Group box for Sample orientation Euler angles
184
+ euler_group = QGroupBox("Sample Orientation (Euler Angles)")
194
185
  euler_layout = QFormLayout(euler_group)
195
186
 
196
187
  self.roll_input = QDoubleSpinBox()
@@ -220,10 +211,10 @@ class InitWindow(QWidget):
220
211
  self.yaw_input.valueChanged.connect(self.update_visualization)
221
212
  euler_layout.addRow("Yaw:", self.yaw_input)
222
213
 
223
- # Add "Import from Diffraction Test" button
224
- self.import_orientation_btn = QPushButton("Import from Diffraction Test")
214
+ # Add "Set UB Matrix (Import from Diffraction Tests)" button
215
+ self.import_orientation_btn = QPushButton("Set UB Matrix")
225
216
  self.import_orientation_btn.setToolTip(
226
- "Calculate Euler angles from known diffraction measurements"
217
+ "Calculate Sample orientation (Euler angles) from known diffraction measurements. E.g. Two Braggs (002) (101) peaks are found at certain angles (tth, theta, phi, chi), at certain energy, the sample orientation can be determined."
227
218
  )
228
219
  self.import_orientation_btn.clicked.connect(self.open_diffraction_test_dialog)
229
220
  euler_layout.addRow(self.import_orientation_btn)
@@ -310,7 +301,7 @@ class InitWindow(QWidget):
310
301
  if dialog.exec_() == DiffractionTestDialog.Accepted:
311
302
  result = dialog.get_result()
312
303
  if result is not None:
313
- # Apply the calculated Euler angles
304
+ # Apply the calculated Sample orientation Euler angles
314
305
  self.roll_input.setValue(result["roll"])
315
306
  self.pitch_input.setValue(result["pitch"])
316
307
  self.yaw_input.setValue(result["yaw"])
@@ -1,6 +1,8 @@
1
- from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
2
- from matplotlib.figure import Figure
3
1
  import numpy as np
2
+ from matplotlib.backends.backend_qt5agg import \
3
+ FigureCanvasQTAgg as FigureCanvas
4
+ from matplotlib.figure import Figure
5
+ from matplotlib.patches import Ellipse
4
6
 
5
7
 
6
8
  class HKLScan2DVisualizer(FigureCanvas):
@@ -222,3 +224,36 @@ class HKLScan2DVisualizer(FigureCanvas):
222
224
  except Exception as e:
223
225
  print(f"Error in _plot_trajectory_only: {e}")
224
226
  return False
227
+
228
+ def plot_accessible_area(self, plane_type, h_max, k_max, l_max, message=None):
229
+ """ plot an ellipse in the plane to represent the accessible area in k-space.
230
+
231
+ Args:
232
+ plane_type: "HK", "HL", or "KL"
233
+ h_max: maximum value of the h-axis
234
+ k_max: maximum value of the k-axis
235
+ l_max: maximum value of the l-axis
236
+ """
237
+ # use matplotlib patches to plot an ellipse in the plane to represent the accessible area in
238
+ # k-space.
239
+
240
+ if plane_type == "HK":
241
+ x_max = h_max
242
+ y_max = k_max
243
+ elif plane_type == "HL":
244
+ x_max = h_max
245
+ y_max = l_max
246
+ elif plane_type == "KL":
247
+ x_max = k_max
248
+ y_max = l_max
249
+
250
+ ellipse = Ellipse(xy=(0, 0), width=2*x_max, height=2*y_max, angle=0, color='#80e8be', alpha=0.35, edgecolor='none')
251
+ self.axes.add_patch(ellipse)
252
+
253
+ # put a gray text on the top left corner of the plot
254
+ if message is not None:
255
+ self.axes.text(0.02, 0.985, message, color='gray', fontsize=8, transform=self.axes.transAxes, va="top", alpha = 0.65)
256
+
257
+ self.axes.text(0,0.1,"accessible area", color='#52a38b', fontsize=8, ha="center")
258
+ self.draw()
259
+ return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: advisor-scattering
3
- Version: 0.9.1
3
+ Version: 0.9.5
4
4
  Summary: Advisor-Scattering: Advanced Visual X-ray Scattering Toolkit for Reciprocal-space visualization and calculation
5
5
  Author: Xunyang Hong
6
6
  License: MIT
@@ -37,6 +37,7 @@ or use the link below to view the demo video.
37
37
  - Visualize scattering geometry and unit cells
38
38
  - Compute and visualize structure factors in reciprocal space.
39
39
  - CIF file drop-in support
40
+ - Import crystal orientation from diffraction test data
40
41
 
41
42
 
42
43
  ## Install
@@ -75,7 +76,8 @@ python -m advisor
75
76
  ### 1. Initialization window
76
77
  - Enter lattice constants (a, b, c) and angles (alpha, beta, gamma); beam energy auto-updates wavelength/|k|.
77
78
  - Optional: drop a CIF to autofill lattice parameters and preview the unit cell.
78
- - Adjust Euler angles (roll, pitch, yaw) to orient the sample relative to the scattering plane;
79
+ - Adjust Euler angles (roll, pitch, yaw) to orient the lattice/sample relative to the goniometer.
80
+ - **New:** Use **Import from Diffraction Test** to automatically determine Euler angles from known diffraction measurements.
79
81
  - Click **Initialize** to load the main interface and pass parameters to all tabs.
80
82
 
81
83
  ### 2. Scattering Geometry tab
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "advisor-scattering"
7
- version = "0.9.1"
7
+ version = "0.9.5"
8
8
  description = "Advisor-Scattering: Advanced Visual X-ray Scattering Toolkit for Reciprocal-space visualization and calculation"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"