usansred package

usansred.io.read

usansred.io.read.config_from_csv(csv_path: str) → ReductionConfig[source]

Reads the reduction configuration from a CSV file.

Returns

ReductionConfig

Validated reduction configuration.

usansred.io.read.config_from_json(json_path: str) → ReductionConfig[source]

Reads the reduction configuration from a JSON file.

Returns

ReductionConfig

Validated reduction configuration.

usansred.io.read.read_config(file_path: str) → ReductionConfig[source]

Wrapper function to read configuration from different file formats.

Parameters

file_path: str

Path to the reduction configuration file.

Returns

ReductionConfig

Validated reduction configuration.

usansred.io.save

Utility functions for USANS reduction. As we add utilities, perhaps we can split them into separater modules

usansred.io.save.save_ascii(input_workspace, file_path, header=None, **saveascii)[source]

Save a workspace to an ASCII file.

Parameters

input_workspacestr or Workspace

Input workspace to be summed before saving.

file_pathstr or path-like

Destination file path for the ASCII output.

headerstr, optional

Header text to insert or replace in the output file. If provided, and it does not start with #, it is prefixed with #.

saveasciikwargs

Additional keyword arguments passed through to mantid.simpleapi.SaveAscii. If argument WriteSpectrumID is not supplied, it defaults to False.

usansred.io.save.save_summed_spectra(input_workspace, file_path, header=None)[source]

Save the summed spectra of a workspace to an ASCII file.

Parameters

input_workspacestr or Workspace

Input workspace to be summed before saving.

file_pathstr or path-like

Destination file path for the ASCII output.

headerstr, optional

Header text to insert or replace in the output file. If provided, and it does not start with #, it is prefixed with #.

usansred.models.data

class usansred.models.data.EventCounts(detector: int = 0, monitor: int = 0, transmission: int = 0)[source]

Bases: object

Event counts associated with a single scan.

detector: int = 0
monitor: int = 0
transmission: int = 0
class usansred.models.data.IQData(q: list[float] = <factory>, i: list[float] = <factory>, e: list[float] = <factory>, t: list[float] = <factory>)[source]

Bases: object

Dataclass for I-Q data associated with a single scan

I vs. Q (intensity vs. momentum transfer) is determined from the X-Y data.

as_dict() → dict[source]
e: list[float] = FieldInfo(annotation=NoneType, required=False, default_factory=list)
i: list[float] = FieldInfo(annotation=NoneType, required=False, default_factory=list)
q: list[float] = FieldInfo(annotation=NoneType, required=False, default_factory=list)
t: list[float] = FieldInfo(annotation=NoneType, required=False, default_factory=list)
class usansred.models.data.MonitorData(xy_data: XYData = <factory>, iq_data: IQData = <factory>, filepath: str = '')[source]

Bases: object

Container for monitor data associated with a single scan.

filepath: str = ''
iq_data: IQData = FieldInfo(annotation=NoneType, required=False, default_factory=IQData)
xy_data: XYData = FieldInfo(annotation=NoneType, required=False, default_factory=XYData)
class usansred.models.data.XYData(x: list[float] = <factory>, y: list[float] = <factory>, e: list[float] = <factory>, t: list[float] = <factory>)[source]

Bases: object

Dataclass for X-Y data associated with a single scan

as_dict() → dict[source]
e: list[float] = FieldInfo(annotation=NoneType, required=False, default_factory=list)
t: list[float] = FieldInfo(annotation=NoneType, required=False, default_factory=list)
x: list[float] = FieldInfo(annotation=NoneType, required=False, default_factory=list)
y: list[float] = FieldInfo(annotation=NoneType, required=False, default_factory=list)

usansred.models.reduction_config

Data model definitions for USANS reduction.

class usansred.models.reduction_config.BackgroundConfig(*, name: ~typing.Annotated[str, ~annotated_types.MinLen(min_length=1)], start_scan_num: int, num_of_scans: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], exclude: list[int] = <factory>, thickness: ~typing.Annotated[float, ~annotated_types.Gt(gt=0)])[source]

Bases: _ScanBase

Background sample configuration.

A background is a measurement without the sample container, used to correct for instrument background and ambient scattering.

measurement_type: ClassVar[MeasurementType] = 'background'
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

