Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fyn-reduce

Tools for Fluorescence Yield (FY) NEXAFS data reduction collected at Beamline 11.0.1.2 (RSOXS) at the Advanced Light Source (ALS).


Installation

pip install -e .

For development dependencies (linting, type checking, tests):

pip install -e ".[dev,test]"

Overview

A typical reduction workflow has three steps:

  1. Load an I0 reference scan from an SMS text file
  2. Load a sample as a series of FITS image files
  3. Define an ROI and call reduce() to get the normalized FY spectrum

Loading an I0 Reference Scan

The I0 reference is a Single Motor Scan (SMS) text file collected without a sample in the beam. Load it with load_sms_file:

from pathlib import Path
from fyn_reduce import load_sms_file

i0 = load_sms_file(Path("/data/i0_scan.txt"))

The returned DataFrame contains one row per energy point. The columns used during reduction are 'Beamline Energy', 'Photodiode', and 'AI 3 Izero'.


Loading a Sample

Sample data is stored as a series of FITS images, one per energy point plus any dark frames. Pass all files for a single sample to FYLoader:

from pathlib import Path
from fyn_reduce import FYLoader, load_sms_file

i0 = load_sms_file(Path("/data/i0_scan.txt"))

files = sorted(Path("/data/sampleA_001/").glob("*.fits"))

loader = FYLoader(files=files, i0=i0, name="sampleA_001")

Files are loaded automatically on construction (autoload=True by default). To defer loading:

loader = FYLoader(files=files, i0=i0, name="sampleA_001", autoload=False)
# ... do other work ...
loader.load()

Dark frames

Dark frames (CCD shutter closed) are identified automatically from the CCD Camera Shutter Inhibit flag in each FITS header and subtracted during reduction. If you have a separately collected dark image, pass it as external_dark:

from fyn_reduce import load_fits_file

dark_image, _ = load_fits_file(Path("/data/dark_001.fits"))
spectrum = loader.reduce(external_dark=dark_image)

When external_dark is provided the embedded dark frames in the series are ignored.


Defining a Region of Interest (ROI)

The ROI controls which pixels on the detector are summed to compute the FY signal. It is defined as a center point and a size in detector pixel coordinates.

loader.roi_center = (512, 512)   # (x, y) center pixel
loader.roi_size   = (500, 500)   # (x_range, y_range) in pixels

The defaults — center (512, 512), size (500, 500) — cover most of a 1024 × 1024 detector. A smaller ROI isolates signal from background:

# Tight ROI around the fluorescence spot
loader.roi_center = (480, 530)
loader.roi_size   = (200, 200)

The ROI is always square. The actual pixel slice applied is:

x: [ roi_center[0] - roi_size[0]/2,  roi_center[0] + roi_size[0]/2 ]
y: [ roi_center[1] - roi_size[1]/2,  roi_center[1] + roi_size[1]/2 ]

To find good ROI coordinates, inspect a representative frame:

from fyn_reduce import load_fits_file
import matplotlib.pyplot as plt

image, meta = load_fits_file(files[0])

fig, ax = plt.subplots()
ax.imshow(image, origin="lower", cmap="viridis")
plt.show()

Use the cursor readout in the matplotlib window to identify the center and extent of the fluorescence spot.


Dezinger Parameters

Hot pixels (cosmic rays, detector artifacts) are removed before the ROI sum using a median filter. Two parameters control sensitivity:

loader.diz_threshold = 10.0   # ratio above which a pixel is replaced
loader.diz_size      = 3      # median filter kernel size (pixels)

A lower diz_threshold removes more pixels; a higher value is more conservative. The default of 10.0 is suitable for most data.


Running the Reduction

Call reduce() after setting the ROI and dezinger parameters:

spectrum = loader.reduce()

The result is a pandas.DataFrame with three columns:

Column Description
Energy Beamline energy (eV)
FY Normalized fluorescence yield (arb. units)
FY_err Poisson error on FY (same units)

Energy offset

If the beamline energy axis needs a calibration correction:

loader.energy_offset = 0.3   # eV; added to every frame energy before reduction
spectrum = loader.reduce()

Saving Results

Save as a tab-delimited .dat file with a lowercase header row:

spectrum.rename(columns={"Energy": "energy", "FY": "fy", "FY_err": "fy_err"}) \
        .to_csv("sampleA_001.dat", sep="\t", index=False)

Complete Example

from pathlib import Path
import matplotlib.pyplot as plt
from fyn_reduce import FYLoader, load_sms_file

data_dir = Path("/data/20260507")

# Load I0 reference
i0 = load_sms_file(data_dir / "i0_scan.txt")

# Load sample
files = sorted((data_dir / "sampleA_001").glob("*.fits"))
loader = FYLoader(files=files, i0=i0, name="sampleA_001")

# Set ROI
loader.roi_center = (480, 530)
loader.roi_size   = (200, 200)

# Reduce
spectrum = loader.reduce()

# Plot
fig, ax = plt.subplots()
ax.errorbar(spectrum["Energy"], spectrum["FY"], yerr=spectrum["FY_err"], fmt="o-")
ax.set_xlabel("Beamline Energy (eV)")
ax.set_ylabel("FY (arb. units)")
ax.set_title("sampleA_001")
plt.show()

# Save
spectrum.rename(columns={"Energy": "energy", "FY": "fy", "FY_err": "fy_err"}) \
        .to_csv(data_dir / "sampleA_001.dat", sep="\t", index=False)

About

Tools for FY-Yield NEXAFS data reduction

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages