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,426 @@
|
|
|
1
|
+
"""Structure Factor Visualizer for 3D HKL space plotting."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
5
|
+
from matplotlib.figure import Figure
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StructureFactorVisualizer3D(FigureCanvas):
|
|
9
|
+
"""3D visualizer for structure factors in reciprocal space"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, width=8, height=6, dpi=100):
|
|
12
|
+
"""Initialize the 3D structure factor visualizer
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
width: Figure width in inches
|
|
16
|
+
height: Figure height in inches
|
|
17
|
+
dpi: Figure resolution
|
|
18
|
+
"""
|
|
19
|
+
# Create figure with proper backend configuration
|
|
20
|
+
self.fig = Figure(figsize=(width, height), dpi=dpi, tight_layout=True)
|
|
21
|
+
super().__init__(self.fig)
|
|
22
|
+
|
|
23
|
+
# Initialize state
|
|
24
|
+
self._initialized = False
|
|
25
|
+
self._colorbar = None
|
|
26
|
+
|
|
27
|
+
# Create 3D subplot
|
|
28
|
+
self._create_3d_plot()
|
|
29
|
+
|
|
30
|
+
# Plane highlight state
|
|
31
|
+
self._active_plane = None # one of {"H","K","L"} or None
|
|
32
|
+
self._plane_alpha_active = 0.85
|
|
33
|
+
self._plane_alpha_inactive = 0.4
|
|
34
|
+
self._H_val = None
|
|
35
|
+
self._K_val = None
|
|
36
|
+
self._L_val = None
|
|
37
|
+
|
|
38
|
+
def _create_3d_plot(self):
|
|
39
|
+
"""Create the 3D subplot and set up basic appearance"""
|
|
40
|
+
try:
|
|
41
|
+
# Clear any existing subplots
|
|
42
|
+
self.fig.clear()
|
|
43
|
+
|
|
44
|
+
# Create 3D subplot
|
|
45
|
+
self.axes = self.fig.add_subplot(111, projection="3d")
|
|
46
|
+
# Containers for plane artists
|
|
47
|
+
self._plane_h = None
|
|
48
|
+
self._plane_k = None
|
|
49
|
+
self._plane_l = None
|
|
50
|
+
self._plane_custom = None
|
|
51
|
+
|
|
52
|
+
# Set basic labels and title
|
|
53
|
+
self.axes.set_xlabel("H (r.l.u.)", fontsize=10)
|
|
54
|
+
self.axes.set_ylabel("K (r.l.u.)", fontsize=10)
|
|
55
|
+
self.axes.set_zlabel("L (r.l.u.)", fontsize=10)
|
|
56
|
+
#self.axes.set_title("Structure Factors in Reciprocal Space", fontsize=12)
|
|
57
|
+
|
|
58
|
+
# Set default axis limits (0, 5) with ±0.5 padding
|
|
59
|
+
self.axes.set_xlim(-0.5, 5.5)
|
|
60
|
+
self.axes.set_ylim(-0.5, 5.5)
|
|
61
|
+
self.axes.set_zlim(-0.5, 5.5)
|
|
62
|
+
|
|
63
|
+
# Set integer ticks only
|
|
64
|
+
from matplotlib.ticker import MaxNLocator
|
|
65
|
+
|
|
66
|
+
self.axes.xaxis.set_major_locator(MaxNLocator(integer=True))
|
|
67
|
+
self.axes.yaxis.set_major_locator(MaxNLocator(integer=True))
|
|
68
|
+
self.axes.zaxis.set_major_locator(MaxNLocator(integer=True))
|
|
69
|
+
|
|
70
|
+
# Set background color
|
|
71
|
+
self.axes.xaxis.pane.fill = False
|
|
72
|
+
self.axes.yaxis.pane.fill = False
|
|
73
|
+
self.axes.zaxis.pane.fill = False
|
|
74
|
+
|
|
75
|
+
# Make grid lines less prominent
|
|
76
|
+
self.axes.grid(True, alpha=0.3)
|
|
77
|
+
|
|
78
|
+
# Set default viewing angle (rotate 180° around Z compared to prior)
|
|
79
|
+
self.axes.view_init(elev=12.5, azim=200)
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
print(f"Error creating 3D plot: {e}")
|
|
83
|
+
|
|
84
|
+
def initialize(self, params: dict = None):
|
|
85
|
+
"""Initialize the visualizer
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
params: Optional parameters (not used for structure factor visualization)
|
|
89
|
+
"""
|
|
90
|
+
self._initialized = True
|
|
91
|
+
self._create_3d_plot()
|
|
92
|
+
self.draw()
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
# --- Plane overlay API ---
|
|
96
|
+
def set_plane_values(self, H=None, K=None, L=None):
|
|
97
|
+
"""Update translucent planes at constant H, K, L. Any None leaves it unchanged.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
H: fixed H value for K-L plane (x = H)
|
|
101
|
+
K: fixed K value for H-L plane (y = K)
|
|
102
|
+
L: fixed L value for H-K plane (z = L)
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
ax = self.axes
|
|
106
|
+
# Determine display extents from current limits
|
|
107
|
+
x_min, x_max = ax.get_xlim()
|
|
108
|
+
y_min, y_max = ax.get_ylim()
|
|
109
|
+
z_min, z_max = ax.get_zlim()
|
|
110
|
+
|
|
111
|
+
import numpy as np
|
|
112
|
+
|
|
113
|
+
# Helper to draw/update a plane
|
|
114
|
+
def draw_plane(current, new_artist_name, const_val, axis: str):
|
|
115
|
+
if const_val is None:
|
|
116
|
+
return current
|
|
117
|
+
# Remove existing
|
|
118
|
+
if current is not None:
|
|
119
|
+
try:
|
|
120
|
+
current.remove()
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
# Choose alpha based on active plane selection
|
|
124
|
+
alpha = (
|
|
125
|
+
self._plane_alpha_active
|
|
126
|
+
if (self._active_plane is not None and axis == self._active_plane)
|
|
127
|
+
else self._plane_alpha_inactive
|
|
128
|
+
)
|
|
129
|
+
color = {
|
|
130
|
+
"H": (1.0, 0.8, 0.86, alpha), # pinkish
|
|
131
|
+
"K": (0.80, 0.92, 0.773, alpha), # greenish
|
|
132
|
+
"L": (0.635, 0.812, 0.996, alpha), # bluish
|
|
133
|
+
}[axis]
|
|
134
|
+
|
|
135
|
+
if axis == "H":
|
|
136
|
+
X = np.full((2, 2), const_val)
|
|
137
|
+
Y = np.array([[y_min, y_min], [y_max, y_max]])
|
|
138
|
+
Z = np.array([[z_min, z_max], [z_min, z_max]])
|
|
139
|
+
elif axis == "K":
|
|
140
|
+
X = np.array([[x_min, x_min], [x_max, x_max]])
|
|
141
|
+
Y = np.full((2, 2), const_val)
|
|
142
|
+
Z = np.array([[z_min, z_max], [z_min, z_max]])
|
|
143
|
+
else: # L
|
|
144
|
+
X = np.array([[x_min, x_max], [x_min, x_max]])
|
|
145
|
+
Y = np.array([[y_min, y_min], [y_max, y_max]])
|
|
146
|
+
Z = np.full((2, 2), const_val)
|
|
147
|
+
|
|
148
|
+
artist = ax.plot_surface(X, Y, Z, color=color, linewidth=0, shade=False)
|
|
149
|
+
return artist
|
|
150
|
+
|
|
151
|
+
self._plane_h = draw_plane(self._plane_h, "_plane_h", H, "H")
|
|
152
|
+
self._plane_k = draw_plane(self._plane_k, "_plane_k", K, "K")
|
|
153
|
+
self._plane_l = draw_plane(self._plane_l, "_plane_l", L, "L")
|
|
154
|
+
|
|
155
|
+
# Store current plane constants for future redraws
|
|
156
|
+
if H is not None:
|
|
157
|
+
self._H_val = H
|
|
158
|
+
if K is not None:
|
|
159
|
+
self._K_val = K
|
|
160
|
+
if L is not None:
|
|
161
|
+
self._L_val = L
|
|
162
|
+
|
|
163
|
+
self.draw()
|
|
164
|
+
except Exception as e:
|
|
165
|
+
print(f"Error updating planes: {e}")
|
|
166
|
+
|
|
167
|
+
def is_initialized(self):
|
|
168
|
+
"""Check if the visualizer is initialized"""
|
|
169
|
+
return self._initialized
|
|
170
|
+
|
|
171
|
+
def set_active_plane(
|
|
172
|
+
self, axis: str, alpha_active: float = None, alpha_inactive: float = None
|
|
173
|
+
):
|
|
174
|
+
"""Highlight one of the planes by adjusting alpha values.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
axis: one of "H", "K", or "L"
|
|
178
|
+
alpha_active: optional override for active plane alpha
|
|
179
|
+
alpha_inactive: optional override for inactive plane alpha
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
axis = (axis or "").upper()
|
|
183
|
+
if axis not in {"H", "K", "L"}:
|
|
184
|
+
return
|
|
185
|
+
if alpha_active is not None:
|
|
186
|
+
self._plane_alpha_active = float(alpha_active)
|
|
187
|
+
if alpha_inactive is not None:
|
|
188
|
+
self._plane_alpha_inactive = float(alpha_inactive)
|
|
189
|
+
self._active_plane = axis
|
|
190
|
+
# Redraw existing planes to apply new alphas
|
|
191
|
+
# Re-issue current values for any planes we know
|
|
192
|
+
if self._H_val is not None:
|
|
193
|
+
self.set_plane_values(H=self._H_val)
|
|
194
|
+
if self._K_val is not None:
|
|
195
|
+
self.set_plane_values(K=self._K_val)
|
|
196
|
+
if self._L_val is not None:
|
|
197
|
+
self.set_plane_values(L=self._L_val)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
print(f"Error setting active plane: {e}")
|
|
200
|
+
|
|
201
|
+
# --- Custom plane overlay (spanned by two vectors) ---
|
|
202
|
+
def set_custom_plane(
|
|
203
|
+
self,
|
|
204
|
+
vector_u: tuple,
|
|
205
|
+
vector_v: tuple,
|
|
206
|
+
u_min: float = -5.0,
|
|
207
|
+
u_max: float = 5.0,
|
|
208
|
+
v_min: float = -5.0,
|
|
209
|
+
v_max: float = 5.0,
|
|
210
|
+
steps: int = 2,
|
|
211
|
+
center: tuple = (0, 0, 0),
|
|
212
|
+
):
|
|
213
|
+
"""Overlay a translucent plane spanned by two vectors in HKL space.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
vector_u: (h, k, l) tuple for the first spanning vector
|
|
217
|
+
vector_v: (h, k, l) tuple for the second spanning vector
|
|
218
|
+
u_min/u_max: parameter range along vector_u
|
|
219
|
+
v_min/v_max: parameter range along vector_v
|
|
220
|
+
steps: grid resolution along each parameter (2 draws a quad)
|
|
221
|
+
center: (h, k, l) tuple shifting the plane center in HKL space
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
# Remove existing custom plane
|
|
225
|
+
if self._plane_custom is not None:
|
|
226
|
+
try:
|
|
227
|
+
self._plane_custom.remove()
|
|
228
|
+
except Exception:
|
|
229
|
+
pass
|
|
230
|
+
self._plane_custom = None
|
|
231
|
+
|
|
232
|
+
# Build parametric grid
|
|
233
|
+
import numpy as np
|
|
234
|
+
|
|
235
|
+
u_vals = np.linspace(u_min, u_max, max(2, int(steps)))
|
|
236
|
+
v_vals = np.linspace(v_min, v_max, max(2, int(steps)))
|
|
237
|
+
U, V = np.meshgrid(u_vals, v_vals)
|
|
238
|
+
|
|
239
|
+
uh, uk, ul = vector_u
|
|
240
|
+
vh, vk, vl = vector_v
|
|
241
|
+
|
|
242
|
+
ch, ck, cl = center
|
|
243
|
+
|
|
244
|
+
X = ch + (U * uh + V * vh)
|
|
245
|
+
Y = ck + (U * uk + V * vk)
|
|
246
|
+
Z = cl + (U * ul + V * vl)
|
|
247
|
+
|
|
248
|
+
color = (0.9, 0.9, 0.2, 0.35) # yellowish translucent
|
|
249
|
+
self._plane_custom = self.axes.plot_surface(
|
|
250
|
+
X, Y, Z, color=color, linewidth=1, shade=False
|
|
251
|
+
)
|
|
252
|
+
self.draw()
|
|
253
|
+
except Exception as e:
|
|
254
|
+
print(f"Error drawing custom plane: {e}")
|
|
255
|
+
|
|
256
|
+
def visualize_structure_factors(self, hkl_list, sf_values):
|
|
257
|
+
"""Visualize structure factors as 3D scatter plot
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
hkl_list: List of [h, k, l] indices, shape (N, 3)
|
|
261
|
+
sf_values: Array of structure factor magnitudes, shape (N,)
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
# Validate inputs
|
|
265
|
+
if len(hkl_list) == 0 or len(sf_values) == 0:
|
|
266
|
+
print("Warning: Empty HKL list or structure factor values")
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
if len(hkl_list) != len(sf_values):
|
|
270
|
+
print(
|
|
271
|
+
f"Error: HKL list length ({len(hkl_list)}) != SF values length ({len(sf_values)})"
|
|
272
|
+
)
|
|
273
|
+
return False
|
|
274
|
+
|
|
275
|
+
# Remove existing colorbar if present
|
|
276
|
+
if self._colorbar is not None:
|
|
277
|
+
self._colorbar.remove()
|
|
278
|
+
self._colorbar = None
|
|
279
|
+
|
|
280
|
+
# Clear and recreate the 3D subplot
|
|
281
|
+
self.fig.clear()
|
|
282
|
+
self.axes = self.fig.add_subplot(111, projection="3d")
|
|
283
|
+
|
|
284
|
+
# Convert to numpy arrays for easier handling
|
|
285
|
+
hkl_array = np.array(hkl_list)
|
|
286
|
+
sf_array = np.array(sf_values)
|
|
287
|
+
|
|
288
|
+
# Extract H, K, L coordinates
|
|
289
|
+
h_coords = hkl_array[:, 0]
|
|
290
|
+
k_coords = hkl_array[:, 1]
|
|
291
|
+
l_coords = hkl_array[:, 2]
|
|
292
|
+
|
|
293
|
+
# Calculate dot sizes based on structure factor magnitude
|
|
294
|
+
# Scale factors for visibility
|
|
295
|
+
min_size = 0 # Increased minimum dot size for better visibility
|
|
296
|
+
max_size = 300 # Increased maximum dot size
|
|
297
|
+
|
|
298
|
+
print(f"SF array max: {sf_array.max()}, min: {sf_array.min()}")
|
|
299
|
+
|
|
300
|
+
# Normalize structure factor values for sizing
|
|
301
|
+
if sf_array.max() > 0:
|
|
302
|
+
normalized_sf = sf_array / sf_array.max()
|
|
303
|
+
dot_sizes = min_size + (max_size - min_size) * normalized_sf
|
|
304
|
+
else:
|
|
305
|
+
dot_sizes = np.full(len(sf_array), min_size)
|
|
306
|
+
|
|
307
|
+
mask = dot_sizes < 1
|
|
308
|
+
h_coords_plot = h_coords[~mask]
|
|
309
|
+
k_coords_plot = k_coords[~mask]
|
|
310
|
+
l_coords_plot = l_coords[~mask]
|
|
311
|
+
sf_array_plot = sf_array[~mask]
|
|
312
|
+
dot_sizes_plot = dot_sizes[~mask]
|
|
313
|
+
# Create scatter plot
|
|
314
|
+
scatter = self.axes.scatter(
|
|
315
|
+
h_coords_plot,
|
|
316
|
+
k_coords_plot,
|
|
317
|
+
l_coords_plot,
|
|
318
|
+
s=dot_sizes_plot,
|
|
319
|
+
c=sf_array_plot,
|
|
320
|
+
cmap="viridis",
|
|
321
|
+
alpha=0.9, # More opaque for better visibility
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
print(f"Created scatter plot with {len(h_coords)} points")
|
|
325
|
+
|
|
326
|
+
# Add text labels next to each dot
|
|
327
|
+
is_label_visible = False
|
|
328
|
+
if is_label_visible:
|
|
329
|
+
for i, (h, k, l) in enumerate(zip(h_coords, k_coords, l_coords)):
|
|
330
|
+
if mask[i]:
|
|
331
|
+
color_font = "silver"
|
|
332
|
+
else:
|
|
333
|
+
color_font = "black"
|
|
334
|
+
# Create label text (e.g., "010" for [0,1,0])
|
|
335
|
+
label = f"{h}{k}{l}"
|
|
336
|
+
|
|
337
|
+
# Add text annotation with slight offset
|
|
338
|
+
self.axes.text(
|
|
339
|
+
h + 0.1,
|
|
340
|
+
k + 0.1,
|
|
341
|
+
l + 0.1, # Small offset from dot position
|
|
342
|
+
label,
|
|
343
|
+
fontsize=8,
|
|
344
|
+
ha="left",
|
|
345
|
+
va="bottom",
|
|
346
|
+
color=color_font,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
print(f"Added text labels for {len(hkl_list)} points")
|
|
350
|
+
|
|
351
|
+
# Add colorbar
|
|
352
|
+
self._colorbar = self.fig.colorbar(
|
|
353
|
+
scatter, ax=self.axes, label="|Structure Factor|", shrink=0.6, pad=0.1
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# Set axis labels and title
|
|
357
|
+
self.axes.set_xlabel("H (r.l.u.)", fontsize=10)
|
|
358
|
+
self.axes.set_ylabel("K (r.l.u.)", fontsize=10)
|
|
359
|
+
self.axes.set_zlabel("L (r.l.u.)", fontsize=10)
|
|
360
|
+
self.axes.set_title("Structure Factors in Reciprocal Space", fontsize=12)
|
|
361
|
+
|
|
362
|
+
# Set axis limits with default range (0, 5) and adjust if needed
|
|
363
|
+
default_max = 5.5
|
|
364
|
+
|
|
365
|
+
# Calculate required ranges based on data with ±0.5 margin
|
|
366
|
+
default_min = -0.5
|
|
367
|
+
h_min = min(h_coords.min(), 0) - 0.5
|
|
368
|
+
k_min = min(k_coords.min(), 0) - 0.5
|
|
369
|
+
l_min = min(l_coords.min(), 0) - 0.5
|
|
370
|
+
h_max = max(h_coords.max() + 0.5, default_max)
|
|
371
|
+
k_max = max(k_coords.max() + 0.5, default_max)
|
|
372
|
+
l_max = max(l_coords.max() + 0.5, default_max)
|
|
373
|
+
|
|
374
|
+
# Set limits from calculated min to max
|
|
375
|
+
self.axes.set_xlim(h_min if not np.isnan(h_min) else default_min, h_max)
|
|
376
|
+
self.axes.set_ylim(k_min if not np.isnan(k_min) else default_min, k_max)
|
|
377
|
+
self.axes.set_zlim(l_min if not np.isnan(l_min) else default_min, l_max)
|
|
378
|
+
|
|
379
|
+
# Set integer ticks only
|
|
380
|
+
from matplotlib.ticker import MaxNLocator
|
|
381
|
+
|
|
382
|
+
self.axes.xaxis.set_major_locator(MaxNLocator(integer=True))
|
|
383
|
+
self.axes.yaxis.set_major_locator(MaxNLocator(integer=True))
|
|
384
|
+
self.axes.zaxis.set_major_locator(MaxNLocator(integer=True))
|
|
385
|
+
|
|
386
|
+
print(f"Set axis limits: X(0, {h_max}), Y(0, {k_max}), Z(0, {l_max})")
|
|
387
|
+
|
|
388
|
+
# Improve 3D viewing angle (rotated 180° around Z)
|
|
389
|
+
self.axes.view_init(elev=12.5, azim=200)
|
|
390
|
+
|
|
391
|
+
# Enable grid with low alpha
|
|
392
|
+
self.axes.grid(True, alpha=0.3)
|
|
393
|
+
|
|
394
|
+
# Update the canvas
|
|
395
|
+
self.draw()
|
|
396
|
+
|
|
397
|
+
print(f"Successfully plotted {len(hkl_list)} structure factors")
|
|
398
|
+
return True
|
|
399
|
+
|
|
400
|
+
except Exception as e:
|
|
401
|
+
print(f"Error in visualize_structure_factors: {e}")
|
|
402
|
+
return False
|
|
403
|
+
|
|
404
|
+
def clear_plot(self):
|
|
405
|
+
"""Clear the current plot"""
|
|
406
|
+
try:
|
|
407
|
+
# Remove existing colorbar if present
|
|
408
|
+
if self._colorbar is not None:
|
|
409
|
+
self._colorbar.remove()
|
|
410
|
+
self._colorbar = None
|
|
411
|
+
|
|
412
|
+
# Recreate the 3D plot
|
|
413
|
+
self._create_3d_plot()
|
|
414
|
+
self.draw()
|
|
415
|
+
except Exception as e:
|
|
416
|
+
print(f"Error clearing plot: {e}")
|
|
417
|
+
|
|
418
|
+
def visualize(self):
|
|
419
|
+
"""Create an empty visualization (placeholder)"""
|
|
420
|
+
try:
|
|
421
|
+
self._create_3d_plot()
|
|
422
|
+
self.draw()
|
|
423
|
+
return True
|
|
424
|
+
except Exception as e:
|
|
425
|
+
print(f"Error in visualize: {e}")
|
|
426
|
+
return False
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""2D Structure Factor Visualizer for sliced HK/HL/KL planes"""
|
|
2
|
+
|
|
3
|
+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
4
|
+
from matplotlib.figure import Figure
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StructureFactorVisualizer2D(FigureCanvas):
|
|
9
|
+
"""2D visualizer that shows a sliced plane of HKL with fixed index.
|
|
10
|
+
|
|
11
|
+
The canvas renders a 2D scatter where marker size and color scale with
|
|
12
|
+
the magnitude of the structure factor.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, width: float = 5.0, height: float = 4.0, dpi: int = 100):
|
|
16
|
+
self.fig = Figure(figsize=(float(width), float(height)), dpi=int(dpi), tight_layout=True)
|
|
17
|
+
super().__init__(self.fig)
|
|
18
|
+
self.axes = self.fig.add_subplot(111)
|
|
19
|
+
self._colorbar = None
|
|
20
|
+
self._initialized = True
|
|
21
|
+
|
|
22
|
+
def clear_plot(self):
|
|
23
|
+
try:
|
|
24
|
+
if self._colorbar is not None:
|
|
25
|
+
self._colorbar.remove()
|
|
26
|
+
self._colorbar = None
|
|
27
|
+
self.fig.clear()
|
|
28
|
+
self.axes = self.fig.add_subplot(111)
|
|
29
|
+
# Default range 0..5 with ±0.5 padding
|
|
30
|
+
self.axes.set_xlim(-0.5, 5.5)
|
|
31
|
+
self.axes.set_ylim(-0.5, 5.5)
|
|
32
|
+
self.draw()
|
|
33
|
+
except Exception:
|
|
34
|
+
# Keep UI responsive even if clear fails
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def visualize_uv_plane_points(
|
|
38
|
+
self,
|
|
39
|
+
uv_points,
|
|
40
|
+
sf_values,
|
|
41
|
+
vector_u_label: str,
|
|
42
|
+
vector_v_label: str,
|
|
43
|
+
vector_center=None,
|
|
44
|
+
value_max=None,
|
|
45
|
+
):
|
|
46
|
+
"""Render points in a user-defined plane parameterized by integers u,v.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
uv_points: list of dicts with keys {'u','v','H','K','L'} for labels
|
|
50
|
+
sf_values: 1D array-like of |F| values per point
|
|
51
|
+
vector_u_label: text label for u-axis (e.g., "[h k l] of U")
|
|
52
|
+
vector_v_label: text label for v-axis
|
|
53
|
+
vector_center: a vector for center of the plane (e.g. [0,0,0])
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
import numpy as np
|
|
57
|
+
|
|
58
|
+
if len(uv_points) == 0:
|
|
59
|
+
return False
|
|
60
|
+
u = np.array([p['u'] for p in uv_points])
|
|
61
|
+
v = np.array([p['v'] for p in uv_points])
|
|
62
|
+
f = np.asarray(sf_values)
|
|
63
|
+
if value_max is None:
|
|
64
|
+
value_max = f.max() if len(f) > 0 else 1.0
|
|
65
|
+
|
|
66
|
+
if len(u) != len(v) or len(u) != len(f):
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
# Reset axes
|
|
70
|
+
if self._colorbar is not None:
|
|
71
|
+
self._colorbar.remove()
|
|
72
|
+
self._colorbar = None
|
|
73
|
+
self.fig.clear()
|
|
74
|
+
ax = self.fig.add_subplot(111)
|
|
75
|
+
self.axes = ax
|
|
76
|
+
|
|
77
|
+
# Normalize sizes
|
|
78
|
+
min_size, max_size = 0, 300
|
|
79
|
+
if value_max > 0:
|
|
80
|
+
sizes = min_size + (max_size - min_size) * (f / value_max)
|
|
81
|
+
else:
|
|
82
|
+
sizes = np.full_like(f, min_size, dtype=float)
|
|
83
|
+
|
|
84
|
+
mask = sizes < 1
|
|
85
|
+
sizes = sizes[~mask]
|
|
86
|
+
u_plot = u[~mask]
|
|
87
|
+
v_plot = v[~mask]
|
|
88
|
+
f_plot = f[~mask]
|
|
89
|
+
|
|
90
|
+
sc = ax.scatter(u_plot, v_plot, c=f_plot, s=sizes, cmap="viridis", alpha=0.9, vmax=value_max)
|
|
91
|
+
|
|
92
|
+
# Labels per point with HKL
|
|
93
|
+
for i, p in enumerate(uv_points):
|
|
94
|
+
if mask[i]:
|
|
95
|
+
color_font = "silver"
|
|
96
|
+
else:
|
|
97
|
+
color_font = "black"
|
|
98
|
+
ax.text(
|
|
99
|
+
p['u'] + 0.05,
|
|
100
|
+
p['v'] + 0.05,
|
|
101
|
+
f"{p['H']} {p['K']} {p['L']}",
|
|
102
|
+
fontsize=7,
|
|
103
|
+
color=color_font,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
ax.set_xlabel(f"u along {vector_u_label}")
|
|
107
|
+
ax.set_ylabel(f"v along {vector_v_label}")
|
|
108
|
+
if vector_center is not None:
|
|
109
|
+
cx, cy, cz = vector_center
|
|
110
|
+
subtitle = f" centered at [{cx} {cy} {cz}]"
|
|
111
|
+
else:
|
|
112
|
+
subtitle = ""
|
|
113
|
+
|
|
114
|
+
# Limits and integer ticks
|
|
115
|
+
u_min, u_max = u.min(), u.max()
|
|
116
|
+
v_min, v_max = v.min(), v.max()
|
|
117
|
+
ax.set_xlim(u_min - 0.5, u_max + 0.5)
|
|
118
|
+
ax.set_ylim(v_min - 0.5, v_max + 0.5)
|
|
119
|
+
try:
|
|
120
|
+
from matplotlib.ticker import MaxNLocator
|
|
121
|
+
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
|
|
122
|
+
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
|
123
|
+
except Exception:
|
|
124
|
+
pass
|
|
125
|
+
ax.grid(True, alpha=0.3)
|
|
126
|
+
self.draw()
|
|
127
|
+
return True
|
|
128
|
+
except Exception:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def visualize_plane(
|
|
132
|
+
self,
|
|
133
|
+
x_values,
|
|
134
|
+
y_values,
|
|
135
|
+
sf_values,
|
|
136
|
+
x_label: str,
|
|
137
|
+
y_label: str,
|
|
138
|
+
fixed_name: str,
|
|
139
|
+
fixed_value: int,
|
|
140
|
+
value_max = None,
|
|
141
|
+
):
|
|
142
|
+
"""Render a 2D plane plot.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
x_values: 1D array-like for x-axis (e.g., H)
|
|
146
|
+
y_values: 1D array-like for y-axis (e.g., K)
|
|
147
|
+
sf_values: 1D array-like magnitudes |F|
|
|
148
|
+
x_label: which HKL index the x-axis represents ("H"|"K"|"L")
|
|
149
|
+
y_label: which HKL index the y-axis represents ("H"|"K"|"L")
|
|
150
|
+
fixed_name: the fixed HKL index ("H"|"K"|"L")
|
|
151
|
+
fixed_value: the integer value of the fixed index
|
|
152
|
+
Returns:
|
|
153
|
+
bool indicating success
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
x = np.asarray(x_values)
|
|
157
|
+
y = np.asarray(y_values)
|
|
158
|
+
f = np.asarray(sf_values)
|
|
159
|
+
if value_max is None:
|
|
160
|
+
value_max = f.max()
|
|
161
|
+
if len(x) == 0 or len(y) == 0 or len(f) == 0 or len(x) != len(y) or len(x) != len(f):
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
# Reset axes
|
|
165
|
+
if self._colorbar is not None:
|
|
166
|
+
self._colorbar.remove()
|
|
167
|
+
self._colorbar = None
|
|
168
|
+
self.fig.clear()
|
|
169
|
+
ax = self.fig.add_subplot(111)
|
|
170
|
+
self.axes = ax
|
|
171
|
+
|
|
172
|
+
# Normalize for size
|
|
173
|
+
min_size, max_size = 0, 300
|
|
174
|
+
if value_max > 0:
|
|
175
|
+
sizes = min_size + (max_size - min_size) * (f / value_max)
|
|
176
|
+
else:
|
|
177
|
+
sizes = np.full_like(f, min_size, dtype=float)
|
|
178
|
+
|
|
179
|
+
mask = sizes < 1
|
|
180
|
+
sizes = sizes[~mask]
|
|
181
|
+
x_plot = x[~mask]
|
|
182
|
+
y_plot = y[~mask]
|
|
183
|
+
f_plot = f[~mask]
|
|
184
|
+
|
|
185
|
+
sc = ax.scatter(x_plot, y_plot, c=f_plot, s=sizes, cmap="viridis", alpha=0.9, vmax=value_max)
|
|
186
|
+
|
|
187
|
+
# Add labels for each point (e.g., integer coordinates)
|
|
188
|
+
x_key = x_label.upper()
|
|
189
|
+
y_key = y_label.upper()
|
|
190
|
+
f_key = fixed_name.upper()
|
|
191
|
+
|
|
192
|
+
for i, (xi, yi) in enumerate(zip(x, y)):
|
|
193
|
+
if mask[i]:
|
|
194
|
+
color_font = "silver"
|
|
195
|
+
else:
|
|
196
|
+
color_font = "black"
|
|
197
|
+
values = {"H": None, "K": None, "L": None}
|
|
198
|
+
values[x_key] = int(round(float(xi)))
|
|
199
|
+
values[y_key] = int(round(float(yi)))
|
|
200
|
+
values[f_key] = int(fixed_value)
|
|
201
|
+
ax.text(
|
|
202
|
+
xi + 0.05,
|
|
203
|
+
yi + 0.05,
|
|
204
|
+
f"{values['H']} {values['K']} {values['L']}",
|
|
205
|
+
fontsize=7,
|
|
206
|
+
color=color_font,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Colorbar intentionally removed for cleaner 2D view
|
|
210
|
+
|
|
211
|
+
ax.set_xlabel(f"{x_label} (r.l.u.)")
|
|
212
|
+
ax.set_ylabel(f"{y_label} (r.l.u.)")
|
|
213
|
+
ax.set_title(f"{x_label}{y_label} plane | {fixed_name} = {fixed_value}")
|
|
214
|
+
|
|
215
|
+
# Set limits and integer ticks
|
|
216
|
+
# Default limits 0..5 with auto-expand if needed
|
|
217
|
+
x_max = max(x.max(), 5)
|
|
218
|
+
y_max = max(y.max(), 5)
|
|
219
|
+
ax.set_xlim(-0.5, x_max + 0.5)
|
|
220
|
+
ax.set_ylim(-0.5, y_max + 0.5)
|
|
221
|
+
try:
|
|
222
|
+
from matplotlib.ticker import MaxNLocator
|
|
223
|
+
|
|
224
|
+
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
|
|
225
|
+
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
|
|
226
|
+
except Exception:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
ax.grid(True, alpha=0.3)
|
|
230
|
+
self.draw()
|
|
231
|
+
return True
|
|
232
|
+
except Exception:
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
|