.. _signal_processor_documentation:
================
Signal Processor
================
After running a simulation, you may need to apply some post-simulation signal processing. For example, you may need to scale the simulated spectrum to match experimental intensities, or you may want to convolve the spectrum with a Lorentzian, Gaussian, or other line-broadening
function. For this reason, **mrsimulator** offers some frequently used NMR signal processing tools within the ``mrsimulator.signal_processor`` module.
.. seealso::
:ref:`signal_processor_examples` for notebooks using common processing functions.
CSDM object
-----------
The simulated spectrum is held in a CSDM [#f1]_ object, which supports multi-dimensional
scientific datasets (NMR, EPR, FTIR, GC, etc.). For more information, see
the `csdmpy documentation `__.
``SignalProcessor`` class
-------------------------
Signal processing is a series of operations sequentially applied to the dataset.
In mrsimulator, the :py:class:`~mrsimulator.signal_processor.SignalProcessor` object is
used to apply operations. Here we create a new SignalProcessor object
.. plot::
:context: reset
# Import the signal_processor module
from mrsimulator import signal_processor as sp
# Create a new SignalProcessor object
processor = sp.SignalProcessor()
Each signal processor object holds a list of operations under the *operations* attribute. Below we add operations to apply Gaussian line broadening and a scale factor.
.. plot::
:context: close-figs
processor.operations = [
sp.IFFT(),
sp.apodization.Gaussian(FWHM="50 Hz"),
sp.FFT(),
sp.Scale(factor=120),
]
First, an inverse Fourier transform is applied to the dataset. Then,
a Gaussian apodization with a full-width-at-half-maximum of 50 Hz
in the frequency domain is applied. The unit used for the ``FWHM``
attribute corresponds to the dimensionality of the dataset. By
choosing Hz, we imply the dataset is in units of frequency.
Finally, a forward Fourier transform is applied to the apodized
dataset, and all points are scaled up by 120 times.
.. note::
Convolutions in **mrsimulator** are performed using the
`Convolution Theorem `_. A spectrum is
Fourier transformed, and apodizations are performed in the time domain before being transformed back into the frequency domain.
Let's create a CSDM object and then apply the operations to visualize the results.
.. plot::
:context: close-figs
import csdmpy as cp
import numpy as np
# Create a CSDM object with delta function at 200 Hz
test_data = np.zeros(500)
test_data[200] = 1
csdm_object = cp.CSDM(
dependent_variables=[cp.as_dependent_variable(test_data)],
dimensions=[cp.LinearDimension(count=500, increment="1 Hz")],
)
To apply the previously defined signal processing operations to the above CSDM object, use
the :py:meth:`~mrsimulator.signal_processor.SignalProcessor.apply_operations` method of the
``SignalProcessor`` instance as follows
.. plot::
:context: close-figs
processed_dataset = processor.apply_operations(dataset = csdm_object)
The variable ``processed_dataset`` is another CSDM object holding the dataset
after the list of operations has been applied to ``csdm_object``. Below is a
plot comparing the unprocessed and processed dataset
.. skip: next
.. plot::
:context: close-figs
:caption: The unprocessed dataset (left) and processed dataset (right) with a Gaussian convolution
and scale factor.
import matplotlib.pyplot as plt
_, ax = plt.subplots(1, 2, figsize = (8, 3), subplot_kw = {"projection":"csdm"})
ax[0].plot(csdm_object, color="black", linewidth=1)
ax[0].set_title("Unprocessed")
ax[1].plot(processed_dataset.real, color="black", linewidth=1)
ax[1].set_title("Processed")
plt.tight_layout()
plt.show()
Applying Operations along a Dimension
-------------------------------------
Multi-dimensional NMR simulations may need different operations applied along different dimensions. Each operation has the attribute ``dim_index``, which is used to apply operations along a certain dimension.
By default, ``dim_index`` is ``None`` and is applied along the 1st dimension. An integer or list of integers can be passed to ``dim_index``, specifying the dimensions. Below are examples of specifying the dimensions
.. plot::
:context: close-figs
# Gaussian apodization along the first dimension (default)
sp.apodization.Gaussian(FWHM="10 Hz")
# Constant offset along the second dimension
sp.baseline.ConstantOffset(offset=10, dim_index=1)
# Exponential apodization along the first and third dimensions
sp.apodization.Exponential(FWHM="10 Hz", dim_index=[0, 2])
Applying Apodizations to specific Dependent Variables
-----------------------------------------------------
Each dimension in a simulated spectrum can hold multiple dependent variables (a.k.a. contributions from multiple spin systems). Each spin system may need different convolutions applied to match an experimental spectrum. The
:py:class:`~mrsimulator.signal_processor.Apodization` sub-classes have the ``dv_index`` attribute, specifying which dependent variable (spin system) to apply the operation on. By default, ``dv_index`` is ``None`` and will apply the convolution to all dependent variables
in a dimension.
.. note::
The index of a dependent variable (spin system) corresponds to the order of spin systems in the
:py:attr:`~mrsimulator.Simulator.spin_systems` list.
.. plot::
:context: close-figs
processor = sp.SignalProcessor(
operations=[
sp.IFFT(),
sp.apodization.Gaussian(FWHM="25 Hz", dv_index=0),
sp.apodization.Gaussian(FWHM="70 Hz", dv_index=1),
sp.IFFT(),
]
)
The above list of operations will apply 25 and 70 Hz of Gaussian
line broadening to dependent variables at index 0 and 1, respectively.
Let's add another dependent variable to the previously created CSDM object to target specific dependent variables.
.. plot::
:context: close-figs
test_data = np.zeros(500)
test_data[300] = 1
csdm_object.add_dependent_variable(cp.as_dependent_variable(test_data))
Now, we again apply the operations with the
:py:meth:`~mrsimulator.signal_processor.SignalProcessor.apply_operations` method.
The comparison of the unprocessed and processed dataset is also shown below.
.. plot::
:context: close-figs
processed_dataset = processor.apply_operations(dataset = csdm_object)
Below is a plot of the dataset before and after applying the operations
.. skip: next
.. plot::
:context: close-figs
:caption: The unprocessed dataset (left) and the processed dataset (right) with
convolutions applied to different dependent variables.
_, ax = plt.subplots(1, 2, figsize=(8, 3), subplot_kw={"projection":"csdm"})
ax[0].plot(csdm_object, linewidth=1)
ax[0].set_title("Unprocessed")
ax[1].plot(processed_dataset.real, linewidth=1)
ax[1].set_title("Processed")
plt.tight_layout()
plt.show()
.. [#f1] Srivastava, D. J., Vosegaard, T., Massiot, D., Grandinetti, P. J.,
Core Scientific Dataset Model: A lightweight and portable model and
file format for multi-dimensional scientific dataset, PLOS ONE,
**15**, 1-38, (2020).
`DOI:10.1371/journal.pone.0225953 `__