dispcraft.measurement

measurement

Loading and cleaning of PSF ground-test measurement data.

See 2-Intro_data/index.html for the dataset description (NISP FM ground-test campaign). Model target: (theta, phi, wavelength) -> (cent_y, cent_z).

load_spectra

load_spectra(filepath, sig_max=0.05, sig_columns=('sig_y', 'sig_z'))

Load a PSF dataset and drop rows with uncertain centroids.

Parameters:
  • filepath (str or Path -- path to a *_first.csv or *_zeroth.csv file) –
  • sig_max (float -- centroid uncertainty threshold [mm]; rows with, default: 0.05 ) –

    any column in sig_columns at or above this are dropped as low quality.

  • sig_columns (tuple of str -- which uncertainty columns to filter on., default: ('sig_y', 'sig_z') ) –

    Defaults to both axes (first-order data has both). 0th-order data only has sig_y populated -- e_PSF_centroid[1] (sig_z) is never measured for it, so callers of zeroth data must pass sig_columns=("sig_y",) or every row is dropped (NaN < sig_max is always False).

Returns:
  • pd.DataFrame -- filtered rows, index reset.
Source code in dispcraft/measurement.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def load_spectra(filepath, sig_max=0.05, sig_columns=("sig_y", "sig_z")):
    """Load a PSF dataset and drop rows with uncertain centroids.

    Parameters
    ----------
    filepath : str or Path -- path to a *_first.csv or *_zeroth.csv file
    sig_max : float -- centroid uncertainty threshold [mm]; rows with
        any column in `sig_columns` at or above this are dropped as low
        quality.
    sig_columns : tuple of str -- which uncertainty columns to filter on.
        Defaults to both axes (first-order data has both). 0th-order data
        only has `sig_y` populated -- `e_PSF_centroid[1]` (sig_z) is never
        measured for it, so callers of zeroth data must pass
        `sig_columns=("sig_y",)` or every row is dropped (NaN < sig_max is
        always False).

    Returns
    -------
    pd.DataFrame -- filtered rows, index reset.
    """
    df = pd.read_csv(filepath)
    mask = pd.Series(True, index=df.index)
    for col in sig_columns:
        mask &= df[col] < sig_max
    return df[mask].reset_index(drop=True)

median_per_spectrum

median_per_spectrum(df)

Collapse repeated centroid measurements to one row per (spectrum, line).

Parameters:
  • df (pd.DataFrame -- a *_first.csv-shaped DataFrame (e.g. from load_spectra)) –
Returns:
  • pd.DataFrame -- one median row per (spectra_id, wavelength) pair.
Source code in dispcraft/measurement.py
37
38
39
40
41
42
43
44
45
46
47
48
def median_per_spectrum(df):
    """Collapse repeated centroid measurements to one row per (spectrum, line).

    Parameters
    ----------
    df : pd.DataFrame -- a *_first.csv-shaped DataFrame (e.g. from load_spectra)

    Returns
    -------
    pd.DataFrame -- one median row per (spectra_id, wavelength) pair.
    """
    return df.groupby(["spectra_id", "wavelength"]).median().reset_index()

zeroth_order_centers

zeroth_order_centers(filepath, sig_max=0.05)

Compute one 0th-order center per spectrum from the two-blob model.

Each spectrum has a rank-1 (blue edge, 1206 nm) and rank-2 (red edge, 1892 nm) blob -- the RGS passband's transmission edges, per doi:10.1051/0004-6361/202555859 (not the previous round-number placeholders) -- the 0th-order center is their mean centroid.

Parameters:
  • filepath (str or Path -- path to a *_zeroth.csv file) –
  • sig_max (float -- centroid uncertainty threshold [mm], see load_spectra., default: 0.05 ) –
Returns:
  • pd.DataFrame -- one row per spectra_id with cent_y, cent_z, theta, phi.
Source code in dispcraft/measurement.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def zeroth_order_centers(filepath, sig_max=0.05):
    """Compute one 0th-order center per spectrum from the two-blob model.

    Each spectrum has a rank-1 (blue edge, 1206 nm) and rank-2 (red edge,
    1892 nm) blob -- the RGS passband's transmission edges, per
    doi:10.1051/0004-6361/202555859 (not the previous round-number
    placeholders) -- the 0th-order center is their mean centroid.

    Parameters
    ----------
    filepath : str or Path -- path to a *_zeroth.csv file
    sig_max : float -- centroid uncertainty threshold [mm], see load_spectra.

    Returns
    -------
    pd.DataFrame -- one row per spectra_id with cent_y, cent_z, theta, phi.
    """
    df = _median_per_rank(load_spectra(filepath, sig_max=sig_max, sig_columns=("sig_y",)))
    centers = df.groupby("spectra_id").agg(
        cent_y=("cent_y", "mean"),
        cent_z=("cent_z", "mean"),
        theta=("theta", "first"),
        phi=("phi", "first"),
    ).reset_index()
    return centers

zeroth_order_separation

zeroth_order_separation(filepath, sig_max=0.05)

Per-spectrum rank2-rank1 (red-blue) 0th-order two-blob separation.

The physical model's m=0 grating term predicts zero wavelength dependence for the 0th order, but this separation is measurably non-zero and field-position-dependent (Stage 5 Phase 3) -- an unmodeled chromatic effect, not noise. Step 4 found the prism's own chromatic dispersion (unaffected by m) explains part of it -- see dispcraft.zeroth_dispersion.predict_zeroth_order_separation_physical.

Parameters:
  • filepath (str or Path -- path to a *_zeroth.csv file) –
  • sig_max (float -- centroid uncertainty threshold [mm], see load_spectra., default: 0.05 ) –
Returns:
  • pd.DataFrame -- one row per spectra_id with both ranks present:

    spectra_id, dy, dz (mm), y_nisp, z_nisp (rank-1's field position).

Source code in dispcraft/measurement.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def zeroth_order_separation(filepath, sig_max=0.05):
    """Per-spectrum rank2-rank1 (red-blue) 0th-order two-blob separation.

    The physical model's `m=0` grating term predicts zero wavelength
    dependence for the 0th order, but this separation is measurably
    non-zero and field-position-dependent (Stage 5 Phase 3) -- an
    unmodeled chromatic effect, not noise. Step 4 found the *prism*'s own
    chromatic dispersion (unaffected by `m`) explains part of it -- see
    `dispcraft.zeroth_dispersion.predict_zeroth_order_separation_physical`.

    Parameters
    ----------
    filepath : str or Path -- path to a *_zeroth.csv file
    sig_max : float -- centroid uncertainty threshold [mm], see load_spectra.

    Returns
    -------
    pd.DataFrame -- one row per spectra_id with both ranks present:
        spectra_id, dy, dz (mm), y_nisp, z_nisp (rank-1's field position).
    """
    df = _median_per_rank(load_spectra(filepath, sig_max=sig_max, sig_columns=("sig_y",)))
    piv_y = df.pivot_table(index="spectra_id", columns="rank", values="cent_y")
    piv_z = df.pivot_table(index="spectra_id", columns="rank", values="cent_z")
    pos = df[df["rank"] == 1].set_index("spectra_id")[["y_nisp", "z_nisp"]]
    out = pd.DataFrame({"dy": piv_y[2] - piv_y[1], "dz": piv_z[2] - piv_z[1]}).dropna().join(pos)
    return out.reset_index()