XRD衍射谱面探数据拼接实践(python实现)
·
在 XRD实验中,传统点探测器直接输出强度-角度曲线,而现代设备越来越多采用二维面探测器。面探带来了更高的采集效率,但也引入一个核心问题:
- 如何把二维图像数据转换为标准的 2θ 衍射数据?
本文通过列角度对齐,将大量.tif的面探数据拼接成连续的XRD数据。
地址:https://gitee.com/zhang_jie_sc/xrd-data-analysis
1.背景
实验扫描流程如下:
- 每次曝光得到一张 256×256 面探图像
- 每张图对应一个 中心角度(mid angle)
- 图像的 每一列 实际对应不同的 2θ
- 多张图覆盖完整扫描范围(例如 10°–90°)
数据格式、数据内容如下图所示:


拼接完成效果:

即:
- X轴:2θ角度
- Y轴:探测器行
- 值:强度
最终形成类似连续衍射图谱的 mosaic。
2.核心物理关系
面探测器中:
- 中心列对应扫描角 mid
- 每个像素存在固定角度偏移
角度换算:
PIX_ANGLE = 0.055 / 225
DEG_PER_PX = PIX_ANGLE / np.pi * 180.0
#列角度
col_angles = mid + (np.arange(W) - c0) * DEG_PER_PX
这一步本质是:
- 把 像素坐标 → 物理角度坐标
3.算法整体思路
整个流程可以理解为:
读取图像
↓
计算每列真实角度
↓
找到该角度属于哪个bin
↓
将该列强度累加到目标矩阵
示例:
原始图像 (256×256)
↓
逐列投影
↓
角度bin对齐
↓
累加到全局矩阵
4.角度Bin设计
为了得到连续谱,需要建立统一角度网格:
angle_edges = np.arange(angle_min,angle_max + angle_step,angle_step)
例如:
10° → 90°
步长 0.02°
这相当于创建:
4000 个角度通道
最终 accumulator:
acc = np.zeros((H, n_bins))
5.关键函数解析
1️⃣ 文件名解析角度
def get_angle(filename):
m = re.search(r"\d+\.\d{3}", filename)
return float(m.group()) if m else 0.0
2️⃣ 列数据累加(核心)
def add_by_column(acc, angle_edges, angle, col):
bin_idx = np.searchsorted(angle_edges, angle, side="right") - 1
if bin_idx < acc.shape[1]:
acc[:, bin_idx] += col
这里做了三件事:
- 找到角度属于哪个 bin
- 取出该列强度
- 累加进全局矩阵
3️⃣ 主拼接流程
for fp in paths:
mid = get_angle(fp)
img = tifffile.imread(fp)
img = np.rot90(img, k=1)
col_angles = mid + (np.arange(W)-c0)*DEG_PER_PX
for j, angle_ in enumerate(col_angles):
if 10 <= angle_ <= 90:
add_by_column(acc, angle_edges, angle_, img[:, j])
6.优点
- 思路简单,先准备一个空白矩阵,再扫描每张图中的每一列数据,找到对应的列数累加即可
- 易于移植,没有复杂的numpy方法,可轻松移植到C++平台
7.完整代码
import re, time
from pathlib import Path
import numpy as np
import tifffile
import matplotlib.pyplot as plt
PIX_ANGLE = 0.055 / 225
DEG_PER_PX = PIX_ANGLE / np.pi * 180.0
def get_angle(filename: str) -> float:
m = re.search(r"\d+\.\d{3}", filename)
return float(m.group()) if m else 0.0
def add_by_column(acc: np.ndarray, angle_edges, angle, col):
bin_idx = np.searchsorted(angle_edges, angle, side="right") - 1
h, w = acc.shape
if bin_idx < w:
acc[:, bin_idx] += col
def stitch_bins_aligned(folder: Path, angle_min=10.0, angle_max=90.0, angle_step=0.02):
paths = sorted([str(p.resolve()) for p in folder.glob("*.tif")])
# bins
angle_edges = np.arange(angle_min, angle_max + angle_step, angle_step)
n_bins = len(angle_edges-1)
H, W = 256, 256
c0 = 128 # 128
acc = np.zeros((H, n_bins), dtype=np.float64)
for i, fp in enumerate(paths, 1):
mid = get_angle(fp)
img = tifffile.imread(fp).astype(np.float64) # (256,256)
img = np.rot90(img, k=1)
col_angles = mid + (np.arange(W) - c0) * DEG_PER_PX # (256,)
for j, angle_ in enumerate(col_angles, 0):
if 10 <= angle_ <= 90:
add_by_column(acc, angle_edges, angle_, img[:, j])
if i % 300 == 0 or i == len(paths):
print(f"已完成 {i}/{len(paths)} ({i / len(paths):.0%})")
acc = np.column_stack((acc, acc[:, -1]))
return acc, angle_edges
if __name__ == "__main__":
folder = Path(r'./MiniPixData/MiniPixData2')
mosaic, angle_edges = stitch_bins_aligned(folder)
print("max:",mosaic.max(),"min:",mosaic.min())
plt.figure(figsize=(10, 4))
plt.imshow(
mosaic,
aspect="auto",
origin="lower",
extent=[angle_edges[0], angle_edges[-1], 0, mosaic.shape[0]],
cmap="jet",
vmin=0,
vmax=mosaic.max()
)
plt.xlabel("2Theta (deg)")
plt.ylabel("Row (px)")
plt.title("Stitched mosaic (bins-aligned, no integration)")
plt.colorbar(label="Intensity")
plt.tight_layout()
plt.show()
更多推荐


所有评论(0)