thickness: Annotated[float, FieldInfo(annotation=NoneType, required=True, description='Background sample thickness in cm.', metadata=[Gt(gt=0)])]
class usansred.models.reduction_config.EmptyCellConfig(*, name: ~typing.Annotated[str, ~annotated_types.MinLen(min_length=1)], start_scan_num: int, num_of_scans: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], exclude: list[int] = <factory>)[source]

Bases: _ScanBase

Configuration for an empty cell scan.

An empty cell is a measurement with a sample container present, but without any sample material. Used to help in the calculation of transmission coefficients and to correct for container scattering.

measurement_type: ClassVar[MeasurementType] = 'empty_cell'
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

thickness: ClassVar[float] = 1.0
class usansred.models.reduction_config.ReductionConfig(*, samples: Annotated[list[SampleConfig], MinLen(min_length=1)], background: BackgroundConfig | None = None, empty_cell: EmptyCellConfig | None = None, save_all_harmonics: bool = False)[source]

Bases: BaseModel

Top-level configuration for a USANS reduction run.

background: BackgroundConfig | None
empty_cell: EmptyCellConfig | None
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

samples: Annotated[list[SampleConfig], FieldInfo(annotation=NoneType, required=True, description='List of sample configurations to reduce.', metadata=[MinLen(min_length=1)])]
save_all_harmonics: bool
class usansred.models.reduction_config.SampleConfig(*, name: ~typing.Annotated[str, ~annotated_types.MinLen(min_length=1)], start_scan_num: int, num_of_scans: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)], exclude: list[int] = <factory>, thickness: ~typing.Annotated[float, ~annotated_types.Gt(gt=0)])[source]

Bases: _ScanBase

Configuration for a single sample to reduce.

measurement_type: ClassVar[MeasurementType] = 'sample'
model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

thickness: Annotated[float, FieldInfo(annotation=NoneType, required=True, description='Sample thickness in cm (numeric string or float is valid).', metadata=[Gt(gt=0)])]

usansred.reduce

class usansred.reduce.CombinedSample(**data: Any)[source]

Bases: BaseModel

Combine multiple Sample measurements at the raw (X,Y,E) scan level before converting to (Q,I,E).

Attributes

namestr

Combined sample name.

experimentExperiment

Experiment this combined sample belongs to.

thicknessfloat

Sample thickness in cm.

measurement_typeMeasurementType

Type of measurement (sample, background, or empty cell).

combined_sampleslist[Sample]

Individual Sample objects whose scans will be combined.

combined_scanslist[Scan]

Scans produced by the combination.

combine() → None[source]

Sum raw XY data from all combined samples scan-by-scan, then generate IQ data.

For each scan index, the monitor and detector XY data from every sample are accumulated. If a sample has fewer scans than others a warning is logged and it is skipped for that index. After accumulation the XY to IQ conversion is performed on the combined data and ready for reduction.

Raises

AssertionError

If combined_samples is empty or none of them contain scans.

combined_samples: list[Sample]
combined_scans: list[Scan]
experiment: Experiment
measurement_type: MeasurementType
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str
thickness: float
class usansred.reduce.Experiment(*, config_file: str, output_dir: str = '', prim_wave: float = 3.6, v_angle: float = 0.042, num_of_banks: int = 4, folder: str = '', samples: list[Sample] = <factory>, background: Sample | None = None, empty_cell: Sample | None = None)[source]

Bases: BaseModel

Experiment configuration for USANS data reduction

Attributes

config_filestr

Path to the configuration file

_configReductionConfig

Validated reduction configuration, always set after construction (private attribute)

output_dirstr | None

Output folder for reduced data, default is current folder

prim_wavefloat

Primary wavelength in Angstroms, default is 3.6

v_anglefloat

Vertical angle, default is 0.042

num_of_banksint

Number of detector banks, default is 4 (not expected to change)

folderstr

Working folder for this experiment, derived from config file path (private attribute)

sampleslist[Sample]

List of samples, populated from config file (private attribute)

backgroundSample | None

Background sample, populated from config file if specified (private attribute)

empty_cellSample | None

Empty cell sample, populated from config file if specified (private attribute)

background: Sample | None
property config: ReductionConfig

Validated reduction configuration, always set after construction.

config_file: str
dump_reduced_data()[source]

Dump reduced data to txt files.

Output files are written for samples and the background, never for the empty cell.

empty_cell: Sample | None
folder: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_context: Any) → None[source]

Post-validation initializer

num_of_banks: int
output_dir: str
prim_wave: float
reduce(output_dir: str | None = None)[source]

Reduce the USANS data

