Source code for xpectra.pipeline.config

"""Configuration shared by the training and prediction pipelines."""

from __future__ import annotations

from dataclasses import asdict, dataclass
from typing import Any


ROUTES = ("norm", "deriv1", "deriv2")

ROUTE_FILES = {
    "norm": "combined_norm_data.csv.xz",
    "deriv1": "combined_norm_deriv1_data.csv.xz",
    "deriv2": "combined_norm_deriv2_data.csv.xz",
}

ROUTE_DESCRIPTIONS = {
    "norm": "normalized spectra",
    "deriv1": "first derivative of normalized spectra",
    "deriv2": "second derivative of normalized spectra",
}

ORDERED_GROUPS = ("PE", "PET", "PP", "PS", "PVC", "Other")

# Coarse 6-group mapping of heterogeneous source labels. These denote spectral families, not
# single-polymer identities: "PET" pools ester-bearing plastics and "PE" includes PEF.
# NOTE: "PPMA" below is a typo for PMMA; since the data label is "PMMA", those spectra currently
# fall through to Other rather than PET. Fixing it would move ~5 rows (Other -> PET) and require
# regenerating results, so it is left as-is here to keep committed results consistent (see the
# grouping note in the manuscript/Methods).
CORE_GROUP = {"PVC"}
PP_MEMBERS = {"PP", "PPL"}
PE_MEMBERS = {"PE", "HDPE", "LDPE", "PEL", "PEF"}
PS_LIKE = {"PS", "ABS"}
PET_LIKE = {"PET", "PLA", "PEVA", "PPMA", "AC", "EAA", "PC", "CA"}


[docs] @dataclass(frozen=True) class PreprocessConfig: """Notebook preprocessing defaults, exposed as CLI-overridable settings.""" label_column: str = "type" sample_id_column: str = "sample_id" unknown_study: str = "kedzierski_2019_u" # The combined route files carry all datasets; downstream selection is by study. # labeled_studies -> supervised training/evaluation (load_labeled, train_route); # external_studies -> independent augmentation pools (pooled explicitly per experiment). labeled_studies: tuple[str, ...] = ( "jung_2018", "kedzierski_2019", "frond_2021", "villegas_camacho_2024_c4", ) external_studies: tuple[str, ...] = ("openspecy", "simple", "awi2024") denoising_method: str = "wavelet" baseline_method: str = "aspls" normalization_method: str = "spectral_moments" interpolate_method: str = "zero" flat_windows: tuple[tuple[float, float], ...] = ( (1880.0, 1900.0), (2400.0, 2700.0), ) exclude_regions: tuple[tuple[float, float], ...] = ( (0.0, 679.0), (3201.0, 5000.0), ) # Atmospheric interpolation: only the CO2 asymmetric-stretch band is blanked. # An empirical sharp-structure check across all five source datasets (RMS of the # high-pass residual, <~25 cm^-1 features, inside vs. flanking control windows; # raw data and the denoised+baseline stage) showed: # - H2O-bend windows (1575-1615, 1620-1660): ratio 0.1-0.8 in every study, i.e. # no water-vapor rotational lines — the source spectra are already background- # compensated. Those windows only removed smooth polymer signal (PS aromatic # ~1600, PET/C=C bands 1620-1660), so they were dropped. # - CO2 window: ratio 2-22x in every study, with the artifact footprint extending # below the old 2329 edge (jung ~2325, villegas down to ~2310), so the band is # kept and widened from (2329, 2369) to (2300, 2380). interpolate_regions: tuple[tuple[float, float], ...] = ( (2300.0, 2380.0), ) wn_min: float = 680.0 wn_max: float = 3100.0 resolution: float = 2.0 descending: bool = True combine_method: str = "pchip" derivative_window_length: int = 15 derivative_polyorder: int = 3 derivative_delta: float = 1.0 random_state: int = 42 test_size: float = 0.2 n_jobs: int = -1
[docs] def to_dict(self) -> dict[str, Any]: return asdict(self)