Skip to content

Commit 69d052b

Browse files
committed
ENH: add plot showing inherent correction error
Due to finite width of pixels.
1 parent 3160f75 commit 69d052b

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# %% [markdown]
2+
# # Ineherent correction error
3+
#
4+
# There is an inherent error in the correction due to the finite width
5+
# of the detectors pixels. The inside and outside edges of the pixel
6+
# will correct to slightly different 2θ and ɸ values. When this exceeds
7+
# the target resolution there is no longer a gain from integrating
8+
# to higher ɸ/z.
9+
10+
# %%
11+
import matplotlib as mpl
12+
import matplotlib.pyplot as plt
13+
import numpy as np
14+
from matplotlib.transforms import blended_transform_factory
15+
from multihead.config import AnalyzerConfig
16+
from multihead.corrections import tth_from_z
17+
18+
from hrd_tools.detector_stats import detectors
19+
20+
21+
# %%
22+
# Configuration and setup for plotting
23+
cfg = AnalyzerConfig(
24+
910,
25+
120,
26+
np.rad2deg(np.arcsin(0.8 / (2 * 3.1355))),
27+
2 * np.rad2deg(np.arcsin(0.8 / (2 * 3.1355))),
28+
detector_roll=0,
29+
)
30+
31+
# Use all available detectors
32+
selected_detectors = list(detectors.keys())
33+
34+
# Calculate detector properties (all in mm)
35+
det_props = {}
36+
for name in selected_detectors:
37+
det = detectors[name]
38+
width = det.sensor_shape[1] * det.pixel_pitch * 1e-3 # convert µm to mm
39+
pixel_size = det.pixel_pitch * 1e-3 # convert µm to mm
40+
det_props[name] = {"width": width, "pixel_size": pixel_size}
41+
print(det_props)
42+
43+
# %%
44+
# Plot 1: Error vs z position for different 2theta values
45+
# Group detectors by pixel size
46+
pixel_size_groups = {}
47+
for det_name in selected_detectors:
48+
pixel_size = det_props[det_name]["pixel_size"]
49+
if pixel_size not in pixel_size_groups:
50+
pixel_size_groups[pixel_size] = []
51+
pixel_size_groups[pixel_size].append(det_name)
52+
53+
# Sort pixel sizes for consistent ordering
54+
sorted_pixel_sizes = sorted(pixel_size_groups.keys())
55+
56+
# Create subplots - one per pixel size
57+
n_pixel_sizes = len(sorted_pixel_sizes)
58+
fig1, axes1 = plt.subplots(
59+
1,
60+
n_pixel_sizes,
61+
layout="constrained",
62+
figsize=(4 * n_pixel_sizes, 4),
63+
dpi=100,
64+
sharey=True,
65+
sharex=True,
66+
)
67+
68+
# Handle case where there's only one pixel size
69+
if n_pixel_sizes == 1:
70+
axes1 = [axes1]
71+
72+
tth_arm_values = [10, 30, 60, 90] # degrees
73+
tth_colors = mpl.colormaps["viridis"](np.linspace(0, 1, len(tth_arm_values)))
74+
75+
# Find the largest detector width to set the plotting range (in mm)
76+
max_width = max(det_props[det]["width"] for det in selected_detectors)
77+
78+
for subplot_idx, pixel_size in enumerate(sorted_pixel_sizes):
79+
ax = axes1[subplot_idx]
80+
81+
# Create z positions at pixel edges from center to edge of detector (in mm)
82+
z_positions = np.arange(0, max_width / 2 + pixel_size, pixel_size)
83+
84+
# Calculate correction for all tth values and positions at once
85+
(tth_all), (phi_all) = tth_from_z(
86+
z_positions.reshape(1, -1),
87+
np.array(tth_arm_values).reshape(-1, 1),
88+
cfg,
89+
)
90+
91+
# Calculate the difference between adjacent positions (pixel edge effect)
92+
delta_2theta = np.abs(tth_all[:, 1:] - tth_all[:, :-1])
93+
94+
# Z positions corresponding to pixel centers (in mm)
95+
z_pos = z_positions[:-1] + pixel_size / 2
96+
97+
# Plot lines for different scatter 2theta values
98+
for i, tth in enumerate(tth_arm_values):
99+
ax.plot(
100+
z_pos,
101+
delta_2theta[i],
102+
color=tth_colors[i],
103+
label=f"$2\\Theta$ = {tth}°",
104+
linewidth=2,
105+
)
106+
107+
# Create blended transform: data coords for x, axes coords for y
108+
trans = blended_transform_factory(ax.transData, ax.transAxes)
109+
110+
# Add vertical lines for detectors with this pixel size
111+
for det_name in pixel_size_groups[pixel_size]:
112+
det_width = det_props[det_name]["width"]
113+
ax.axvline(x=det_width / 2, color="gray", linestyle="--", alpha=0.7)
114+
115+
# Add detector name annotation at top of axes
116+
ax.annotate(
117+
det_name,
118+
xy=(det_width / 2, 1.0),
119+
xytext=(2, -2),
120+
textcoords="offset points",
121+
xycoords=trans,
122+
color="gray",
123+
fontsize=8,
124+
rotation=90,
125+
va="top",
126+
ha="left",
127+
)
128+
129+
ax.set_title(f"{pixel_size * 1e3:.0f} µm pixel")
130+
ax.grid(True, alpha=0.3)
131+
132+
# Only add legend to first subplot
133+
if subplot_idx == 0:
134+
ax.legend()
135+
ax.set_ylabel("2θ correction error (deg)")
136+
137+
ax.set_xlabel("Distance from detector center (mm)")
138+
139+
fig1.suptitle("Inherent 2θ correction error vs 2Θ angle")
140+
plt.show()
141+

0 commit comments

Comments
 (0)