SAXS/WAXS module (CHAP.saxswaxs)
The CHAP.saxswaxs module contains processing tools unique to SAXS/WAXS processing. This document describes how to use them in a pipeline configuration YAML file so that SAXS/WAXS workflows can be run from the terminal on the CHESS Linux system.
Workflow description
General overview
Setup a Zarr container for the complete raw and processed dataset.
First, we set up a container to hold the raw and processed datasets before any processing begins. This step needs the following information to allocate an appropriate amount of space:
Dataset size
Detector size(s)
Integrated data size(s)
In practice, you should prepare two supplementary configuration files in addition to the parameters for the tools involved in this step: one for a
MapConfig, object, another for aPyFaiIntegrationProcessorConfigobject. This step also sets the number of chunks for each data array in the container. Selecting the right number of chunks is important for optimizing performance during the next step.Example pipeline configuration
config: root: . log_level: debug setup: # Step 1: read in configuration files - common.YAMLReader: filename: map_config.yaml schema: common.models.map.MapConfig - common.YAMLReader: filename: pyfai_integration_processor_config.yaml schema: common.models.integration.PyfaiIntegrationConfig # Step 2: set up Zarr container for processed data based on # configurations provided from Step 1 - saxswaxs.SetupProcessor: dataset_shape: - 50 dataset_chunks: - 10 detectors: - id: PIL9 shape: - 407 - 487 - id: PIL11 shape: - 407 - 487 # Step 3: Write the Zarr container to file - common.ZarrWriter: filename: data.zarr force_overwrite: true
Fill container with processed data
Next, we read in the raw data and perform the configured integration(s). To get the best performance for large datasets, this step should be performed across multiple pipeline processes runnning in parallel. Each process will handle reading, processing, and writing just one part of the whole dataset – exactly one chunk of each array in the container set up previously, to be precise. To fill the container from the previous step with processed data, each parallel process will need to know the following information:
The spec file for the scan whose data will be processed
The scan number for the scan whose data will be processed
Parameters indicating a specific slice of the scan to process
Example pipeline configuration
config: root: . log_level: debug update: # Step 1: read in configuration files - common.YAMLReader: filename: map_config.yaml schema: common.models.map.MapConfig - common.YAMLReader: filename: pyfai_integration_processor_config.yaml schema: common.models.integration.PyfaiIntegrationConfig # Step 2: read and process the data - saxswaxs.UpdateValuesProcessor: spec_file: spec_file scan_number: 1 detectors: - id: PIL9 shape: - 407 - 487 - id: PIL11 shape: - 407 - 487 idx_slice: start: 0 stop: 10 step: 1 # Step 3: write processed data to the container set up earlier - common.ZarrValuesWriter: filename: data.zarr
Perform final user adjustments
These “final adjustments” are all optional, but they can include the following:
converting from .zarr format to NeXus format
performing flux, absorption, and / or background corrections on integrated data
inserting links to coordinate axes next to processed data arrays
reshaping the dataset from an “unstructured” to a “structured” representation
Example format conversion pipeline configuration
config: root: . log_level: debug convert: # One tool only: ZarrToNexusProcessor takes care of reading old # zarr file and writing new NeXus file, too - common.ZarrToNexusProcessor: zarr_filename: data.zarr nexus_filename: data.nxs
Example flux correction pipeline configuration
config: root: . log_level: debug flux_correct: # Step 1: Read in uncorrected data and flux data to perform flux correction - common.NexusReader: filename: data.nxs nxpath: waxs_azimuthal/data/I nxmemory: 100000 name: intensity - common.NexusReader: filename: data.nxs nxpath: spec_file_012/scalar_data/presample_intensity nxmemory: 100000 name: presample_intensity # Step 2: Perform flux correction calculations - saxswaxs.FluxCorrectionProcessor: nxprocess: true presample_intensity_reference_rate: 50000 # Write flux corrected results back to original file as their own # new NXprocess group - common.NexusWriter: filename: data.nxs nxpath: /waxs_azimuthal_flux_corrected force_overwrite: true
Example coordinate linking pipeline configuration
config: root: . log_level: debug linkdims: # Step 1: Read in the Nexus file in which we're adding linked data fields - common.NexusReader: filename: data.nxs mode: rw # Step 2: Create new linked data arrays from all the groups listed # in `link_from` to all the existing data arrays listed in # `link_to` - common.NexusMakeLinkProcessor: link_from: - waxs_azimuthal/data - waxs_cake/data - waxs_radial/data - waxs_azimuthal_flux_abs_bg_corrected/data - waxs_azimuthal_flux_abs_corrected/data - waxs_azimuthal_flux_corrected/data - waxs_cake_flux_abs_bg_corrected/data - waxs_cake_flux_abs_corrected/data - waxs_cake_flux_corrected/data - waxs_radial_flux_abs_bg_corrected/data - waxs_radial_flux_abs_corrected/data - waxs_radial_flux_corrected/data link_to: - spec_file_012/independent_dimensions/samx - spec_file_012/independent_dimensions/samzmakelink # No writing tool necessary, `common.NexusMakeLinkProcessor` will # modilfy the file in place as long as it's read in with `mode: # rw`.
Example unstructured-to-structured pipeline configuration
config: root: . log_level: debug struct: # Step 1: Read in the data arrays to structure, and all their # coordinate axes arrays too - common.NexusReader: filename: data.nxs nxpath: spec_file_012/independent_dimensions/samx name: samx - common.NexusReader: filename: data.nxs nxpath: spec_file_012/independent_dimensions/samz name: samz - common.NexusReader: filename: data.nxs nxpath: waxs_azimuthal/data/q_A^-1 name: waxs_azimuthal_q - common.NexusReader: filename: data.nxs nxpath: waxs_radial/data/chi_deg name: waxs_radial_chi - common.NexusReader: filename: data.nxs nxpath: waxs_azimuthal/data/I name: waxs_azimuthal_I - common.NexusReader: filename: data.nxs nxpath: waxs_radial/data/I name: waxs_radial_I # Step 2: restructure signal arrays in a single shared NXdata object - saxswaxs.UnstructuredToStructuredProcessor: name: structured_data fields: - name: samx type: axis - name: samz type: axis - name: waxs_azimuthal_q type: axis - name: waxs_radial_chi type: axis - name: waxs_azimuthal_I axes: [samx, samz, waxs_azimuthal_q] type: signal - name: waxs_radial_I axes: [samx, samz, waxs_radial_chi] type: signal # Step 3: Write the structured data to a new group in the original NeXus file - common.NexusWriter: filename: data.nxs nxpath: /structured force_overwrite: true
Output data formats
This is a basic guide to help find the most essential datasets produced in SAXS/WAXS analysis. It is not an exhaustive specification of every last bit of data / metadata that one can possibly find in the data formats in question.
The
CHAP.saxswaxs.processor.SetupProcessordetermines a hierarchical tree structure to contain processed SAXSWAXS data.This tree structure is returned in Zarr file format to start, but it may be converted:
to the same format of NeXus file as that produced by saxswaxsworkflow using
CHAP.saxswaxs.processor.NewZarrToOldNexusProcessorto an indentically structured NeXus file in a reversible operation using
CHAP.common.processor.ZarrToNexusProcessor(reversible withCHAP.common.processor.NexusToZarrProcessor).
CHAP.saxswaxs.processor.SetupProcessor output structure
Root level
The structure of the Zarr container is related to the configuration fields provided to
CHAP.saxswaxs.processor.SetupProcessor:map_configdefines the parameters needed to locate and read in a raw CHESS datasetpyfai_configdefines one or more pyFAI integrations to perform on 2D detector data (raw or corrected)correction_configdefines any number of standard SAXS/WAXS corrections to perform (on raw or integrated data)fit_configdefines any number of fits to perform on processed 1D data (integrated and corrected, or just integrated)map_configand each individual integration / correction / fit defined inpyfai_config/correction_config/fit_configwill correspond to a root-level group of the results container.The name of the group is determined by the configuration’s
titlefield.For instance, suppose
CHAP.saxswaxs.processor.SetupProcessoris run with the following configurations:map_config: title: my_dataset <...> pyfai_config: - title: saxs_radial <...> correction_config: - title: saxs_radial_corrected <...> fit_config: - title: fit_saxs_radial <...> - title: fit_saxs_radial_corrected <...>
…then the resulting tree hierarchy will be:
/ # root level group my_dataset/ # raw data group <...> saxs_radial/ # integrated data group <...> saxs_radial_corrected/ # corrected data group <...> fit_saxs_radial/ # fit data group <...> fit_saxs_radial_corrected/ # fit data group <...>
NB: the types and names of the root level groups returned by
CHAP.saxswaxs.processor.SetupProcessorare the same as those in the NeXus file produced bysaxswaxsworkflow.Descriptions of the trees found underneath each root-level group are below.
map_config sub-tree
This structure is generic, not specific to
CHAP.saxswaxs.The tree underneath looks like:
<map_config.title>/ map_config # map configuration serialized as JSON string detector_config # detector configuration serialized as JSON string spec_scans/ # metadata about each SPEC scan in the map data/ # group containing data arrays of raw detector frames, one dataset per detector <sample_name>/ # sample metadata independent_dimensions/ # group containing data arrays of the map's coordinate values, one dataset per dimension scalar_data/ # group containing data arrays of scalar-valued data, one dataset per scalar readback channel # (includes beam intensity monitors, dwell time, etc.) detector_ids # list of detector identifiers
Dimensions that are constant across the map are demoted from
independent_dimensions/intoscalar_data/.
pyfai_config sub-trees
There is one root-level group per integration defined in
pyfai_config(identified by each integration’stitle/namefield).The tree underneath looks like:
<integration_name>/ data/ I # integrated intensity array <radial_or_azimuthal_coord> # coordinate axis array (e.g. q values, chi angles) [<azimuthal_or_radial_coord>] # other coordinate axis array (if the integration is 2D)
The coordinate array name and units depend on the integration method:
integrate1d: one radial coordinate (e.g.q_A^-1)integrate2d: one azimuthal coordinate + one radial coordinateintegrate_radial: one azimuthal coordinate (e.g.chi_deg)
The
data/group also carries links back to the map coordinate arrays in themap_configgroup.
correction_config sub-trees
There is one root-level group per correction defined in
correction_config(identified by each correction’stitle/namefield).The tree underneath looks like:
<correction_name>/ data/ I_corrected # corrected intensity (single upstream source) # — or, if multiple upstream sources are named — I_corrected_<source> # one corrected-intensity array per source # — when a background scan is configured — I_background # averaged background intensity frame background_presample_intensity background_postsample_intensity
The
data/group carries links back to the map coordinate arrays in themap_configgroup and to the coordinate arrays of the upstream integration.
fit_config sub-trees
There is one root-level group per fit defined in
fit_config(identified by each fit’stitle/namefield).The tree underneath looks like:
<fit_name>/ data/ best_fit # best-fit curve at each scan point residual # residual (data minus best fit) at each scan point redchi # reduced chi-squared at each scan point num_func_eval # number of function evaluations at each scan point success # whether the fit converged at each scan point components/ <model_name>/ # one sub-group per model component (e.g. "gaussian", "linear") parameters/ # one sub-group per fit parameter <param_name>/ value # fitted value at each scan point error # uncertainty at each scan point initial # initial value min, max # bounds vary # whether the parameter was free expression # constraint expression, if any data/ best_fit # component's contribution to the best-fit curve
The
data/group carries links back to the map coordinate arrays in themap_configgroup and to the coordinate arrays of the upstream integration or correction.
CHAP.saxswaxs.processor.NewZarrToOldNexusProcessor output structure
Converts a Zarr file created by
CHAP.saxswaxs.processor.SetupProcessorinto anNXrootobject whose layout mimics the NeXus files produced by saxswaxsworkflow and is compatible with the fitting notebook tools in the same repository.The output NeXus file contains two kinds of top-level groups:
One
NXentrycontaining the map’s coordinate values and scalar-valued dataOne
NXprocessper integration or correction, containing the processed data
All data arrays in this structure are reshaped to be the same shape of the map (as opposed to flattened along the map’s dimenisions, as they are in the Zarr format above).
map_config sub-tree
This structure is nearly identical to the other
map_configsub-tree defined above, but with some important differences:the group containing arrays of raw detector data is not present
coordinate value arrays contain only the unique, sorted values along each dimension (which may or may not be smaller than the length of the full dataset)
pyfai_config sub-trees
There is one
NXprocessgroup per integration in the Zarr file.The tree underneath looks like:
<integration_name>/ data/ I # integrated intensity array <radial_or_azimuthal_coord> # intensity coordinate axis array (e.g. q values, chi angles) [<azimuthal_or_radial_coord>] # other intensity coordinate axis array (if the integration is 2D) <dim_label> # map coordinate axis array (links to /<map_title>/independent_dimensions/<dim_label>) [<other_dim_label>] # more links to /<map_title>/independent_dimensions/<other_dim_label> if the map has more than one independent_dimension
Each
data/group carriesaxesand*_indicesattributes so that every axis — both map dimensions and signal coordinate axes — is labelled.
correction_config sub-trees
There is one
NXprocessgroup per correction in the Zarr file.The tree underneath looks like:
<correction_name>/ data/ I_corrected # corrected intensity array I_uncorrected # uncorrected intensity source array (links to /<uncorrected_data_name>/data/I) <radial_or_azimuthal_coord> # intensity coordinate axis array (e.g. q values, chi angles) [<azimuthal_or_radial_coord>] # other intensity coordinate axis array (if the integration is 2D) <dim_label> # map coordinate axis array (links to /<map_title>/independent_dimensions/<dim_label>) [<other_dim_label>] # more links to /<map_title>/independent_dimensions/<other_dim_label> if the map has more than one independent_dimension
Each
data/group carriesaxesand*_indicesattributes so that every axis — both map dimensions and signal coordinate axes — is labelled.
Optimizing performance
Guide on selecting appropriate values for dataset_chunks and running multiple update jobs in parallel
Notes on corrections calculations
There are currently three convenience tools available for performing corrections: saxswaxs.FluxCorrectionProcessor, saxswaxs.FluxAbsorptionCorrectionProcessor, and saxswaxs.FluxAbsorptionBackrgroundCorrectionProcessor. The exact calculations that each ones performs are detailed below.
Definitions
\(I_{uncorrected}\) is the uncorrected, integrated dataset. When reading this data as input for a
saxswaxs.*CorrectionProcessor, usename: intensity.\(I_{incident}\) is the presample intensity dataset. When reading this data as input for a
saxswaxs.*CorrectionProcessor, usename: presample_intensity.\(I_{transmitted}\) is the postsample intensity dataset. When reading this data as input for a
saxswaxs.*CorrectionProcessor, usename: postsample_intensity.\(t_{dwell}\) is the scan’s dwell time at each point in the map configuration to which the correction tool is applied. When reading this data as input for a
saxswaxs.*CorrectionProcessor, usename: dwell_time_actual.\(I_{background}\) is the background integrated detector data. When reading this data as input for a
saxswaxs.*CorrectionProcessor, usename: background_intensity.\(I_{incident, background}\) is the background presample intensity. When reading this data as input for a
saxswaxs.*CorrectionProcessor, usename: background_presample_intensity.\(I_{transmitted, background}\) is the background postsample intensity. When reading this data as input for a
saxswaxs.*CorrectionProcessor, usename: background_postsample_intensity.\(\phi_{reference}\) is
presample_intensity_reference_ratein the correction tool’s own parameters. However, ifpresample_intensity_reference_ratewas not given in the tool’s configuration, a value for this quantity will be calculated with:
\(T = \frac{I_{transmitted}}{I_{incident}} / \frac{I_{transmitted, background}}{I_{incident, background}}\)
\(t\) is the sample thickness in \(cm\). This quantity only appears in
saxswaxs.FluxAbsorptionBackgroundCorrectionProcessor. The value of this parameter can be set in one of two ways:Use the
sample_thickness_cmparameter ofsaxswaxs.FluxAbsorptionBackgroundCorrectionProcessor, ORUse the
sample_mu_inv_cm(”\(\mu\)”) parameter ofsaxswaxs.FluxAbsorptionBackgroundCorrectionProcessor. \(\mu\) is known as the the linear attenuation coefficient of the sample, and is related to the mass attenuation coefficient, \((\mu/\rho)\) [cm\(^2\)/g], by the sample density. Tabulated values of the \((\mu/\rho)(E)\) for each element are available here: https://physics.nist.gov/PhysRefData/XrayMassCoef/tab3.html. For our purposes:
NB: When using
saxswaxs.FluxAbsorptionBackgroundCorrectionProcessor, do not use both thesample_thickness_cmandsample_mu_inv_cmparameters at the same time. Specifying both parameters makes the definition of the sample thickness ambiguous. The Processor will raise an error if both parameters are supplied.\(C_f\) is the scalar factor for putting flux, absorption, background, and thickness corrected data into absolute intensity units. Taken directly from the
absolute_intensity_scalarparameter from the correction tool config file.
saxswaxs.FluxCorrectionProcessor
saxswaxs.FluxAbsorptionCorrectionProcessor
saxswaxs.FluxAbsoprtionBackgroundCorrectionProcessor
This tool functions differently depending on what parameters are provided:
If neither
sample_thickness_cmorsample_mu_inv_cmare provided:
If either
sample_thickness_cmorsample_mu_inv_cmare provided:
Running a workflow
At CHESS
Use environment: source /nfs/chess/sw/miniforge3_chap/bin/activate; conda activate CHAP_saxswaxs
And/or add this to your ~/.bashrc: alias CHAP_saxswaxs='/nfs/chess/sw/miniforge3_chap/envs/CHAP_saxswaxs/bin/CHAP'
Batch jobs
Drop in & replace: PyfaiIntegrationProcessorConfig idx_slice on update jobs will need adjustments to drop in & replace: MapConfig Script for constructuing and / or sending off multiple update jobs in parallel