Utilities and validation

Scan construction helpers and scan-data validation.

Utility functions for beamline operations.

This module provides helper functions for common beamline tasks like motor alignment, grid scan generation, and data analysis.

resonance.api.utils.calculate_center_of_mass(scan_data: DataFrame, motor_col: str, signal_col: str, use_mean: bool = True) float[source]

Calculate center of mass for alignment.

Parameters:
  • scan_data (pd.DataFrame) – Scan results from scan_from_dataframe

  • motor_col (str) – Motor position column name

  • signal_col (str) – Signal column name

  • use_mean (bool, optional) – Use _mean column if True (default: True)

Returns:

Center of mass position

Return type:

float

Examples

>>> results = await server.scan_from_dataframe(scan_df, ...)
>>> com = calculate_center_of_mass(
...     results,
...     motor_col="Sample X_position",
...     signal_col="Photodiode_mean"
... )
>>> # Move to center of mass
>>> await server.motor.set("Sample X", com)
resonance.api.utils.create_energy_scan(energies: ndarray, exposure_time: float | ndarray = 1.0, energy_motor: str = 'Beamline Energy') DataFrame[source]

Create an energy scan DataFrame.

Parameters:
  • energies (np.ndarray) – Array of photon energies

  • exposure_time (float | np.ndarray, optional) – Exposure time(s) (default: 1.0)

  • energy_motor (str, optional) – Energy motor name (default: “Beamline Energy”)

Returns:

Scan definition ready for scan_from_dataframe

Return type:

pd.DataFrame

Examples

>>> # Carbon K-edge
>>> energies = np.linspace(280, 320, 200)
>>> scan = create_energy_scan(energies, exposure_time=1.0)
>>>
>>> # Variable exposure times
>>> energies = np.linspace(280, 320, 200)
>>> exposure = np.ones(200)
>>> exposure[100:150] = 2.0  # Longer at edge
>>> scan = create_energy_scan(energies, exposure_time=exposure)
resonance.api.utils.create_grid_scan(x_range: tuple[float, float, int], y_range: tuple[float, float, int], exposure_time: float = 1.0, x_motor: str = 'Sample X', y_motor: str = 'Sample Y') DataFrame[source]

Create a 2D grid scan DataFrame.

Parameters:
  • x_range (tuple[float, float, int]) – X range as (start, stop, num_points)

  • y_range (tuple[float, float, int]) – Y range as (start, stop, num_points)

  • exposure_time (float, optional) – Exposure time for all points (default: 1.0)

  • x_motor (str, optional) – X motor name (default: “Sample X”)

  • y_motor (str, optional) – Y motor name (default: “Sample Y”)

Returns:

Scan definition ready for scan_from_dataframe

Return type:

pd.DataFrame

Examples

>>> # Create 3x3 grid
>>> grid = create_grid_scan(
...     x_range=(10, 12, 3),
...     y_range=(0, 2, 3),
...     exposure_time=1.0
... )
>>> print(len(grid))  # 9 points
9
resonance.api.utils.create_line_scan(motor: str, start: float, stop: float, num_points: int, exposure_time: float = 1.0) DataFrame[source]

Create a 1D line scan DataFrame.

Parameters:
  • motor (str) – Motor name

  • start (float) – Start position

  • stop (float) – Stop position

  • num_points (int) – Number of points

  • exposure_time (float, optional) – Exposure time for all points (default: 1.0)

Returns:

Scan definition ready for scan_from_dataframe

Return type:

pd.DataFrame

Examples

>>> scan = create_line_scan(
...     motor="Sample X",
...     start=10,
...     stop=15,
...     num_points=11,
...     exposure_time=1.5
... )
resonance.api.utils.find_peak_position(scan_data: DataFrame, motor_col: str, signal_col: str, use_mean: bool = True) tuple[float, float][source]

Find peak position in 1D scan data.

Parameters:
  • scan_data (pd.DataFrame) – Scan results from scan_from_dataframe

  • motor_col (str) – Motor position column name (e.g., “Sample X_position”)

  • signal_col (str) – Signal column name (e.g., “Photodiode_mean”)

  • use_mean (bool, optional) – Use _mean column if True, else use raw column (default: True)