Parameters

output_dir: str | None

The result will be dumped to the output folder. If none will just use current folder

samples: list[Sample]
v_angle: float
class usansred.reduce.Sample(**data: Any)[source]

Bases: BaseModel

Container for sample information, related scans, and data reduction methods

property config
counts: EventCounts
property data

Main detector data, currently an alias for detector_data[0]

data_bg_subtracted: list[IQData]
property data_reduced: IQData | None

Reduced data, currently an alias for the first harmonic of the bg_subtracted data

Returns

IQData | None

The background-subtracted first harmonic, or None when no subtraction has been performed for this measurement.

data_scaled: list[IQData]
detector_data: list[IQData]
dump_data_to_csv(filepath: str, data: IQData | XYData, title: str | None = None)[source]

Dump IQ or XY data to a CSV file.

dump_reduced_data_to_csv(detector_data: bool = True, scaled_data: bool = True, background_subtracted_data: bool = True) → None[source]

Write this measurement’s reduced data to CSV text files in the experiment’s output directory.

Each flag enables one category of output file (all default to True; the reduction workflow in Experiment.dump_reduced_data always uses the defaults). Higher harmonics with no data are skipped with a warning, but the first harmonic is required for the detector_data and scaled_data categories.

Parameters

detector_databool

Write the stitched, monitor-normalized data, UN_<name>_det_1_unscaled.txt. With save_all_harmonics, higher harmonics go to UN_<name>_det_<n>_unscaled.txt.

scaled_databool

Write the data rescaled by analyzer solid angle, sample thickness, and transmission, UN_<name>_det_1.txt. With save_all_harmonics, higher harmonics go to UN_<name>_det_<n>.txt.

background_subtracted_databool

Write the background- (or empty-cell-) subtracted data, UN_<name>_det_1_background_subtracted.txt. With save_all_harmonics, higher harmonics go to UN_<name>_det_<n>_background_subtracted.txt. Only written when a subtraction actually occurred (is_reduced is True).

Raises

RuntimeError

If detector_data or scaled_data is requested but the first harmonic of the corresponding data is missing or empty.

exclude: list[int]
experiment: Experiment
property is_reduced: bool

Flag to indicate if the sample has been reduced

property label: str

Display label for log messages, e.g. ‘sample S115_dry’ or ‘empty cell EmptyPCell’.

measurement_type: MeasurementType
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_context: Any) → None[source]

Post-validation initializer

name: str
normalize_by_monitor() → None[source]

Normalize detector intensities by monitor counts for all scans.

property num_of_banks: int

Number of detector banks in the Experiment

num_of_scans: int
reduce() → None[source]

Reduce this measurement’s scans.

Normalize each scan by its monitor counts, stitch the scans into one rocking curve per harmonic, center the curves on the analyzer motor angle of the unscattered beam, and rescale to momentum transfer in 1/angstrom. For samples, the background (or, in its absence, the empty cell) is then subtracted from every harmonic.

rescale_data() → None[source]

Rescale reflected data by the analyzer’s solid angle acceptance, by sample thickness, and by the transmission coefficient.

rocking_curve_centering() → float[source]

Center the stitched rocking curves by fitting a Gaussian peak to the rocking curve of the first harmonic.

Notice that the q values of the stitched rocking curves are analyzer motor angles at this stage of reduction, not yet converted to Q values. The first harmonic is fit to a Gaussian over the mostly symmetric angle range [q_min, -q_min], where q_min is the minimum analyzier motor angle. It will be a negative value. The center of the fitted Gaussian represents the value of the analyzer motor angle at which the analyzer reflects neutrons that have not been scattered by the sample. It should be very close to zero.

Returns

float

Fitted first-harmonic motor-angle center.

scans: list[Scan]
property size: int

Number of detector data points

property size_reduced: int

Number of reduced data points in the first harmonic

start_scan_num: int
stitch_scans()[source]

Stitch scan data from each detector bank into per-bank intensity curves.

For each detector bank (harmonic), combine all scans in self.scans onto a single Intensity-versus-Q profile. Detector intensities and errors are expected to already be normalized by Scan.normalize_by_monitor before being added to the stitched output.

Notice that at this stage, the “Q” values are actually analyzer-motor angles, that is, detector bank iq_data.q values are analyzer-motor angles (in arcsec units).

If two or more scans contain the same “Q” value, their intensities are combined into one point using inverse-variance weighting, and the combined uncertainty is stored as the square root of the inverse summed weights.

