Skip to content

Xact 625i XRF Analyzer

The Xact 625i is a continuous X-ray fluorescence (XRF) analyzer for real-time elemental analysis of particulate matter.

Instrument Overview

Specification Value
Manufacturer Cooper Environmental Services
Model Xact 625i
Measurement Elemental composition (ng/m3)
Time Resolution Hourly
Elements 72 elements (Mg to U)

Data Format

  • File format: CSV
  • Time format: MM/DD/YYYY HH:MM:SS
  • Data structure: Element concentrations with uncertainties + environmental parameters

Column Structure

The raw data file contains:

  1. Element concentrations: Element AtomicNumber (ng/m3) (e.g., Mg 12 (ng/m3))
  2. Uncertainties: Element Uncert (ng/m3) (e.g., Mg uncert (ng/m3))
  3. Environmental parameters: Temperature, pressure, flow, RH, etc.
  4. Alarm codes: Instrument status indicators

Supported Elements

ELEMENTS = [
    'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe',
    'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo',
    'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Cs', 'Ba', 'La', 'Ce',
    'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu',
    'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Th', 'Pa', 'U'
]

Usage

from datetime import datetime
from pathlib import Path
from AeroViz import RawDataReader

# Read Xact data
data = RawDataReader(
    instrument='Xact',
    path=Path('/path/to/xact/data'),
    start=datetime(2024, 1, 1),
    end=datetime(2024, 12, 31),
    mean_freq='1h'
)

# View available elements
print(data.columns.tolist())
# ['Mg', 'Al', 'Si', 'S', 'K', 'Ca', 'Fe', 'Pb', ...]

Quality Control

QC Rules Applied

Rule Condition Description
Instrument Error ALARM code 100-110 Instrument malfunction detected
Upscale Warning ALARM code 200-203 Concentration exceeds measurement range
Invalid Value < 0 or > 100,000 ng/m3 Concentration outside valid range
High Uncertainty Uncertainty > 50% of value Measurement reliability concern

Alarm Codes

Error Codes (100-110) - Invalidate data:

Code Description
100 X-ray Voltage Error
101 X-ray Current Error
102 Tube Temperature Error
103 Enclosure Temperature Error
104 Tape Error
105 Pump Error
106 Filter Wheel Error
107 Dynamic Rod Error
108 Nozzle Error
109 Energy Calibration Error
110 Software Error

Warning Codes (200-203) - Upscale warnings:

Code Description
200 Upscale Cr Warning
201 Upscale Pb Warning
202 Upscale Cd Warning
203 Upscale Nb Warning

Output Data

Element Columns

Each detected element has two columns: - Element - Concentration in ng/m3 (e.g., Pb, Fe, S) - Element_uncert - Measurement uncertainty in ng/m3

Environmental Columns

Column Description Unit
AT Ambient Temperature C
SAMPLE_T Sample Temperature C
BP Barometric Pressure mmHg
TAPE Tape Pressure mmHg
FLOW_25 Flow at 25C slpm
FLOW_ACT Actual Flow lpm
FLOW_STD Standard Flow slpm
VOLUME Sample Volume L
TUBE_T X-ray Tube Temperature C
ENCLOSURE_T Enclosure Temperature C
FILAMENT_V Filament Voltage V
SDD_T SDD Temperature C
DPP_T DPP Temperature C
RH Relative Humidity %
WIND Wind Speed m/s
WIND_DIR Wind Direction deg
SAMPLE_TIME Sample Time min
ALARM Alarm Code -

Example Analysis

from AeroViz import RawDataReader
from pathlib import Path
from datetime import datetime

# Read data
xact = RawDataReader(
    instrument='Xact',
    path=Path('./data/xact'),
    start=datetime(2024, 1, 1),
    end=datetime(2024, 3, 31)
)

# Calculate crustal elements ratio
soil_elements = ['Al', 'Si', 'Ca', 'Fe', 'Ti']
xact['Soil'] = (
    2.20 * xact['Al'] +
    2.49 * xact['Si'] +
    1.63 * xact['Ca'] +
    2.42 * xact['Fe'] +
    1.94 * xact['Ti']
) / 1000  # Convert to ug/m3

# Heavy metals analysis
heavy_metals = ['Pb', 'Cd', 'As', 'Cr', 'Ni']
print(xact[heavy_metals].describe())

Notes

  • Data is automatically rounded to hourly resolution
  • Duplicate timestamps are removed
  • Non-numeric values are coerced to NaN
  • QC flags are stored in QC_Flag column during processing
  • Units are ng/m3 (nanograms per cubic meter)

API Reference

AeroViz.rawDataReader.script.Xact.Reader

Reader(path: Path | str, reset: bool | str = False, qc: bool | str = True, **kwargs)

Bases: AbstractReader

Xact 625i XRF Analyzer Data Reader