Returns:

(peak_position, peak_value)

Return type:

tuple[float, float]

Examples

>>> results = await server.scan_from_dataframe(scan_df, ...)
>>> peak_x, peak_signal = find_peak_position(
...     results,
...     motor_col="Sample X_position",
...     signal_col="Photodiode_mean"
... )
>>> print(f"Peak at {peak_x:.2f} with signal {peak_signal:.3f}")
resonance.api.utils.knife_edge_analysis(scan_data: DataFrame, motor_col: str, signal_col: str, threshold: float = 0.5, direct_beam: Literal['above', 'below'] = 'above') float[source]

Analyze knife-edge scan to find edge position.

Useful for aligning samples to incident beam.

Parameters:
  • scan_data (pd.DataFrame) – Scan results from scan_from_dataframe

  • motor_col (str) – Motor position column name

  • signal_col (str) – Signal column name

  • threshold (float, optional) – Fraction of max signal to define edge (default: 0.5)

  • direct_beam (Literal["above", "below"], optional) – Direction of direct beam relative to edge (default: “above”)

Returns:

Estimated edge position

Return type:

float

Examples

>>> results = await server.scan_from_dataframe(scan_df, ...)
>>> edge_pos = knife_edge_analysis(
...     results,
...     motor_col="Sample Z_position",
...     signal_col="Photodiode_mean"
... )
>>> print(f"Edge position at {edge_pos:.2f}")
resonance.api.utils.merge_scans(scans: list[DataFrame], motor_col: str, average_overlaps: bool = True) DataFrame[source]

Merge multiple scans into a single dataset.

Parameters:
  • scans (list[pd.DataFrame]) – List of scan DataFrames to merge

  • motor_col (str) – Motor position column to use for alignment

  • average_overlaps (bool, optional) – Average overlapping points (default: True)

Returns:

Merged scan data

Return type:

pd.DataFrame

Examples

>>> # Combine multiple energy ranges
>>> scan1 = await nexafs_scan(server, np.linspace(280, 290, 50))
>>> scan2 = await nexafs_scan(server, np.linspace(288, 300, 100))
>>> merged = merge_scans([scan1, scan2], motor_col="energy")
resonance.api.utils.resample_scan_data(scan_data: DataFrame, motor_col: str, num_points: int, columns_to_interpolate: list[str] | None = None) DataFrame[source]

Resample scan data to uniform grid.

Useful for combining scans with different point densities.

Parameters:
  • scan_data (pd.DataFrame) – Original scan data

  • motor_col (str) – Motor position column for interpolation axis

  • num_points (int) – Number of points in resampled data

  • columns_to_interpolate (list[str] | None, optional) – Columns to interpolate (default: all numeric columns)

Returns:

Resampled data with uniform spacing

Return type:

pd.DataFrame

Examples

>>> # Resample to 100 points
>>> resampled = resample_scan_data(
...     results,
...     motor_col="Beamline Energy_position",
...     num_points=100
... )

DataFrame validation utilities for scan plan creation

resonance.api.validation.find_exposure_column(df: DataFrame) str | None[source]

Find exposure time column using pattern matching.

Handles common variations: - “exposure”, “exp” - “count_time”, “count time” - “Unnamed: 2” (pandas default for unnamed columns) - “” (empty string column name)

Parameters:

df: Input DataFrame

Returns:

Column name if found, None otherwise

resonance.api.validation.validate_motor_columns(df: DataFrame) list[str][source]

Validate that DataFrame columns match known motor names.

Parameters:

df: Input DataFrame

Returns:

List of valid motor column names

Raises:

ValidationError: If invalid columns found or no motor columns present

resonance.api.validation.validate_scan_dataframe(df: DataFrame) tuple[list[str], str | None][source]

Validate complete scan DataFrame.

Parameters:

df: Input DataFrame with motor columns and optional exposure column

Returns:

Tuple of (motor_column_names, exposure_column_name)

Raises:

ValidationError: If validation fails