advisor-scattering 0.5.0__py3-none-any.whl
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.
- advisor/__init__.py +3 -0
- advisor/__main__.py +7 -0
- advisor/app.py +40 -0
- advisor/controllers/__init__.py +6 -0
- advisor/controllers/app_controller.py +69 -0
- advisor/controllers/feature_controller.py +25 -0
- advisor/domain/__init__.py +23 -0
- advisor/domain/core/__init__.py +8 -0
- advisor/domain/core/lab.py +121 -0
- advisor/domain/core/lattice.py +79 -0
- advisor/domain/core/sample.py +101 -0
- advisor/domain/geometry.py +212 -0
- advisor/domain/unit_converter.py +82 -0
- advisor/features/__init__.py +6 -0
- advisor/features/scattering_geometry/controllers/__init__.py +5 -0
- advisor/features/scattering_geometry/controllers/scattering_geometry_controller.py +26 -0
- advisor/features/scattering_geometry/domain/__init__.py +5 -0
- advisor/features/scattering_geometry/domain/brillouin_calculator.py +410 -0
- advisor/features/scattering_geometry/domain/core.py +516 -0
- advisor/features/scattering_geometry/ui/__init__.py +5 -0
- advisor/features/scattering_geometry/ui/components/__init__.py +17 -0
- advisor/features/scattering_geometry/ui/components/angles_to_hkl_components.py +150 -0
- advisor/features/scattering_geometry/ui/components/hk_angles_components.py +430 -0
- advisor/features/scattering_geometry/ui/components/hkl_scan_components.py +526 -0
- advisor/features/scattering_geometry/ui/components/hkl_to_angles_components.py +315 -0
- advisor/features/scattering_geometry/ui/scattering_geometry_tab.py +725 -0
- advisor/features/structure_factor/controllers/__init__.py +6 -0
- advisor/features/structure_factor/controllers/structure_factor_controller.py +25 -0
- advisor/features/structure_factor/domain/__init__.py +6 -0
- advisor/features/structure_factor/domain/structure_factor_calculator.py +107 -0
- advisor/features/structure_factor/ui/__init__.py +6 -0
- advisor/features/structure_factor/ui/components/__init__.py +12 -0
- advisor/features/structure_factor/ui/components/customized_plane_components.py +358 -0
- advisor/features/structure_factor/ui/components/hkl_plane_components.py +391 -0
- advisor/features/structure_factor/ui/structure_factor_tab.py +273 -0
- advisor/resources/__init__.py +0 -0
- advisor/resources/config/app_config.json +14 -0
- advisor/resources/config/tips.json +4 -0
- advisor/resources/data/nacl.cif +111 -0
- advisor/resources/icons/bz_caculator.jpg +0 -0
- advisor/resources/icons/bz_calculator.png +0 -0
- advisor/resources/icons/minus.svg +3 -0
- advisor/resources/icons/placeholder.png +0 -0
- advisor/resources/icons/plus.svg +3 -0
- advisor/resources/icons/reset.png +0 -0
- advisor/resources/icons/sf_calculator.jpg +0 -0
- advisor/resources/icons/sf_calculator.png +0 -0
- advisor/resources/icons.qrc +6 -0
- advisor/resources/qss/styles.qss +348 -0
- advisor/resources/resources_rc.py +83 -0
- advisor/ui/__init__.py +7 -0
- advisor/ui/init_window.py +566 -0
- advisor/ui/main_window.py +174 -0
- advisor/ui/tab_interface.py +44 -0
- advisor/ui/tips.py +30 -0
- advisor/ui/utils/__init__.py +6 -0
- advisor/ui/utils/readcif.py +129 -0
- advisor/ui/visualizers/HKLScan2DVisualizer.py +224 -0
- advisor/ui/visualizers/__init__.py +8 -0
- advisor/ui/visualizers/coordinate_visualizer.py +203 -0
- advisor/ui/visualizers/scattering_visualizer.py +301 -0
- advisor/ui/visualizers/structure_factor_visualizer.py +426 -0
- advisor/ui/visualizers/structure_factor_visualizer_2d.py +235 -0
- advisor/ui/visualizers/unitcell_visualizer.py +518 -0
- advisor_scattering-0.5.0.dist-info/METADATA +122 -0
- advisor_scattering-0.5.0.dist-info/RECORD +69 -0
- advisor_scattering-0.5.0.dist-info/WHEEL +5 -0
- advisor_scattering-0.5.0.dist-info/entry_points.txt +3 -0
- advisor_scattering-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""This module provides a class for visualizing the coordinate system in a 3D space. More
|
|
2
|
+
specifically, it visualizes the relative position of of crystal coordinates with respect to the lab
|
|
3
|
+
coordinate system.
|
|
4
|
+
"""
|
|
5
|
+
import numpy as np
|
|
6
|
+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
7
|
+
from matplotlib.figure import Figure
|
|
8
|
+
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
|
|
9
|
+
from advisor.domain.core import Lab
|
|
10
|
+
|
|
11
|
+
class CoordinateVisualizer(FigureCanvas):
|
|
12
|
+
"""Visualizer for coordinate system with 3D interactive canvas."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, width=4, height=4, dpi=100):
|
|
15
|
+
"""Initialize the visualizer with a 3D canvas."""
|
|
16
|
+
self.fig = Figure(figsize=(width, height), dpi=dpi)
|
|
17
|
+
self.axes = self.fig.add_subplot(111, projection="3d")
|
|
18
|
+
self.axes.set_axis_off()
|
|
19
|
+
super().__init__(self.fig)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Set background color to white
|
|
23
|
+
self.fig.patch.set_facecolor("white")
|
|
24
|
+
self.axes.set_facecolor("white")
|
|
25
|
+
|
|
26
|
+
# Set initial view (adjusted for x-y scattering plane, beam from -y)
|
|
27
|
+
self.axes.view_init(elev=20, azim=30, roll=0)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Set initial limits
|
|
31
|
+
self.axes.set_xlim(-0.75, 0.75)
|
|
32
|
+
self.axes.set_ylim(-0.75, 0.75)
|
|
33
|
+
self.axes.set_zlim(-0.75, 0.75)
|
|
34
|
+
|
|
35
|
+
# Initialize reciprocal lattice vectors in lab frame
|
|
36
|
+
self.a_star_lab = np.array([1, 0, 0])
|
|
37
|
+
self.b_star_lab = np.array([0, 1, 0])
|
|
38
|
+
self.c_star_lab = np.array([0, 0, 1])
|
|
39
|
+
self.roll = 0
|
|
40
|
+
self.pitch = 0
|
|
41
|
+
self.yaw = 0
|
|
42
|
+
|
|
43
|
+
def initialize(self, params: dict):
|
|
44
|
+
"""Initialize the visualizer with the given parameters."""
|
|
45
|
+
self.roll = params["roll"]
|
|
46
|
+
self.pitch = params["pitch"]
|
|
47
|
+
self.yaw = params["yaw"]
|
|
48
|
+
a, b, c = params["a"], params["b"], params["c"]
|
|
49
|
+
alpha, beta, gamma = params["alpha"], params["beta"], params["gamma"]
|
|
50
|
+
|
|
51
|
+
lab = Lab()
|
|
52
|
+
lab.initialize(
|
|
53
|
+
a, b, c, alpha, beta, gamma, self.roll, self.pitch, self.yaw, 0, 0, 0
|
|
54
|
+
)
|
|
55
|
+
# calculate the corresponding a_star_lab, b_star_lab, c_star_lab
|
|
56
|
+
self.a_star_lab, self.b_star_lab, self.c_star_lab = (
|
|
57
|
+
lab.get_reciprocal_space_vectors()
|
|
58
|
+
)
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
def visualize_lab_system(self):
|
|
62
|
+
"""Update the visualization with new crystal coordinates system."""
|
|
63
|
+
# Clear previous plot
|
|
64
|
+
self.axes.clear()
|
|
65
|
+
|
|
66
|
+
# Plot the scattering plane (x-y plane, z=0), adjusted for beam from -y
|
|
67
|
+
scatter_plane_vertices = np.array(
|
|
68
|
+
[
|
|
69
|
+
[1.25, -1.25, 0], # bottom right
|
|
70
|
+
[-0.25, -1.25, 0], # bottom left
|
|
71
|
+
[1.25, 1.25, 0], # top right
|
|
72
|
+
[-0.25, 1.25, 0], # top left
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
scatter_plane_faces = np.array([[0, 1, 3, 2]]) # single face
|
|
77
|
+
self.axes.add_collection3d(
|
|
78
|
+
Poly3DCollection(
|
|
79
|
+
scatter_plane_vertices[scatter_plane_faces],
|
|
80
|
+
facecolors=[0.3010, 0.7450, 0.9330], # light blue
|
|
81
|
+
edgecolors=[0.7, 0.7, 0.7],
|
|
82
|
+
alpha=0.15,
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Define vertices of the cube (standing perpendicular to scattering plane)
|
|
87
|
+
# Rotated 90° so thin dimension (0.25) is along x-axis, facing beam from -y
|
|
88
|
+
ver = np.array(
|
|
89
|
+
[
|
|
90
|
+
[0.125, 0.25, 0.5], # top front right
|
|
91
|
+
[0.125, -0.25, 0.5], # top front left
|
|
92
|
+
[-0.125, -0.25, 0.5], # top back left
|
|
93
|
+
[-0.125, 0.25, 0.5], # top back right
|
|
94
|
+
[0.125, 0.25, -0.5], # bottom front right
|
|
95
|
+
[0.125, -0.25, -0.5], # bottom front left
|
|
96
|
+
[-0.125, -0.25, -0.5], # bottom back left
|
|
97
|
+
[-0.125, 0.25, -0.5], # bottom back right
|
|
98
|
+
]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Define faces of the cube
|
|
102
|
+
fac = np.array(
|
|
103
|
+
[
|
|
104
|
+
[0, 1, 2, 3], # top face
|
|
105
|
+
[4, 5, 6, 7], # bottom face
|
|
106
|
+
[0, 1, 5, 4], # front face
|
|
107
|
+
[2, 3, 7, 6], # back face
|
|
108
|
+
[0, 3, 7, 4], # right face
|
|
109
|
+
[1, 2, 6, 5], # left face
|
|
110
|
+
]
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Plot the cube
|
|
114
|
+
self.axes.add_collection3d(
|
|
115
|
+
Poly3DCollection(
|
|
116
|
+
ver[fac],
|
|
117
|
+
facecolors=[0.3, 0.3, 0.3],
|
|
118
|
+
edgecolors=[0.55, 0.55, 0.55],
|
|
119
|
+
alpha=0.2,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Normalize the vectors
|
|
124
|
+
a_star_norm = self.a_star_lab / np.linalg.norm(self.a_star_lab)
|
|
125
|
+
b_star_norm = self.b_star_lab / np.linalg.norm(self.b_star_lab)
|
|
126
|
+
c_star_norm = self.c_star_lab / np.linalg.norm(self.c_star_lab)
|
|
127
|
+
|
|
128
|
+
# Plot the normalized vectors
|
|
129
|
+
vectors = [a_star_norm, b_star_norm, c_star_norm]
|
|
130
|
+
colors = ["r", "r", "r"]
|
|
131
|
+
labels = ["$a^*$", "$b^*$", "$c^*$"]
|
|
132
|
+
|
|
133
|
+
for vec, color, label in zip(vectors, colors, labels):
|
|
134
|
+
# Plot the vector
|
|
135
|
+
self.axes.quiver(
|
|
136
|
+
0,
|
|
137
|
+
0,
|
|
138
|
+
0, # origin
|
|
139
|
+
vec[0],
|
|
140
|
+
vec[1],
|
|
141
|
+
vec[2], # vector components
|
|
142
|
+
color=color,
|
|
143
|
+
alpha=1,
|
|
144
|
+
linewidth=2,
|
|
145
|
+
arrow_length_ratio=0.2,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Add text label at the tip of the vector
|
|
149
|
+
# Add a small offset to prevent text from overlapping with the arrow
|
|
150
|
+
offset = 0.2
|
|
151
|
+
self.axes.text(
|
|
152
|
+
vec[0] + offset,
|
|
153
|
+
vec[1] + offset,
|
|
154
|
+
vec[2] + offset,
|
|
155
|
+
label,
|
|
156
|
+
color=color,
|
|
157
|
+
fontsize=14,
|
|
158
|
+
ha="center",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# plot the vector of the lab coordinate system, by default it is the unit vectors
|
|
162
|
+
e_X = np.array([1, 0, 0]) / 0.65
|
|
163
|
+
e_Y = np.array([0, 1, 0]) / 0.65
|
|
164
|
+
e_Z = np.array([0, 0, 1]) / 0.65
|
|
165
|
+
vectors = [e_X, e_Y, e_Z]
|
|
166
|
+
colors = ["r", "g", "b"]
|
|
167
|
+
labels = ["$X$", "$Y$", "$Z$"]
|
|
168
|
+
|
|
169
|
+
for vec, color, label in zip(vectors, colors, labels):
|
|
170
|
+
# Plot the vector
|
|
171
|
+
self.axes.quiver(
|
|
172
|
+
0,
|
|
173
|
+
0,
|
|
174
|
+
0, # origin
|
|
175
|
+
vec[0],
|
|
176
|
+
vec[1],
|
|
177
|
+
vec[2], # vector components
|
|
178
|
+
color=(64 / 255, 148 / 255, 184 / 255),
|
|
179
|
+
alpha=1,
|
|
180
|
+
linewidth=0.8,
|
|
181
|
+
arrow_length_ratio=0.1,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Add text label at the tip of the vector
|
|
185
|
+
# Add a small offset to prevent text from overlapping with the arrow
|
|
186
|
+
offset = 0.2
|
|
187
|
+
self.axes.text(
|
|
188
|
+
vec[0] + offset,
|
|
189
|
+
vec[1] + offset,
|
|
190
|
+
vec[2] + offset,
|
|
191
|
+
label,
|
|
192
|
+
color=(64 / 255, 148 / 255, 184 / 255),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Set axis limits
|
|
196
|
+
self.axes.set_xlim(-1, 1)
|
|
197
|
+
self.axes.set_ylim(-1, 1)
|
|
198
|
+
self.axes.set_zlim(-1, 1)
|
|
199
|
+
|
|
200
|
+
self.axes.set_axis_off()
|
|
201
|
+
self.fig.tight_layout()
|
|
202
|
+
# Update the canvas
|
|
203
|
+
self.draw()
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""This is a class to visualize the X-ray scattering geometry."""
|
|
5
|
+
import numpy as np
|
|
6
|
+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
7
|
+
from matplotlib.figure import Figure
|
|
8
|
+
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
|
|
9
|
+
from advisor.domain import (
|
|
10
|
+
get_reciprocal_space_vectors,
|
|
11
|
+
get_rotation,
|
|
12
|
+
)
|
|
13
|
+
from advisor.domain.core import Lab
|
|
14
|
+
|
|
15
|
+
class ScatteringVisualizer(FigureCanvas):
|
|
16
|
+
"""Visualizer for scattering geometry with 3D interactive canvas."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, width=4, height=4, dpi=100):
|
|
19
|
+
"""Initialize the visualizer with a 3D canvas."""
|
|
20
|
+
self.fig = Figure(figsize=(width, height), dpi=dpi)
|
|
21
|
+
self.axes = self.fig.add_subplot(111, projection="3d")
|
|
22
|
+
super().__init__(self.fig)
|
|
23
|
+
|
|
24
|
+
# Set initial view (adjusted for x-y scattering plane, beam from -y)
|
|
25
|
+
self.axes.view_init(elev=20, azim=30, roll=0)
|
|
26
|
+
|
|
27
|
+
# Initialize reciprocal lattice vectors in lab frame
|
|
28
|
+
self.a_star_lab = np.array([1, 0, 0])
|
|
29
|
+
self.b_star_lab = np.array([0, 1, 0])
|
|
30
|
+
self.c_star_lab = np.array([0, 0, 1])
|
|
31
|
+
self.roll = 0
|
|
32
|
+
self.pitch = 0
|
|
33
|
+
self.yaw = 0
|
|
34
|
+
|
|
35
|
+
def initialize(self, params: dict):
|
|
36
|
+
"""Initialize the visualizer with the given crystal coordinates system."""
|
|
37
|
+
self.roll = params["roll"]
|
|
38
|
+
self.pitch = params["pitch"]
|
|
39
|
+
self.yaw = params["yaw"]
|
|
40
|
+
a, b, c = params["a"], params["b"], params["c"]
|
|
41
|
+
alpha, beta, gamma = params["alpha"], params["beta"], params["gamma"]
|
|
42
|
+
lab = Lab()
|
|
43
|
+
lab.initialize(
|
|
44
|
+
a, b, c, alpha, beta, gamma, self.roll, self.pitch, self.yaw, 0, 0, 0
|
|
45
|
+
)
|
|
46
|
+
# calculate the corresponding a_star_lab, b_star_lab, c_star_lab
|
|
47
|
+
self.a_star_lab, self.b_star_lab, self.c_star_lab = (
|
|
48
|
+
lab.get_reciprocal_space_vectors(is_normalized=True)
|
|
49
|
+
)
|
|
50
|
+
self.a_lab, self.b_lab, self.c_lab = lab.get_real_space_vectors(is_normalized=True)
|
|
51
|
+
self.visualize_lab_system()
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
def visualize_lab_system(self, chi=0, phi=0, plot_k_basis=True, plot_basis = False, is_clear=True):
|
|
55
|
+
"""Update the visualization with new crystal coordinates system."""
|
|
56
|
+
if is_clear:
|
|
57
|
+
# Clear previous plot
|
|
58
|
+
self.axes.clear()
|
|
59
|
+
|
|
60
|
+
# Define vertices of the sample (standing perpendicular to scattering plane)
|
|
61
|
+
# Rotated 90° so thin dimension (0.25) is along x-axis, facing beam from -y
|
|
62
|
+
vertices_sample = np.array(
|
|
63
|
+
[
|
|
64
|
+
[0.125, 0.25, 0.5], # top front right
|
|
65
|
+
[0.125, -0.25, 0.5], # top front left
|
|
66
|
+
[-0.125, -0.25, 0.5], # top back left
|
|
67
|
+
[-0.125, 0.25, 0.5], # top back right
|
|
68
|
+
[0.125, 0.25, -0.5], # bottom front right
|
|
69
|
+
[0.125, -0.25, -0.5], # bottom front left
|
|
70
|
+
[-0.125, -0.25, -0.5], # bottom back left
|
|
71
|
+
[-0.125, 0.25, -0.5], # bottom back right
|
|
72
|
+
]
|
|
73
|
+
)
|
|
74
|
+
vertices_sample = _rotate_vertices(vertices_sample, phi, chi)
|
|
75
|
+
# Define faces of the cube
|
|
76
|
+
faces_sample = np.array(
|
|
77
|
+
[
|
|
78
|
+
[0, 1, 2, 3], # top face
|
|
79
|
+
[4, 5, 6, 7], # bottom face
|
|
80
|
+
[0, 1, 5, 4], # front face
|
|
81
|
+
[2, 3, 7, 6], # back face
|
|
82
|
+
[0, 3, 7, 4], # right face
|
|
83
|
+
[1, 2, 6, 5], # left face
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Plot the cube
|
|
88
|
+
self.axes.add_collection3d(
|
|
89
|
+
Poly3DCollection(
|
|
90
|
+
vertices_sample[faces_sample],
|
|
91
|
+
facecolors=[0.3, 0.3, 0.3],
|
|
92
|
+
edgecolors=[0.55, 0.55, 0.55, 0.2],
|
|
93
|
+
alpha=0.05,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# add extra color to the top face
|
|
98
|
+
|
|
99
|
+
# add extra color to the top face
|
|
100
|
+
self.axes.add_collection3d(
|
|
101
|
+
Poly3DCollection(
|
|
102
|
+
[vertices_sample[faces_sample[0]]],
|
|
103
|
+
facecolors=[0.3, 0.3, 0.3],
|
|
104
|
+
edgecolors=[0.55, 0.55, 0.55, 0.1],
|
|
105
|
+
alpha=0.25,
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
a_star_norm = self.a_star_lab if plot_k_basis else [0, 0, 0]
|
|
110
|
+
a_star_label = "$a^*$" if plot_k_basis else None
|
|
111
|
+
b_star_norm = self.b_star_lab if plot_k_basis else [0, 0, 0]
|
|
112
|
+
b_star_label = "$b^*$" if plot_k_basis else None
|
|
113
|
+
c_star_norm = self.c_star_lab if plot_k_basis else [0, 0, 0]
|
|
114
|
+
c_star_label = "$c^*$" if plot_k_basis else None
|
|
115
|
+
a_norm = self.a_lab if plot_basis else [0, 0, 0]
|
|
116
|
+
a_label = "$a$" if plot_basis else None
|
|
117
|
+
b_norm = self.b_lab if plot_basis else [0, 0, 0]
|
|
118
|
+
b_label = "$b$" if plot_basis else None
|
|
119
|
+
c_norm = self.c_lab if plot_basis else [0, 0, 0]
|
|
120
|
+
c_label = "$c$" if plot_basis else None
|
|
121
|
+
|
|
122
|
+
# Plot the normalized vectors
|
|
123
|
+
vectors = [a_star_norm, b_star_norm, c_star_norm, a_norm, b_norm, c_norm]
|
|
124
|
+
vectors = _rotate_vertices(vectors, phi, chi)
|
|
125
|
+
colors = ["tomato", "tomato", "tomato", "dodgerblue", "dodgerblue", "dodgerblue"]
|
|
126
|
+
labels = [a_star_label, b_star_label, c_star_label, a_label, b_label, c_label]
|
|
127
|
+
|
|
128
|
+
for vec, color, label in zip(vectors, colors, labels):
|
|
129
|
+
# Plot the vector
|
|
130
|
+
self.axes.quiver(
|
|
131
|
+
0,
|
|
132
|
+
0,
|
|
133
|
+
0, # origin
|
|
134
|
+
vec[0],
|
|
135
|
+
vec[1],
|
|
136
|
+
vec[2], # vector components
|
|
137
|
+
color=color,
|
|
138
|
+
alpha=0.25,
|
|
139
|
+
linewidth=2,
|
|
140
|
+
arrow_length_ratio=0.2,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Add text label at the tip of the vector
|
|
144
|
+
# Add a small offset to prevent text from overlapping with the arrow
|
|
145
|
+
offset = 0.1
|
|
146
|
+
self.axes.text(
|
|
147
|
+
vec[0] + offset+0.1,
|
|
148
|
+
vec[1] + offset,
|
|
149
|
+
vec[2] + offset,
|
|
150
|
+
label,
|
|
151
|
+
color=color,
|
|
152
|
+
fontsize=14,
|
|
153
|
+
ha="center",
|
|
154
|
+
alpha=0.4,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# plot the vector of the lab coordinate system, by default it is the unit vectors
|
|
158
|
+
e_X = np.array([1, 0, 0]) / 0.65
|
|
159
|
+
e_Y = np.array([0, 1, 0]) / 0.65
|
|
160
|
+
e_Z = np.array([0, 0, 1]) / 0.65
|
|
161
|
+
vectors = [e_X, e_Y, e_Z]
|
|
162
|
+
#vectors = _rotate_vertices(vectors, phi, chi)
|
|
163
|
+
colors = ["r", "g", "b"]
|
|
164
|
+
labels = ["$X$", "$Y$", "$Z$"]
|
|
165
|
+
|
|
166
|
+
for vec, color, label in zip(vectors, colors, labels):
|
|
167
|
+
# Plot the vector
|
|
168
|
+
self.axes.quiver(
|
|
169
|
+
0,
|
|
170
|
+
0,
|
|
171
|
+
0, # origin
|
|
172
|
+
vec[0],
|
|
173
|
+
vec[1],
|
|
174
|
+
vec[2], # vector components
|
|
175
|
+
color=(64 / 255, 148 / 255, 184 / 255),
|
|
176
|
+
alpha=0.4,
|
|
177
|
+
linewidth=0.8,
|
|
178
|
+
arrow_length_ratio=0.1,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Add text label at the tip of the vector
|
|
182
|
+
# Add a small offset to prevent text from overlapping with the arrow
|
|
183
|
+
offset = 0.2
|
|
184
|
+
self.axes.text(
|
|
185
|
+
vec[0] + offset,
|
|
186
|
+
vec[1] + offset,
|
|
187
|
+
vec[2] + offset,
|
|
188
|
+
label,
|
|
189
|
+
color=(64 / 255, 148 / 255, 184 / 255),
|
|
190
|
+
alpha=0.4,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Update the canvas
|
|
194
|
+
# Set axis limits
|
|
195
|
+
self.axes.set_xlim(-1, 1)
|
|
196
|
+
self.axes.set_ylim(-1, 1)
|
|
197
|
+
self.axes.set_zlim(-1, 1)
|
|
198
|
+
|
|
199
|
+
self.axes.set_axis_off()
|
|
200
|
+
self.fig.tight_layout()
|
|
201
|
+
self.draw()
|
|
202
|
+
|
|
203
|
+
def visualize_scattering_geometry(self, scattering_angles=None, is_clear=False):
|
|
204
|
+
"""Update the visualization with new scattering angles."""
|
|
205
|
+
if is_clear:
|
|
206
|
+
# Clear previous plot
|
|
207
|
+
self.axes.clear()
|
|
208
|
+
|
|
209
|
+
# Plot the scattering plane (x-y plane, z=0), adjusted for beam from -y
|
|
210
|
+
scatter_plane_vertices = np.array(
|
|
211
|
+
[
|
|
212
|
+
[1.25, -1.25, 0], # bottom right
|
|
213
|
+
[-0.25, -1.25, 0], # bottom left
|
|
214
|
+
[1.25, 1.25, 0], # top right
|
|
215
|
+
[-0.25, 1.25, 0], # top left
|
|
216
|
+
]
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
scatter_plane_faces = np.array([[0, 1, 3, 2]]) # single face
|
|
220
|
+
self.axes.add_collection3d(
|
|
221
|
+
Poly3DCollection(
|
|
222
|
+
scatter_plane_vertices[scatter_plane_faces],
|
|
223
|
+
facecolors=[0.3510, 0.7850, 0.9330], # light blue
|
|
224
|
+
edgecolors=[0.7, 0.7, 0.7],
|
|
225
|
+
alpha=0.3,
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Plot the x-ray beam
|
|
230
|
+
if scattering_angles is None:
|
|
231
|
+
scattering_angles = {
|
|
232
|
+
"theta": 50,
|
|
233
|
+
"tth": 150,
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Extract angles from data
|
|
237
|
+
theta = scattering_angles.get("theta", 50) # theta angle
|
|
238
|
+
tth = scattering_angles.get("tth", 150) # two theta angle
|
|
239
|
+
|
|
240
|
+
# Plot incident beam (k_in) - coming from -y direction (x-y plane)
|
|
241
|
+
offset = 0
|
|
242
|
+
k_in_length = 1.3
|
|
243
|
+
# Rotated 90° so theta=0 means beam comes from -y direction
|
|
244
|
+
k_in_x = -k_in_length * np.sin(np.radians(theta))
|
|
245
|
+
k_in_y = -k_in_length * np.cos(np.radians(theta))
|
|
246
|
+
k_in_z = 0
|
|
247
|
+
# Draw colored arrow on top
|
|
248
|
+
self.axes.quiver(
|
|
249
|
+
-k_in_x,
|
|
250
|
+
-k_in_y + offset,
|
|
251
|
+
-k_in_z,
|
|
252
|
+
k_in_x,
|
|
253
|
+
k_in_y,
|
|
254
|
+
k_in_z,
|
|
255
|
+
color=(191 / 255, 44 / 255, 0),
|
|
256
|
+
alpha=1,
|
|
257
|
+
linewidth=5,
|
|
258
|
+
arrow_length_ratio=0.2,
|
|
259
|
+
zorder=10,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Plot scattered beam (k_out) - in x-y plane, rotated 90° from original
|
|
263
|
+
k_out_length = 1.3
|
|
264
|
+
# Rotated 90° to match incident beam coming from -y
|
|
265
|
+
k_out_x = k_out_length * np.sin(np.radians(tth - theta))
|
|
266
|
+
k_out_y = -k_out_length * np.cos(np.radians(tth - theta))
|
|
267
|
+
k_out_z = 0
|
|
268
|
+
|
|
269
|
+
# Draw colored arrow on top
|
|
270
|
+
self.axes.quiver(
|
|
271
|
+
0,
|
|
272
|
+
0 + offset,
|
|
273
|
+
0,
|
|
274
|
+
k_out_x,
|
|
275
|
+
k_out_y,
|
|
276
|
+
k_out_z,
|
|
277
|
+
color=(2 / 255, 78 / 255, 191 / 255),
|
|
278
|
+
linewidth=5,
|
|
279
|
+
arrow_length_ratio=0.2,
|
|
280
|
+
zorder=10,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Set axis limits
|
|
284
|
+
self.axes.set_xlim(-1, 1)
|
|
285
|
+
self.axes.set_ylim(-1, 1)
|
|
286
|
+
self.axes.set_zlim(-1, 1)
|
|
287
|
+
|
|
288
|
+
self.axes.set_axis_off()
|
|
289
|
+
self.fig.tight_layout()
|
|
290
|
+
# Update the canvas
|
|
291
|
+
self.draw()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _rotate_vertices(vertices, phi, chi):
|
|
296
|
+
"""Rotate the vertices of the sample with respect to the scattering plane by the given phi and chi angles."""
|
|
297
|
+
rotation_matrix = get_rotation(phi, chi)
|
|
298
|
+
vertices = np.array(vertices)
|
|
299
|
+
for i, vertex in enumerate(vertices):
|
|
300
|
+
vertices[i] = rotation_matrix @ vertex
|
|
301
|
+
return vertices
|