PyRef is a Python library for reducing 2D X-ray reflectivity detector images into 1D reflectivity signals. It is designed for processing experimental data from polarized resonant soft X-ray reflectivity (PRSoXR) beamlines, specifically beamline 11.0.1.2 at the Advanced Light Source (ALS).
pip install pyref
For development installations, see the development setup section.
from pathlib import Path
from pyref.loader import PrsoxrLoader
# Initialize loader with experiment directory
loader = PrsoxrLoader(Path("/path/to/experiment/data"))
# Access reflectivity data
refl_df = loader.refl
print(refl_df)
# Open interactive masking tool
loader.mask_image()
# The mask is automatically applied to all subsequent processing
# Press 'm' to save and close the mask editor
# Reflectivity DataFrame
refl = loader.refl # Columns: file_name, Q, r, dr
# Full metadata DataFrame
meta = loader.meta # All images, headers, calculated columns
# Plot reflectivity data
plot = loader.plot_data()
Experimental data is organized into “stitches” - separate measurement chunks where beamline parameters are adjusted to capture reflectivity across multiple orders of magnitude.
# Group by stitch (file_name)
for file_name, stitch_data in loader.refl.group_by("file_name"):
print(f"Stitch: {file_name}")
print(f"Q range: {stitch_data['Q'].min()} to {stitch_data['Q'].max()}")
print(f"Number of points: {len(stitch_data)}")
PyRef automatically propagates uncertainty through all calculations. The reflectivity DataFrame includes dr (uncertainty in reflectivity) calculated from counting statistics.
import polars as pl
from pyref.utils import err_prop_mult, err_prop_div, weighted_mean
# Example: Propagate uncertainty through multiplication
# If you need to apply a scale factor with uncertainty
result = df.with_columns([
err_prop_mult(
pl.col("r"), pl.col("dr"),
pl.lit(scale_factor), pl.lit(scale_uncertainty)
).alias("scaled_r_err")
])
# Weighted mean for combining overlapping points
combined = df.group_by("Q").agg([
weighted_mean(pl.col("r"), pl.col("dr")).alias("r_mean"),
weighted_std(pl.col("dr")).alias("r_err")
])
dr = sqrt(r)file_nameTo combine multiple reflectivity scans collected in separate stitches:
Example workflow:
# This is a conceptual example - full stitching implementation may vary
stitches = loader.refl.group_by("file_name")
stitched_data = []
cumulative_scale = 1.0
cumulative_scale_err = 0.0
for i, (file_name, stitch) in enumerate(stitches):
if i == 0:
# First stitch is reference
stitched_data.append(stitch)
continue
# Find overlap with previous stitch
prev_stitch = stitched_data[-1]
overlap = find_overlapping_q(prev_stitch, stitch)
if len(overlap) > 0:
# Calculate scale factor from overlap
scale, scale_err = calculate_scale_factor(overlap)
# Apply scale with uncertainty propagation
scaled_stitch = stitch.with_columns([
(pl.col("r") * scale).alias("r"),
err_prop_mult(
pl.col("r"), pl.col("dr"),
pl.lit(scale), pl.lit(scale_err)
).alias("dr")
])
# Combine overlapping points using weighted mean
combined = combine_overlapping_points(prev_stitch, scaled_stitch)
stitched_data[-1] = combined
else:
stitched_data.append(stitch)
PyRef implements standard error propagation formulas:
Multiplication: σ(xy) = |xy| * sqrt((σx/x)² + (σy/y)²)
Division: σ(x/y) = |x/y| * sqrt((σx/x)² + (σy/y)²)
Weighted Mean: Uses inverse-variance weighting: w = 1/σ²
These are implemented as Polars expressions and can be used in any DataFrame operation.
from pyref.types import HeaderValue
loader = PrsoxrLoader(
directory,
extra_keys=[
HeaderValue.HIGHER_ORDER_SUPPRESSOR,
HeaderValue.EXIT_SLIT_SIZE,
# Add any custom header keys
]
)
from pyref.io import read_experiment, read_fits
# Read entire experiment directory
df = read_experiment("/path/to/data", headers=["Beamline Energy", "EXPOSURE"])
# Read specific FITS file
df = read_fits("/path/to/file.fits", headers=["Beamline Energy"])
# Save as CSV files (one per stitch)
loader.write_csv()
# Save as Parquet (more efficient)
loader.write_parquet()
# Visualize individual frames with beam spot and properties
loader.check_spot()
Main class for loading and processing reflectivity data.
Parameters:
directory (Path): Path to directory containing FITS filesextra_keys (list[HeaderValue] |
None): Additional header keys to extract |
Attributes:
refl (pl.DataFrame): Reflectivity DataFrame with Q, r, drmeta (pl.DataFrame): Full metadata DataFrame with all imagesmask (np.ndarray): Image mask for beam isolationenergy (np.ndarray): Unique energy values in experimentpolarization (Literal[”s”, “p”]): Polarization statename (str |
np.ndarray): Sample name(s) |
files (np.ndarray): List of file names (stitches)Methods:
mask_image(): Open interactive mask editorcheck_spot(): Interactive frame viewerplot_data(): Plot reflectivity datawrite_csv(): Export to CSV fileswrite_parquet(): Export to Parquet fileserr_prop_mult(lhs, lhs_err, rhs, rhs_err): Error propagation for multiplicationerr_prop_div(lhs, lhs_err, rhs, rhs_err): Error propagation for divisionweighted_mean(values, weights): Weighted mean using inverse-variance weightsweighted_std(weights): Weighted standard deviation# Clone repository
git clone https://github.com/WSU-Carbon-Lab/pyref.git
cd pyref
# Install in development mode
pip install -e .
# Or use uv
uv pip install -e .
pyref/
├── python/pyref/ # Python package
│ ├── loader.py # Main PrsoxrLoader class
│ ├── image.py # Image processing functions
│ ├── masking.py # Interactive masking tools
│ ├── io/ # I/O functions
│ ├── fitting/ # Reflectivity fitting
│ └── utils/ # Utility functions
├── src/ # Rust backend
│ ├── loader.rs # FITS file reading
│ ├── io.rs # Image processing
│ └── lib.rs # Polars plugins
└── tests/ # Test suite
Contributions are welcome! Please see the contributing guidelines for details.
If you use PyRef in your research, please cite:
[Add citation information here]
[Add license information here]
Developed for processing data from beamline 11.0.1.2 at the Advanced Light Source, Lawrence Berkeley National Laboratory.