Module interferogram
Expand source code
POWERS2 = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768]
MAX_WAVENUMBER = 15802
class Interferogram:
"""Interferogram class for all data processing, plotting, and saving.
Attributes:
dir: directory holding data file
filename: filename holding data
datestamp: datestamp recording when data was taken
res: resolution of scan
vel: mirror velocity of scan
LP: low pass frequency of scan
HP: high pass frequency of scan
gain: gain of scan
raw_data: parsed interferogram acquisitions of scan
num_acq: number of acquisitions of scan
num_pts: number of points of each acquistion in scan
wavenumbers: wavenumber axis for each acquisiton
spectra: fourier transformed spectra with mertz correction
"""
def __init__(self, DIR, filename, sampling=1):
self.dir = DIR
self.filename = filename
with open(DIR+filename) as f:
data = f.read().splitlines()
# Read in params at the top of the file
self.datestamp = data[0].replace("#","").strip()
result = re.search(r'\d+', data[1])
self.res = int(result.group())
result = re.search(r'\d+', data[2])
self.vel = int(result.group())
result = re.search(r'\d+', data[3])
self.LP = int(result.group())
result = re.search(r'\d+', data[4])
self.HP = int(result.group())
result = re.search(r'\d+', data[5])
self.gain = int(result.group())
# Read in the raw spectrum data
raw = list(filter(None, data[6:]))
scan_idx = [i for i in range(0, len(raw)) if "#" in raw[i]] + [len(raw)]
self.raw_data = np.array(
[np.array(raw[scan_idx[i]+1:scan_idx[i+1]], dtype=np.float) for i in range(0, len(scan_idx)-1)]
)
self.num_acq = len(self.raw_data)
# Find the wavenumbers
self.num_pts = [len(self.raw_data[i]) for i in range(0, self.num_acq)]
self.wavenumbers = np.array(
[sampling*np.linspace(0, MAX_WAVENUMBER, num=int(self.num_pts[i])/2) for i in range(0, self.num_acq)]
)
# Find the spectra
mean = np.mean(np.array([x for x in raw if "#" not in x], dtype=np.float))
self.spectra = self.fft(mean)
def fft(self, mean):
"""
Calculates the spectra of the interferogram
Args:
interferogram2: The test sample spectrum, a np.array
Returns:
A numpy array of transmittances.
Raises:
AssertionError: An error occurred if resolutions are different
"""
new_data = []
for line in self.raw_data:
# Normalize data
new_line = Interferogram.normalize_raw_data(line, mean)
# Zero fill the data
new_line = Interferogram.zero_fill(new_line)
# Find the phase corrections
phase, re_phase, im_phase = self.mertz(new_line)
# fft the interferogram
inter, re_inter, im_inter = Interferogram.fft_interferogram(line)
# Record the spectra multiplied by correction
new_data.append(np.absolute(np.multiply(inter, phase)))
return np.array(new_data)
def mertz(self, data):
"""
Returns the phase correction data
The real and imaginary data arrays are multiplied by the power spectrum
Args:
data: interferogram to be filled
Returns:
fft spectrum: A numpy array of data multiplied by the power spectrum
re spectrum: A real numpy array of data multiplied by the power spectrum
im spectrum: A imaginary numpy array of data multiplied by the power spectrum
"""
# find burtst location
burst = np.argmax(data)
# Add the apodization triangular phase correction
copied_burst = np.zeros(len(data))
copied_burst[burst-128:burst+128] = data[burst-128:burst+128]
copied_burst = np.multiply(copied_burst, Interferogram.triangular_signal(burst, len(copied_burst)))
# Slicing to rotate about ZPD
copied_burst = np.concatenate((copied_burst[burst:], copied_burst[:burst]))
# data is FFT'd, producing real and imaginary data arrays.
ffted = np.fft.fft(copied_burst)
im_data = ffted.imag
re_data = ffted.real
# Find power spectrum
power = np.absolute(ffted)
# The real and imaginary data arrays are multiplied by the power spectrum
return np.multiply(power, ffted), np.multiply(re_data, power), np.multiply(im_data, power)
@staticmethod
def fft_interferogram(data):
"""
Do the fft for the interferogram, including the apodization, etc.
Args:
data: interferogram to be filled
Returns:
fft spectrum: A numpy array of ffted data
re spectrum: A real numpy array of ffted data
im spectrum: A imaginary numpy array of ffted data
"""
# find burst location
burst = np.argmax(data)
# Apodize the whole interferogram with triangular function
data = np.multiply(data, Interferogram.triangular_signal(burst, len(data)))
# zero fill
data = Interferogram.zero_fill(data)
# Slicing to rotate about ZPD
data = np.concatenate((data[burst:], data[:burst]))
# data is FFT'd, producing real and imaginary data arrays.
ffted = np.fft.fft(data)
return ffted, ffted.real, ffted.imag
@staticmethod
def zero_fill(data):
"""
FFT requires number of points in the interferogram be a power of 2.
If it is not, then the size of the array is increased to the next higher power of 2
and the added points are set to 0.
Args:
data: interferogram to be filled
Returns:
A numpy array of filled interferogram
"""
for i in range(10, len(POWERS2)):
if POWERS2[i] >= len(data):
break
return np.concatenate((data, np.zeros(POWERS2[i]-len(data))))
@staticmethod
def normalize_raw_data(data, mean):
"""The mean of the data is subtracted from the data, brings baseline of data to 0"""
return data - mean
@staticmethod
def apodize(burst, data):
return np.multiply(data, Interferogram.triangular_signal(burst, len(data)))
@staticmethod
def triangular_signal(center, total_len):
window = signal.triang(2*center-1)
try:
return np.concatenate((window, np.zeros(total_len-len(window))))
except:
return window[:total_len]
@staticmethod
def calc_transmittance(interferogram1, interferogram2):
"""
Calculates the transmittance per wavenumber
Args:
interferogram1: The background spectrum, a np.array
interferogram2: The test sample spectrum, a np.array
Returns:
A numpy array of transmittances.
Raises:
AssertionError: An error occurred if resolutions are different
"""
assert (len(interferogram1) == len(interferogram2)), 'Background spectrum has {} pts, sample spectrum has {} pts'.format(len(interferogram1), len(interferogram2))
return np.divide(interferogram2, interferogram1)
@staticmethod
def calc_absorbance(transmittance):
"""
Calculates the absorbance per wavenumber
Args:
transmittance: array of transmittances
Returns:
A numpy array of absorbances.
"""
return -np.log(transmittance)
@staticmethod
def calc_snr(interferogram1, interferogram2):
"""
Calculates the signal to noise ratio
Args:
interferogram1: The background spectrum, a np.array
interferogram2: The test sample spectrum, a np.array
Returns:
A signal to noise value
"""
transmittance = Interferogram.calc_transmittance(interferogram1, interferogram2)
noise_rms = np.sqrt(np.mean(np.square(transmittance)))
return 100 / noise_rms
Getting Started
Create an Interferogram object:
# Set filename and directory
DIR = "week2/"
filename = "Step 6/co2_cell_4cm-1 (3min).txt"
# Create Interferogram
co2 = Interferogram(DIR, filename)
Classes
class Interferogram (DIR, filename, sampling=1)-
Interferogram class for all data processing, plotting, and saving.
Attributes
dir- directory holding data file
filename- filename holding data
datestamp- datestamp recording when data was taken
res- resolution of scan
vel- mirror velocity of scan
LP- low pass frequency of scan
HP- high pass frequency of scan
gain- gain of scan
raw_data- parsed interferogram acquisitions of scan
num_acq- number of acquisitions of scan
num_pts- number of points of each acquistion in scan
wavenumbers- wavenumber axis for each acquisiton
spectra- fourier transformed spectra with mertz correction
Expand source code
class Interferogram: """Interferogram class for all data processing, plotting, and saving. Attributes: dir: directory holding data file filename: filename holding data datestamp: datestamp recording when data was taken res: resolution of scan vel: mirror velocity of scan LP: low pass frequency of scan HP: high pass frequency of scan gain: gain of scan raw_data: parsed interferogram acquisitions of scan num_acq: number of acquisitions of scan num_pts: number of points of each acquistion in scan wavenumbers: wavenumber axis for each acquisiton spectra: fourier transformed spectra with mertz correction """ def __init__(self, DIR, filename, sampling=1): self.dir = DIR self.filename = filename with open(DIR+filename) as f: data = f.read().splitlines() # Read in params at the top of the file self.datestamp = data[0].replace("#","").strip() result = re.search(r'\d+', data[1]) self.res = int(result.group()) result = re.search(r'\d+', data[2]) self.vel = int(result.group()) result = re.search(r'\d+', data[3]) self.LP = int(result.group()) result = re.search(r'\d+', data[4]) self.HP = int(result.group()) result = re.search(r'\d+', data[5]) self.gain = int(result.group()) # Read in the raw spectrum data raw = list(filter(None, data[6:])) scan_idx = [i for i in range(0, len(raw)) if "#" in raw[i]] + [len(raw)] self.raw_data = np.array( [np.array(raw[scan_idx[i]+1:scan_idx[i+1]], dtype=np.float) for i in range(0, len(scan_idx)-1)] ) self.num_acq = len(self.raw_data) # Find the wavenumbers self.num_pts = [len(self.raw_data[i]) for i in range(0, self.num_acq)] self.wavenumbers = np.array( [sampling*np.linspace(0, MAX_WAVENUMBER, num=int(self.num_pts[i])/2) for i in range(0, self.num_acq)] ) # Find the spectra mean = np.mean(np.array([x for x in raw if "#" not in x], dtype=np.float)) self.spectra = self.fft(mean) def fft(self, mean): """ Calculates the spectra of the interferogram Args: interferogram2: The test sample spectrum, a np.array Returns: A numpy array of transmittances. Raises: AssertionError: An error occurred if resolutions are different """ new_data = [] for line in self.raw_data: # Normalize data new_line = Interferogram.normalize_raw_data(line, mean) # Zero fill the data new_line = Interferogram.zero_fill(new_line) # Find the phase corrections phase, re_phase, im_phase = self.mertz(new_line) # fft the interferogram inter, re_inter, im_inter = Interferogram.fft_interferogram(line) # Record the spectra multiplied by correction new_data.append(np.absolute(np.multiply(inter, phase))) return np.array(new_data) def mertz(self, data): """ Returns the phase correction data The real and imaginary data arrays are multiplied by the power spectrum Args: data: interferogram to be filled Returns: fft spectrum: A numpy array of data multiplied by the power spectrum re spectrum: A real numpy array of data multiplied by the power spectrum im spectrum: A imaginary numpy array of data multiplied by the power spectrum """ # find burtst location burst = np.argmax(data) # Add the apodization triangular phase correction copied_burst = np.zeros(len(data)) copied_burst[burst-128:burst+128] = data[burst-128:burst+128] copied_burst = np.multiply(copied_burst, Interferogram.triangular_signal(burst, len(copied_burst))) # Slicing to rotate about ZPD copied_burst = np.concatenate((copied_burst[burst:], copied_burst[:burst])) # data is FFT'd, producing real and imaginary data arrays. ffted = np.fft.fft(copied_burst) im_data = ffted.imag re_data = ffted.real # Find power spectrum power = np.absolute(ffted) # The real and imaginary data arrays are multiplied by the power spectrum return np.multiply(power, ffted), np.multiply(re_data, power), np.multiply(im_data, power) @staticmethod def fft_interferogram(data): """ Do the fft for the interferogram, including the apodization, etc. Args: data: interferogram to be filled Returns: fft spectrum: A numpy array of ffted data re spectrum: A real numpy array of ffted data im spectrum: A imaginary numpy array of ffted data """ # find burst location burst = np.argmax(data) # Apodize the whole interferogram with triangular function data = np.multiply(data, Interferogram.triangular_signal(burst, len(data))) # zero fill data = Interferogram.zero_fill(data) # Slicing to rotate about ZPD data = np.concatenate((data[burst:], data[:burst])) # data is FFT'd, producing real and imaginary data arrays. ffted = np.fft.fft(data) return ffted, ffted.real, ffted.imag @staticmethod def zero_fill(data): """ FFT requires number of points in the interferogram be a power of 2. If it is not, then the size of the array is increased to the next higher power of 2 and the added points are set to 0. Args: data: interferogram to be filled Returns: A numpy array of filled interferogram """ for i in range(10, len(POWERS2)): if POWERS2[i] >= len(data): break return np.concatenate((data, np.zeros(POWERS2[i]-len(data)))) @staticmethod def normalize_raw_data(data, mean): """The mean of the data is subtracted from the data, brings baseline of data to 0""" return data - mean @staticmethod def apodize(burst, data): return np.multiply(data, Interferogram.triangular_signal(burst, len(data))) @staticmethod def triangular_signal(center, total_len): window = signal.triang(2*center-1) try: return np.concatenate((window, np.zeros(total_len-len(window)))) except: return window[:total_len] @staticmethod def calc_transmittance(interferogram1, interferogram2): """ Calculates the transmittance per wavenumber Args: interferogram1: The background spectrum, a np.array interferogram2: The test sample spectrum, a np.array Returns: A numpy array of transmittances. Raises: AssertionError: An error occurred if resolutions are different """ assert (len(interferogram1) == len(interferogram2)), 'Background spectrum has {} pts, sample spectrum has {} pts'.format(len(interferogram1), len(interferogram2)) return np.divide(interferogram2, interferogram1) @staticmethod def calc_absorbance(transmittance): """ Calculates the absorbance per wavenumber Args: transmittance: array of transmittances Returns: A numpy array of absorbances. """ return -np.log(transmittance) @staticmethod def calc_snr(interferogram1, interferogram2): """ Calculates the signal to noise ratio Args: interferogram1: The background spectrum, a np.array interferogram2: The test sample spectrum, a np.array Returns: A signal to noise value """ transmittance = Interferogram.calc_transmittance(interferogram1, interferogram2) noise_rms = np.sqrt(np.mean(np.square(transmittance))) return 100 / noise_rmsStatic methods
def apodize(burst, data)-
Expand source code
@staticmethod def apodize(burst, data): return np.multiply(data, Interferogram.triangular_signal(burst, len(data))) def calc_absorbance(transmittance)-
Calculates the absorbance per wavenumber
Args
transmittance- array of transmittances
Returns
A numpy array of absorbances.
Expand source code
@staticmethod def calc_absorbance(transmittance): """ Calculates the absorbance per wavenumber Args: transmittance: array of transmittances Returns: A numpy array of absorbances. """ return -np.log(transmittance) def calc_snr(interferogram1, interferogram2)-
Calculates the signal to noise ratio
Args
interferogram1- The background spectrum, a np.array
interferogram2- The test sample spectrum, a np.array
Returns
Asignaltonoisevalue
Expand source code
@staticmethod def calc_snr(interferogram1, interferogram2): """ Calculates the signal to noise ratio Args: interferogram1: The background spectrum, a np.array interferogram2: The test sample spectrum, a np.array Returns: A signal to noise value """ transmittance = Interferogram.calc_transmittance(interferogram1, interferogram2) noise_rms = np.sqrt(np.mean(np.square(transmittance))) return 100 / noise_rms def calc_transmittance(interferogram1, interferogram2)-
Calculates the transmittance per wavenumber
Args
interferogram1- The background spectrum, a np.array
interferogram2- The test sample spectrum, a np.array
Returns
Anumpyarrayoftransmittances.
Raises
AssertionError- An error occurred if resolutions are different
Expand source code
@staticmethod def calc_transmittance(interferogram1, interferogram2): """ Calculates the transmittance per wavenumber Args: interferogram1: The background spectrum, a np.array interferogram2: The test sample spectrum, a np.array Returns: A numpy array of transmittances. Raises: AssertionError: An error occurred if resolutions are different """ assert (len(interferogram1) == len(interferogram2)), 'Background spectrum has {} pts, sample spectrum has {} pts'.format(len(interferogram1), len(interferogram2)) return np.divide(interferogram2, interferogram1) def fft_interferogram(data)-
Do the fft for the interferogram, including the apodization, etc.
Args: data: interferogram to be filled Returns: fft spectrum: A numpy array of ffted data re spectrum: A real numpy array of ffted data im spectrum: A imaginary numpy array of ffted data
Expand source code
@staticmethod def fft_interferogram(data): """ Do the fft for the interferogram, including the apodization, etc. Args: data: interferogram to be filled Returns: fft spectrum: A numpy array of ffted data re spectrum: A real numpy array of ffted data im spectrum: A imaginary numpy array of ffted data """ # find burst location burst = np.argmax(data) # Apodize the whole interferogram with triangular function data = np.multiply(data, Interferogram.triangular_signal(burst, len(data))) # zero fill data = Interferogram.zero_fill(data) # Slicing to rotate about ZPD data = np.concatenate((data[burst:], data[:burst])) # data is FFT'd, producing real and imaginary data arrays. ffted = np.fft.fft(data) return ffted, ffted.real, ffted.imag def normalize_raw_data(data, mean)-
The mean of the data is subtracted from the data, brings baseline of data to 0
Expand source code
@staticmethod def normalize_raw_data(data, mean): """The mean of the data is subtracted from the data, brings baseline of data to 0""" return data - mean def triangular_signal(center, total_len)-
Expand source code
@staticmethod def triangular_signal(center, total_len): window = signal.triang(2*center-1) try: return np.concatenate((window, np.zeros(total_len-len(window)))) except: return window[:total_len] def zero_fill(data)-
FFT requires number of points in the interferogram be a power of 2. If it is not, then the size of the array is increased to the next higher power of 2 and the added points are set to 0.
Args: data: interferogram to be filled Returns: A numpy array of filled interferogram
Expand source code
@staticmethod def zero_fill(data): """ FFT requires number of points in the interferogram be a power of 2. If it is not, then the size of the array is increased to the next higher power of 2 and the added points are set to 0. Args: data: interferogram to be filled Returns: A numpy array of filled interferogram """ for i in range(10, len(POWERS2)): if POWERS2[i] >= len(data): break return np.concatenate((data, np.zeros(POWERS2[i]-len(data))))
Methods
def fft(self, mean)-
Calculates the spectra of the interferogram
Args
interferogram2- The test sample spectrum, a np.array
Returns
Anumpyarrayoftransmittances.
Raises
AssertionError- An error occurred if resolutions are different
Expand source code
def fft(self, mean): """ Calculates the spectra of the interferogram Args: interferogram2: The test sample spectrum, a np.array Returns: A numpy array of transmittances. Raises: AssertionError: An error occurred if resolutions are different """ new_data = [] for line in self.raw_data: # Normalize data new_line = Interferogram.normalize_raw_data(line, mean) # Zero fill the data new_line = Interferogram.zero_fill(new_line) # Find the phase corrections phase, re_phase, im_phase = self.mertz(new_line) # fft the interferogram inter, re_inter, im_inter = Interferogram.fft_interferogram(line) # Record the spectra multiplied by correction new_data.append(np.absolute(np.multiply(inter, phase))) return np.array(new_data) def mertz(self, data)-
Returns the phase correction data The real and imaginary data arrays are multiplied by the power spectrum
Args: data: interferogram to be filled Returns: fft spectrum: A numpy array of data multiplied by the power spectrum re spectrum: A real numpy array of data multiplied by the power spectrum im spectrum: A imaginary numpy array of data multiplied by the power spectrum
Expand source code
def mertz(self, data): """ Returns the phase correction data The real and imaginary data arrays are multiplied by the power spectrum Args: data: interferogram to be filled Returns: fft spectrum: A numpy array of data multiplied by the power spectrum re spectrum: A real numpy array of data multiplied by the power spectrum im spectrum: A imaginary numpy array of data multiplied by the power spectrum """ # find burtst location burst = np.argmax(data) # Add the apodization triangular phase correction copied_burst = np.zeros(len(data)) copied_burst[burst-128:burst+128] = data[burst-128:burst+128] copied_burst = np.multiply(copied_burst, Interferogram.triangular_signal(burst, len(copied_burst))) # Slicing to rotate about ZPD copied_burst = np.concatenate((copied_burst[burst:], copied_burst[:burst])) # data is FFT'd, producing real and imaginary data arrays. ffted = np.fft.fft(copied_burst) im_data = ffted.imag re_data = ffted.real # Find power spectrum power = np.absolute(ffted) # The real and imaginary data arrays are multiplied by the power spectrum return np.multiply(power, ffted), np.multiply(re_data, power), np.multiply(im_data, power)