Solving crystal orientation from 2D Laue diffraction images
uv is a fast Python package installer and resolver. If you don't have it installed:
curl -LsSf https://astral.sh/uv/install.sh | shThen create a virtual environment and install the project:
uv venv env
source env/bin/activate # On Windows: env\Scripts\activate
uv pip install -e .python -m venv env
source env/bin/activate # On Windows: env\Scripts\activate
python -m pip install -e .uv pip install -e ".[test]" # with uv
# or
python -m pip install -e ".[test]" # with pipWe use JAX is to enable GPU-accelerated optimization algorithms. Subhkl uses the CPU version of JAX by default, but can be installed with support for NVIDIA or AMD GPUs.
Installation options:
# CPU-only JAX acceleration (faster, but no GPU)
pip install subhkl
# NVIDIA GPU support (CUDA 12.x)
pip install subhkl[cuda12]
# AMD GPU support (ROCm)
pip install subhkl[rocm]For development (editable install):
# CPU JAX version
uv pip install -e .
# CUDA 12
uv pip install -e ".[cuda12]"
# ROCm (AMD)
uv pip install -e ".[rocm]"Building:
docker build -t subhkl .
Running:
docker run -it --rm --name=subhkl --gpus all subhkl
subhkl will be available for import inside of Python in the container.
You will need to get the raw mesolite IMAGINE images from GitLab.
Assume that they are stored in the folder mesolite_202405.
The script run_all_imagine.sh runs the full workflow for a single
image. You can use the following command to apply the script to all the images in
mesolite_202405. This will generate a .mtz file for each input image.
for Z in mesolite_202405/*.tif; do run_all_imagine.sh $Z& doneTo merge the output .mtz files, you can use reciprocalspaceship. We
will probably add this as a command, but for now the following python code
works.
import reciprocalspaceship as rs
import os
mtzs = []
for file in os.listdir("mesolite_202405"):
if os.path.splitext(file)[1] == ".mtz":
mtzs.append(rs.read_mtz(os.path.join("mesolite_202405", file)))
rs.concat(mtzs).hkl_to_asu().write_mtz("mesolite_202405/meso.mtz")which creates a single .mtz file mesolite_202405/meso.mtz that contains
all reflections.
finder and rbf-integrator draw their unrolled-detector plots while they
run, which costs both rendering time and a lot of disk at the 600 dpi they use.
Two commands rebuild those plots later from the HDF5 files, so a run can be
told to draw nothing and still be looked at afterwards:
# run the search without drawing anything
python -m subhkl.io.parser finder images.h5 MANDI --output-filename found.h5
# ... and draw it later, from the two HDF5 files
python -m subhkl.io.parser finder-visualize images.h5 found.h5
python -m subhkl.io.parser integrator-visualize images.h5 integrated.h5images.h5 is the reduced (or merged) image stack the step ran on; the peaks
file supplies the peak centres and the width fitted to each one. Neither
command re-runs the search, so this is quick, and --dpi trades resolution for
size. Keep the two HDF5 files -- from a benchmark artifact, for instance -- and
any run can be spot-checked without the raw data.
Each peak is outlined at the size it was actually fitted with, not at a fixed
marker size: a circle of radius n_sigma * sigma for the sparse-RBF finder's
isotropic widths, and the corresponding ellipse for the RBF integrator's full
covariance. --n-sigma sets which contour that is (2 by default, holding about
86% of a 2D Gaussian's flux). The finders that fit no width are drawn as plain
markers, because there is no size to draw.
This project uses the Laue Equation to relate Miller indices
where:
-
$\mathbf{h}$ : Miller indices vector $\begin{pmatrix} h \ k \ l \end{pmatrix}$. -
$B$ : Reciprocal lattice matrix (Cartesian system). Transforms Miller indices to reciprocal space units ($1/\text{\AA}$ if$2\pi$ is not absorbed). -
$U$ : Orientation matrix (Sample to Cartesian). Transforms reciprocal lattice to the goniometer/sample frame. -
$R$ : Goniometer rotation matrix (Lab to Sample). Calculated from goniometer axes and angles using Mantid'sSetGoniometerconvention ($R = R_{\text{omega}} R_{\text{chi}} R_{\text{phi}}$ ).
The scattering vector
where
-
Lab Frame:
$Z$ is along the incident beam,$Y$ is vertically upward. - Sample Frame: Attached to the innermost goniometer axis.
-
Angles:
$2\theta$ is the scattering angle,$\phi$ is the azimuthal angle.
pytest -vTests that need the mesolite dataset are marked mesolite and are left out of
that run, because the dataset is downloaded from Zenodo on first use and that
takes hours. Ask for them explicitly:
pytest -v -m mesolite
# or download only the first few files, which is enough for most of them
MESOLITE_MAX_FILES=1 pytest -v -m mesoliteThe other markers are slow and integration; CI runs unit tests with
-m "not slow and not integration and not mesolite".
ruff format --check && ruff checkTo auto-fix formatting issues:
ruff formatThe project uses automated publishing to PyPI and GitHub Container Registry when you create a semantic version tag.
Prerequisites:
-
Set up PyPI trusted publishing (one-time setup):
- Go to https://pypi.org/manage/account/publishing/
- Add GitHub as a trusted publisher for your repository
- Set the workflow filename to
publish.yaml - Set the environment name to
pypi
-
Create and push a release tag:
# Create a new version tag (e.g., v0.1.0)
git tag v0.1.0
# Push the tag to GitHub
git push origin v0.1.0This will automatically:
- Build and publish the package to PyPI
- Build and push a Docker image to
ghcr.io/zjmorgan/subhkl:v0.1.0(andlatest)