A specialized reader for Xact 625i continuous XRF analyzer data files, which measure elemental composition of particulate matter.

Attributes

nam class-attribute instance-attribute
nam = 'Xact'
_sample_time class-attribute instance-attribute
_sample_time = None
ELEMENTS class-attribute instance-attribute
ELEMENTS = ['Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Th', 'Pa', 'U']
ENV_COLUMNS class-attribute instance-attribute
ENV_COLUMNS = ['AT', 'SAMPLE_T', 'BP', 'TAPE', 'FLOW_25', 'FLOW_ACT', 'FLOW_STD', 'VOLUME', 'TUBE_T', 'ENCLOSURE_T', 'FILAMENT_V', 'SDD_T', 'DPP_T', 'RH', 'WIND', 'WIND_DIR', 'SAMPLE_TIME', 'ALARM', 'SAMPLE_TYPE']
ERROR_CODES class-attribute instance-attribute
ERROR_CODES = {100: 'Xray Voltage Error', 101: 'Xray Current Error', 102: 'Tube Temperature Error', 103: 'Enclosure Temperature Error', 104: 'Tape Error', 105: 'Pump Error', 106: 'Filter Wheel Error', 107: 'Dynamic Rod Error', 108: 'Nozzle Error', 109: 'Energy Calibration Error', 110: 'Software Error'}
WARNING_CODES class-attribute instance-attribute
WARNING_CODES = {200: 'Upscale Cr Warning', 201: 'Upscale Pb Warning', 202: 'Upscale Cd Warning', 203: 'Upscale Nb Warning'}
MANUAL_MDL class-attribute instance-attribute
MANUAL_MDL = {'Al': {15: 840, 30: 290, 60: 100, 120: 35, 180: 19, 240: 12}, 'Si': {15: 150, 30: 51, 60: 17.8, 120: 6.3, 180: 3.4, 240: 2.2}, 'P': {15: 44, 30: 15, 60: 5.2, 120: 1.8, 180: 0.99, 240: 0.64}, 'S': {15: 26, 30: 9.1, 60: 3.16, 120: 1.1, 180: 0.6, 240: 0.39}, 'Cl': {15: 15, 30: 5, 60: 1.73, 120: 0.61, 180: 0.33, 240: 0.21}, 'K': {15: 9.8, 30: 3.4, 60: 1.17, 120: 0.41, 180: 0.22, 240: 0.14}, 'Ca': {15: 2.5, 30: 0.86, 60: 0.3, 120: 0.1, 180: 0.057, 240: 0.037}, 'Ti': {15: 1.3, 30: 0.46, 60: 0.16, 120: 0.056, 180: 0.03, 240: 0.02}, 'V': {15: 1, 30: 0.34, 60: 0.12, 120: 0.042, 180: 0.023, 240: 0.015}, 'Cr': {15: 0.97, 30: 0.33, 60: 0.12, 120: 0.041, 180: 0.022, 240: 0.014}, 'Mn': {15: 1.2, 30: 0.41, 60: 0.14, 120: 0.05, 180: 0.027, 240: 0.018}, 'Fe': {15: 1.4, 30: 0.49, 60: 0.17, 120: 0.061, 180: 0.033, 240: 0.021}, 'Co': {15: 1.1, 30: 0.39, 60: 0.14, 120: 0.049, 180: 0.026, 240: 0.017}, 'Ni': {15: 0.78, 30: 0.27, 60: 0.1, 120: 0.034, 180: 0.018, 240: 0.012}, 'Cu': {15: 0.65, 30: 0.23, 60: 0.079, 120: 0.028, 180: 0.015, 240: 0.01}, 'Zn': {15: 0.55, 30: 0.19, 60: 0.067, 120: 0.023, 180: 0.013, 240: 0.008}, 'As': {15: 0.52, 30: 0.18, 60: 0.063, 120: 0.022, 180: 0.012, 240: 0.008}, 'Se': {15: 0.66, 30: 0.23, 60: 0.081, 120: 0.029, 180: 0.016, 240: 0.01}, 'Br': {15: 0.85, 30: 0.3, 60: 0.1, 120: 0.037, 180: 0.02, 240: 0.013}, 'Ag': {15: 16, 30: 5.5, 60: 1.9, 120: 0.68, 180: 0.37, 240: 0.24}, 'Cd': {15: 21, 30: 7.2, 60: 2.5, 120: 0.89, 180: 0.48, 240: 0.31}, 'In': {15: 26, 30: 8.9, 60: 3.1, 120: 1.1, 180: 0.6, 240: 0.39}, 'Sn': {15: 33, 30: 12, 60: 4.1, 120: 1.4, 180: 0.78, 240: 0.51}, 'Sb': {15: 42, 30: 15, 60: 5.2, 120: 1.8, 180: 0.99, 240: 0.64}, 'Ba': {15: 3.3, 30: 1.1, 60: 0.39, 120: 0.14, 180: 0.074, 240: 0.048}, 'Hg': {15: 0.99, 30: 0.35, 60: 0.12, 120: 0.043, 180: 0.023, 240: 0.015}, 'Tl': {15: 0.95, 30: 0.33, 60: 0.12, 120: 0.041, 180: 0.022, 240: 0.014}, 'Pb': {15: 1, 30: 0.36, 60: 0.13, 120: 0.045, 180: 0.024, 240: 0.016}, 'Bi': {15: 1.1, 30: 0.37, 60: 0.13, 120: 0.046, 180: 0.025, 240: 0.016}}
MANUAL_SAMPLE_TIMES class-attribute instance-attribute
MANUAL_SAMPLE_TIMES = (15, 30, 60, 120, 180, 240)
DETECTION_SIGMA class-attribute instance-attribute
DETECTION_SIGMA = 3
QUANTIFICATION_SIGMA class-attribute instance-attribute
QUANTIFICATION_SIGMA = 10
RELIABLE_FRACTION class-attribute instance-attribute
RELIABLE_FRACTION = 0.75
MIN_VALUE class-attribute instance-attribute
MIN_VALUE = 0
MAX_VALUE class-attribute instance-attribute
MAX_VALUE = 100000
INTERNAL_STD_ELEMENT class-attribute instance-attribute
INTERNAL_STD_ELEMENT = 'Nb'
INTERNAL_STD_TOLERANCE class-attribute instance-attribute
INTERNAL_STD_TOLERANCE = 0.2
MDL property
MDL: dict

