Using individual_axes for 1D Baseline Correction

This example will show how to apply one dimensional baseline correction to two dimensional data using Baseline2D.individual_axes(). Note that this is valid only if each baseline along the axis uses the same inputs; otherwise, the more appropriate approach is to use a for-loop with the corresponding Baseline method.

import matplotlib.pyplot as plt
import numpy as np

from pybaselines import Baseline2D
from pybaselines.utils import gaussian

def plot_contour_with_projection(X, Z, data):
    """Plots the countour plot and 3d projection."""
    fig = plt.figure(layout='constrained', figsize=plt.figaspect(0.5))
    ax_1 = fig.add_subplot(1, 2, 1)
    ax_1.contourf(X, Z, data, cmap='coolwarm')
    ax_2 = fig.add_subplot(1, 2, 2, projection='3d')
    ax_2.plot_surface(X, Z, data, cmap='coolwarm')

    ax_1.set_xlabel('Raman Shift (cm$^{-1}$)')
    ax_1.set_ylabel('Temperature ($^o$C)')
    ax_2.set_xlabel('Raman Shift (cm$^{-1}$)')
    ax_2.set_ylabel('Temperature ($^o$C)')

def plot_1d(x, data):
    """Plots the data in only one dimension."""
    # reverse so that data for lowest temperatures is plotted first
    plt.plot(x, data[::-1].T)
    plt.xlabel('Raman Shift (cm$^{-1}$)')
    plt.ylabel('Intensity (Counts)')

The data for this example will simulate Raman spectroscopy measurements that were taken while heating a sample. Within the sample, peaks for one specimen disappear as the temperature is raised, which could occur due to a chemical reaction, phase change, decomposition, etc. Further, as the temperature increases, the measured baseline slightly increases.

len_temperature = 25
wavenumber = np.linspace(50, 300, 1000)
temperature = np.linspace(25, 100, len_temperature)
X, T = np.meshgrid(wavenumber, temperature, indexing='ij')
noise_generator = np.random.default_rng(0)
data = []
for i, t_value in enumerate(temperature):
    signal = (
        gaussian(wavenumber, 11 * (1 - i / len_temperature), 90, 3)
        + gaussian(wavenumber, 12 * (1 - i / len_temperature), 110, 6)
        + gaussian(wavenumber, 13, 210, 8)
    real_baseline = 100 + 0.005 * wavenumber + 0.0001 * (wavenumber - 120)**2 + 0.08 * t_value
    data.append(signal + real_baseline + noise_generator.normal(scale=0.1, size=wavenumber.size))
y = np.array(data)

plot_contour_with_projection(X, T, y.T)
plot along axes 1d baseline

When considering the baseline of this data, it is more helpful to plot all measurements only considering the wavenumber dependence.

plot_1d(wavenumber, y)
plot along axes 1d baseline

While the measured data is two dimensional, each baseline can be considered as only dependent on the wavenumbers and independent of every other measurement along the temperature axis. Thus, individual_axes can be called on just the axis corresponding to the wavenumbers (ie. axis 1, the columns).

baseline_fitter = Baseline2D(temperature, wavenumber)
baseline, params = baseline_fitter.individual_axes(
    y, axes=1, method='pspline_arpls', method_kwargs={'lam': 1e4}

Looking at the one dimensional representation, each spectrum was correctly baseline corrected.

plot_1d(wavenumber, y - baseline)
plot along axes 1d baseline

Finally, looking at the two dimensional representation of the data again, the dependance of the intensity for each peak with temperature is more easily seen.

plot_contour_with_projection(X, T, (y - baseline).T)
plot along axes 1d baseline

Total running time of the script: (0 minutes 1.518 seconds)

Gallery generated by Sphinx-Gallery