diff --git a/src/ramanchada2/misc/utils/argmin2d.py b/src/ramanchada2/misc/utils/argmin2d.py index 7785c074..306660b9 100644 --- a/src/ramanchada2/misc/utils/argmin2d.py +++ b/src/ramanchada2/misc/utils/argmin2d.py @@ -39,9 +39,24 @@ def align(x, y, **kw_args): """ Iteratively finds best match between x and y and evaluates the x scaling parameters. - min((lambda(x, *p)-y)**2 | *p) + Finds best parameters *p that minimise L2 distance between scaled x and original y + min((lambda(x, *p)-y)**2 | *p) + + Args: + x (ArrayLike[float]): values that need to match the reference + y (ArrayLike[float]): reference values + p0 (Union[List[float], npt.NDArray], optional): initial values for the parameters `p`. + Defaults to [0, 1, 0, 0]. + func (Callable, optional): Objective function to minimize. Returns list penalties + calculated for each `p`. The total objective function is sum of the elements. + Defaults to polynomial of 3-th degree. + max_iter (PositiveInt, optional): max number of iterations. Defaults to 1000. + + Returns: + ArrayLike[float]: array of parameters `p` that minimize the objective funciton """ + if isinstance(p0, list): p = np.array(p0) else: diff --git a/src/ramanchada2/protocols/calib_ne_si_argmin2d_iter_gg.py b/src/ramanchada2/protocols/calib_ne_si_argmin2d_iter_gg.py index d160b3ed..89896c75 100644 --- a/src/ramanchada2/protocols/calib_ne_si_argmin2d_iter_gg.py +++ b/src/ramanchada2/protocols/calib_ne_si_argmin2d_iter_gg.py @@ -9,6 +9,22 @@ def neon_calibration(ne_cm_1: Spectrum, wl: Literal[514, 532, 633, 785]): + """ + Neon calibration + + The approximate laser wavelengt `wl` is used to translate the neon spectrum to [nm]. + Then using :func:`~ramanchada2.spectrum.calibration.by_deltas.xcal_argmin2d_iter_lowpass` + the approximate neon spectrum in [nm] is scaled to match the reference lines. + This way it is calibrated to absolute wavelengths. A Makima spline is calculated so that + it takes wavenumbers [1/cm] and return wavelength-calibrated x-axis in wavelengths [nm]. + + Args: + ne_cm_1 (Spectrum): neon spectrum used for the calibration. Should be in [1/cm] + wl (Literal[514, 532, 633, 785]): Approximate laser wavelength in [nm] + + Returns: + Callable(ArrayLike[float]): callable (spline) that applies the calibration + """ ref = rc2const.neon_wl_dict[wl] ne_nm = ne_cm_1.subtract_moving_minimum(200).shift_cm_1_to_abs_nm_filter(wl).normalize() # type: ignore @@ -24,11 +40,16 @@ def silicon_calibration(si_nm: Spectrum, """ Calculate calibration function for lazer zeroing + Takes wavelength-calibrated Silicon spectrum in wavelengths [nm] and using + the Silicon peak position it calculates the real laser wavelength and a Makima + spline that translates the wavelengt-calibrated x-axis wavelength [nm] values to + lazer-zeroed Raman shift in wavenumbers [1/cm]. + Args: si_nm: Spectrum - Silicon spectrum + Wavelength-calibrated Silicon spectrum in wavelengths [nm] wl: Literal[514, 532, 633, 785] - Laser wavelength + Approximate Laser wavelength find_peaks_kw: dict, optional keywords for find_peak. Default values are `{'prominence': min(.8, si_nm.y_noise_MAD()*50), 'width': 2, 'wlen': 100}` @@ -72,6 +93,24 @@ def neon_silicon_calibration(ne_cm_1: Spectrum, sil_fit_kw={}, sil_find_kw={} ): + """ + Perform neon and silicon calibration together + + Combines :func:`~ramanchada2.protocols.calib_ne_si_argmin2d_iter_gg.neon_calibration` + and :func:`~ramanchada2.protocols.calib_ne_si_argmin2d_iter_gg.silicon_calibration`. + Returned spline is calculated using the wavlength-calibrated x-axis values translated + to Raman shift wavenumbers using the calculated laser wavelength in `silicon_calibration` + + Args: + ne_cm_1 (Spectrum): neon spectrum used for the calibration. Should be in [1/cm] + si_cm_1 (Spectrum): silicon spectrum to estimate laser wavelength. Should be in [1/cm]. + wl (Literal[514, 532, 633, 785]): Approximate laser wavelength in [nm] + sil_fit_kw (dict, optional): kwargs sent as `find_peaks_kw` in `silicon_calibration`. Defaults to {}. + sil_find_kw (dict, optional): kwargs sent as `fit_peaks_kw` in `silicon_calibration`. Defaults to {}. + + Returns: + Callable(ArrayLike[float]): callable (spline) that applies the calibration + """ ne_spline = neon_calibration(ne_cm_1, wl) si_nm = si_cm_1.scale_xaxis_fun(ne_spline) # type: ignore si_spline, wl = silicon_calibration(si_nm, wl, diff --git a/src/ramanchada2/spectrum/calibration/by_deltas.py b/src/ramanchada2/spectrum/calibration/by_deltas.py index bba9e042..468a130c 100644 --- a/src/ramanchada2/spectrum/calibration/by_deltas.py +++ b/src/ramanchada2/spectrum/calibration/by_deltas.py @@ -182,6 +182,25 @@ def xcal_fine(old_spe: Spectrum, poly_order: NonNegativeInt, find_peaks_kw={}, ): + """ + Iterative calibration with provided reference based on :func:`~ramanchada2.misc.utils.argmin2d.align` + + Iteratively apply polynomial of `poly_order` degree to match + the found peaks to the reference locations. The pairs are created + using :func:`~ramanchada2.misc.utils.argmin2d.align` algorithm. + + Args: + old_spe (Spectrum): internal use only + new_spe (Spectrum): internal use only + ref (Union[Dict[float, float], List[float]]): _description_ + ref (Dict[float, float]): + If a dict is provided - wavenumber - amplitude pairs. + If a list is provided - wavenumbers only. + poly_order (NonNegativeInt): polynomial degree to be used usualy 2 or 3 + should_fit (bool, optional): Whether the peaks should be fit or to + associate the positions with the maxima. Defaults to False. + find_peaks_kw (dict, optional): kwargs to be used in find_peaks. Defaults to {}. + """ if isinstance(ref, dict): ref_pos = np.array(list(ref.keys())) @@ -260,6 +279,24 @@ def xcal_argmin2d_iter_lowpass(old_spe: Spectrum, new_spe: Spectrum, /, *, ref: Dict[float, float], low_pass_nfreqs: List[int] = [100, 500]): + """ + Calibrate spectrum + + The calibration is done in multiple steps. Both the spectrum and the reference + are passed through a low-pass filter to preserve only general structure of the + spectrum. `low_pass_nfreqs` defines the number of frequencies to be preserved in + each step. Once all steps with low-pass filter a final step without a low-pass + filter is performed. Each calibration step is performed using + :func:`~ramanchada2.spectrum.calibration.by_deltas.xcal_fine` algorithm. + + Args: + old_spe (Spectrum): internal use only + new_spe (Spectrum): internal use only + ref (Dict[float, float]): wavenumber - amplitude pairs + low_pass_nfreqs (List[int], optional): The number of elements defines the + number of low-pass steps and their values define the amount of frequencies + to keep. Defaults to [100, 500]. + """ def semi_spe_from_dict(deltas: dict, xaxis): y = np.zeros_like(xaxis) for pos, ampl in deltas.items():