After all scans for a bank are processed, the stitched points are sorted by Q

The method also generates log messages for the raw scan theta ranges and converted Q ranges in 1/angstrom for the first detector bank. Results are stored on self.detector_data; no value is returned.

subtract_background(background: Sample) → None[source]

Subtract background (or empty-cell) data from this sample’s data, harmonic by harmonic.

Harmonic n of the background is subtracted from harmonic n of the sample, and never from a different harmonic: rescale_data applies the order-dependent angle-to-Q factor 2 * pi * n / wavelength to sample and background alike, and rocking_curve_centering applies a single fitted motor-angle offset to every harmonic, so only same-order curves share a comparable momentum-transfer axis.

Results are stored in self.data_bg_subtracted, positionally aligned with self.data_scaled: harmonic n is entry n - 1. A harmonic that cannot be subtracted gets an empty IQData placeholder so the indices never shift.

Parameters

backgroundSample

The background or empty-cell sample to subtract. Must be processed (stitched and scaled).

Raises

RuntimeError

If the first harmonic is missing or empty for either this sample or the background.

thickness: float
transmission: float
transmitted: float
class usansred.reduce.Scan(**data: Any)[source]

Bases: BaseModel

A single scan (run) of a sample in an experiment

Attributes

numberint

Scan (run) number

countsEventCounts

Event counts object for this scan

experimentExperiment

Experiment this scan belongs to

monitor_dataMonitorData

Monitor data associated with this scan

detector_datalist[MonitorData]

Detector data associated with this scan

load_databool

Whether to load data files during initialization. Set to False when creating placeholder scans (e.g. for CombinedSample).

convert_xy_to_iq(xy_data: XYData) → IQData[source]

Convert XY data to IQ data

Directly copies x to q, y to i, and t. For error, calculates based on a Poisson-like statistical model: err = sqrt(|y - 0.5| + 0.5) which ensures a minimum value to avoid zero error for low counts.

counts: EventCounts
detector_data: list[MonitorData]
experiment: Experiment
load()[source]

Load data files for this scan.

load_data: bool
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_context: Any) → None[source]

Post-validation initializer

monitor_data: MonitorData
normalize_by_monitor() → None[source]

Normalize detector intensities by monitor counts.

Each harmonic in the scan is normalized independently, and within each harmonic, the counts collected at the detector during the time the analyzer-motor remained at a particular angle are divided by the counts collected at the monitor during such time.

property num_of_banks: int

Number of detector banks in the Experiment

number: int
read_xy_file(filepath: str) → XYData[source]

Read XY data from a file

property size: int

Number of data points

usansred.reduce.horizontal_rocking_width(order: int) → float[source]

FWHM (arcs) of the resolution function at the detector for a given reflection order

References

M. Agamalian et al., “Progress on The Time-of-Flight Ultra Small Angle Neutron Scattering Instrument at SNS”, J. Phys.: Conf. Ser. 1021 (2018) 012033.

Parameters

orderint

Positive reflection order (a.k.a. harmonic or bank)

Returns

float

Computed horizontal angular resolution.

usansred.reduce.main()[source]

Main function to run USANS data reduction

usansred.reduce.parse_args(argv: list[str] | None = None) → Namespace[source]

Parse command-line arguments for USANS data reduction.

Parameters

argvlist[str] | None, optional

Command-line arguments to parse, excluding the program name. When None, arguments are read from sys.argv.

Returns

argparse.Namespace

Parsed arguments containing the setup-file path, output directory, and deprecated logbin compatibility flag.

usansred.reduce_USANS

usansred.reduce_USANS.get_sequence_info(seq_file)[source]

Get the sequence information from local autoreduce folder json files for autoreduction

usansred.reduce_USANS.main()[source]
usansred.reduce_USANS.update_sequence_info(out_file, info)[source]

usansred.summary

summary.py: summary of the reduced data.

usansred.summary.generate_report(config_file_path: str, data_dir: str | None = None, output_dir: str | None = None)[source]

Generate report from a reduction config file.

Parameters

config_file_pathstr

Path to the configuration file (CSV or JSON).

data_dirstr | None

Directory where the reduced data are stored. If None, use the reduced dir in the config file directory.

output_dirstr | None

Where to save the report. If None, use data_folder/reduced.

usansred.utils

usansred.utils.cast_to_bool(value: Any) → bool[source]

Convert common setup-file values to a boolean.