Per-element 1σ detection limits (ng/m³) for this run's sample time.

The manual's table wins where it has an entry, because it is the vendor's own specification and it varies with sample time — a 15-minute sample has ~8× the detection limit of a 60-minute one, so a single fixed number is only right for one configuration. config/supported_instruments.py supplies the rest (elements CES publishes no limit for); those are marked as unverified by element_reliability.

Used for the per-element below-MDL diagnostic, never as a row-level flag — see log_below_mdl.

Methods:

manual_mdl
manual_mdl(sample_time: float | None = None) -> dict

The manual's detection limits at sample_time minutes.

Falls back to the sample time seen in the data (SAMPLE_TIME), then to 60 minutes. A time between two tabulated ones takes the longer column, which is the conservative direction: a shorter sample cannot have a lower detection limit than the manual quotes for a longer one.

element_reliability
element_reliability(df) -> DataFrame

Classify each element by how well the instrument actually measured it.

The {element}_uncert columns are 1σ uncertainties on the same basis as the manual's detection limits, so Currie's criteria apply directly: a value is detected at >= 3σ and quantifiable at >= 10σ. An element is classed by the fraction of the run's samples reaching each:

quantitative usable as a time series semi-quantitative detected, but the magnitudes carry large error below-detection reported, but indistinguishable from noise

Whether the manual specifies the element is reported separately, in manual_mdl / published_limit. The two axes are independent and both matter: an element measured at 100 sigma with no published detection limit (the internal standard, Nb) is empirically solid but has no vendor accuracy to appeal to, while one that is in the manual and still sits below detection is simply not present at this site. Collapsing them would hide one or the other.

This is per element, not per row, and that is the point: on a 45-column XRF frame the interesting question is never "is this row bad" but "which of these columns can I use". A row-level rule over all elements fires on ~100 % of rows (measured), which says nothing.

Returns a DataFrame indexed by element, worst first.

_raw_reader
_raw_reader(file)

Read and parse raw Xact 625i XRF data files.

Returns all columns from the raw file. Column selection is deferred to _QC() and _process() stages.

_QC
_QC(_df)

Perform quality control on Xact XRF data.

QC Rules Applied
  1. Calibration Mode : SAMPLE_TYPE != 1 indicates zero calibration
  2. Instrument Error : ALARM code 100-110 indicates instrument error
  3. Upscale Warning : ALARM code 200-203 indicates upscale warning
  4. Invalid Value : Element concentration outside valid range (0-100000 ng/m3)
  5. Internal Std Drift : Nb internal standard deviates ±20% from median

Detection limits (MDL, from the config) are reported per element in the log rather than flagged: with 45 elements, "any element below its MDL" is true for practically every row, and a non-Valid flag NaNs the whole row downstream — flagging it would delete the dataset. See log_below_mdl.

_report_element_reliability
_report_element_reliability(reliability) -> None

Log the per-element verdicts and write them next to the other outputs.

The sidecar is the actionable artifact: it tells you which of the 45 columns to build a time series from, which to treat as indicative, and which are noise the instrument reports because it was configured to.

decode_alarm
decode_alarm(alarm_code)

Decode ALARM code to human-readable message.

Parameters:

Name Type Description Default
alarm_code int

The ALARM code from the Xact data

required

Returns:

Type Description
str

Human-readable description of the alarm