Source code for crikit.cri.kk

"""
Kramers-Kronig relation phase retrieval.

References
----------
[1] Y. Liu, Y. J. Lee, and M. T. Cicerone, "Broadband CARS spectral
phase retrieval using a time-domain Kramers-Kronig transform,"
Opt. Lett. 34, 1363-1365 (2009).

[2] C. H. Camp Jr, Y. J. Lee, and M. T. Cicerone, "Quantitative, \
Comparable Coherent Anti-Stokes Raman Scattering (CARS) \
Spectroscopy: Correcting Errors in Phase Retrieval," Journal of Raman \
Spectroscopy 47, 408-415 (2016). arXiv:1507.06543.


"""

import timeit as _timeit

import numpy as _np

from crikit.cri.algorithms.kk import kkrelation as _kkrelation
from crikit.utils.datacheck import _rng_is_pix_vec
from crikit.utils.general import find_nearest as _find_nearest
from crikit.utils.general import mean_nd_to_1d as _mean_nd_to_1d

[docs]class KramersKronig: """ Retrieve the real and imaginary components of coherent Raman data via the \ Kramers-Kronig (KK) relation. See References. Parameters ---------- cars_amp_offset : float, optional (default=0.0) DC offset applied to CARS spectrum(a) prior to KK relation. See Notes \ and Ref. [2]. nrb_amp_offset : float, optional (default=0.0) DC offset applied to NRB spectrum(a) prior to KK relation. See Notes \ and Ref. [2]. conjugate : bool If spectra go from high-to-low-wavenumber (left-to-right), you should conjugate the KK output. phase_offset : float or ndarray, optional (default=0.0) Phase constant or ndarray applied to retrieved phase prior to \ separating the real and imaginary components. See Notes \ and Ref. [2]. norm_to_nrb : bool, optional (default=True) Normalize the amplitude by sqrt(NRB). This effectively removes several \ system reponse functions. Highly recommended. See Ref. [2] pad_factor : int, optional (default=1) Multiple size of spectral-length to pad the ends of each spectra with. \ Padded with a constant value corresponding to the value at that end of \ the spectrum. See Ref. [1]. rng : ndarray (1D), optional (default=None) Range of pixels/frequencies (if freq provided) to perform over n_edge : int, optional For edge values, take a mean of n_edge neighbors axis : int, optional Axis to perform over min_value : float, optional Applies to cars/nrb (the ratio). Values below min_value set to min_value bad_value : float, optional Applies to cars/nrb (the ratio). Inf's and NaN's set to bad_value hilb_kwargs : dict kwargs sent to the hilbert transform. Only pad_factor, n_edge, and axis are automatically sent -- these will overwrite anything in hilb_kwargs. Returns ------- ndarray KK of cars Notes ----- - This function does NOT overwrite input data - The imaginary components provides the sponatenous Raman-like spectra(um). - This module assumes the spectra are oriented as such that the frequency (wavenumber) increases with increasing index. If this is not the case for your spectra(um), apply a phase_offset of pi. References ---------- [1] Y. Liu, Y. J. Lee, and M. T. Cicerone, "Broadband CARS spectral phase retrieval using a time-domain Kramers-Kronig transform," Opt. Lett. 34, 1363-1365 (2009). [2] C. H. Camp Jr, Y. J. Lee, and M. T. Cicerone, "Quantitative, \ Comparable Coherent Anti-Stokes Raman Scattering (CARS) \ Spectroscopy: Correcting Errors in Phase Retrieval," Journal of Raman \ Spectroscopy 47, 408-415 (2016). arXiv:1507.06543. """ def __init__(self, cars_amp_offset=0.0, nrb_amp_offset=0.0, conjugate=False, phase_offset=0.0, norm_to_nrb=True, pad_factor=1, rng=None, n_edge=1, axis=-1, bad_value=1e-8, min_value=None, hilb_kwargs={}, **kwargs): # Hilbert transform kwargs self.hilb_kwargs = hilb_kwargs # KK kwargs self.kk_kwargs = {'conjugate':conjugate, 'phase_offset':phase_offset, 'norm_to_nrb':norm_to_nrb, 'pad_factor':pad_factor, 'n_edge':n_edge, 'axis':axis, 'bad_value':bad_value, 'min_value':min_value} self.cars_amp_offset = cars_amp_offset self.nrb_amp_offset = nrb_amp_offset # Check range of operation self.rng = _rng_is_pix_vec(rng) @property def phase_offset(self): """ Phase offset """ return self.kk_kwargs['phase_offset'] @phase_offset.setter def phase_offset(self, value): """ Set Phase Offset """ self.kk_kwargs['phase_offset'] = value @property def norm_to_nrb(self): """ norm_to_nrb """ return self.kk_kwargs['norm_to_nrb'] @norm_to_nrb.setter def norm_to_nrb(self, value): """ Set norm_to_nrb """ self.kk_kwargs['norm_to_nrb'] = value @property def pad_factor(self): """ pad_factor """ return self.kk_kwargs['pad_factor'] @pad_factor.setter def pad_factor(self, value): """ Set pad_factor """ self.kk_kwargs['pad_factor'] = value
[docs] def _calc(self, cars, nrb, ret_obj): # Assume that an nD nrb should be averaged to be 1D # TODO: Change this in the future to accept matching NRB's nrb = _mean_nd_to_1d(nrb) if self.rng is None: kkd = _kkrelation(bg=nrb + self.nrb_amp_offset, cri=cars + self.cars_amp_offset, hilb_kwargs=self.hilb_kwargs, **self.kk_kwargs) ret_obj *= 0 ret_obj += kkd else: slice_cars_rng = cars.ndim*[slice(None)] slice_cars_rng[self.kk_kwargs['axis']] = self.rng slice_cars_rng = tuple(slice_cars_rng) slice_ret_obj = ret_obj.ndim*[slice(None)] slice_ret_obj[self.kk_kwargs['axis']] = self.rng slice_ret_obj = tuple(slice_ret_obj) kkd = _kkrelation(bg=nrb[self.rng] + self.nrb_amp_offset, cri=cars[slice_cars_rng] + self.cars_amp_offset, hilb_kwargs=self.hilb_kwargs, **self.kk_kwargs) ret_obj *= 0 ret_obj[slice_ret_obj] += kkd return True
[docs] def calculate(self, cars, nrb): """ cars : ndarray Coherent Raman signal. nrb : ndarray Nonresonant background (NRB) """ kkd = _np.zeros(cars.shape, dtype=_np.complex) self._calc(cars, nrb, ret_obj=kkd) return kkd
[docs] def _transform(self, cars, nrb): if issubclass(cars.dtype.type, _np.complex): success = self._calc(cars, nrb, ret_obj=cars) return success else: return False
if __name__ == '__main__': # pragma: no cover from crikit.data.spectrum import Spectrum as _Spectrum from crikit.data.spectra import Spectra as _Spectra from crikit.data.hsi import Hsi as _Hsi hsi = _Hsi() nrb = _Spectra() WN = _np.linspace(-1386,3826,400) X = .055 + 1/(1000-WN-1j*20) + 1/(3000-WN-1j*20) XNR = 0*X + 0.055 E = 1*_np.exp(-(WN-2000)**2/(2*3000**2)) # Simulated spectrum CARS = _np.abs(E+X)**2 NRB = _np.abs(E+XNR)**2 nrb.data = NRB # Copies of spectrum temp = _np.dot(_np.ones((30,30,1)),CARS[None,:]) # Create an HSData class instance hsi.data = temp num_spectra = int(hsi.size/WN.size) hsi.freq.data = WN start = _timeit.default_timer() kkd = _kkrelation(NRB,CARS) stop = _timeit.default_timer() print('Single spectrum -- Total time: {:.6f} sec'.format(stop-start)) _timeit.time.sleep(2) start = _timeit.default_timer() kkd = _kkrelation(NRB,temp) stop = _timeit.default_timer() print('Data-based -- Total time: {:.6f} sec ({:.6f} sec/spectrum)'.format(stop-start, (stop-start)/num_spectra)) _timeit.time.sleep(2) kk = KramersKronig(cars_amp_offset=0, nrb_amp_offset=0, norm_to_nrb=False, pad_factor=1) start = _timeit.default_timer() kk.calculate(hsi.data, nrb.data) stop = _timeit.default_timer() print('Class-based -- Total time: {:.6f} sec ({:.6f} sec/spectrum)'.format(stop-start, (stop-start)/num_spectra)) hsi.data = temp _timeit.time.sleep(2) import matplotlib as mpl mpl.rcParams['font.size'] = 14 import matplotlib.pyplot as plt hsi.freq.op_list_freq = [500, 4000] #plt.plot(hsi.freq.freq_vec[hsi.freq.op_range_pix], hsi.data[0,0,hsi.freq.op_range_pix]) kk = KramersKronig(cars_amp_offset=0, nrb_amp_offset=0, norm_to_nrb=False, rng=hsi.freq.op_range_pix, pad_factor=1) start = _timeit.default_timer() del kkd kkd = kk.calculate(hsi.data, nrb.data) stop = _timeit.default_timer() print('Pixrange Class-based -- Total time: {:.6f} sec ({:.6f} sec/spectrum)'.format(stop-start, (stop-start)/num_spectra)) hsi.data = kkd del kkd _timeit.time.sleep(2) plt.plot(WN, X.imag, label='Imag.{$\chi_{R}$}') plt.plot(hsi.freq.data[hsi.freq.op_range_pix], hsi.data[10,10,hsi.freq.op_range_pix].imag, 'r*', label='KK-Retrieved') plt.legend(loc='best') plt.xlabel('Wavenumber (cm$^{-1}$)') plt.ylabel('Raman Int. (au)') plt.title('Raman vs KK-Retrieved CARS') plt.show() print(_np.allclose(X.imag[hsi.freq.op_range_pix], hsi.data[10,10,hsi.freq.op_range_pix].imag,rtol=1))