''' Useful stuff having to do with measurement processing.
For example, if you want to set up a spectrum transmission baseline, or a weight functional basis
Generally, these states are not device states, but could change from day to day
'''
import numpy as np
from lightlab import logger
[docs]class SpectrumMeasurementAssistant(object):
''' Class for preprocessing measured spectra
Calculates background spectra by 1) smoothing, 2) tuning/splicing, and 3) peak nulling
Also handles resonance finding (This could move to a separate manager or external function)
Interfaces directly with OSA. It DOES NOT set tuning states.
'''
useBgs = ['tuned', 'smoothed', 'const'] # The order matters
bgSmoothDefault = 2.0 # in nm
def __init__(self, nChan=1, arePeaks=False, osaRef=None):
self.nChan = nChan
self.arePeaks = arePeaks
if osaRef is None:
print('Warning: osa is not initialized as of laboratory-state update. It will be phony')
self.osa = osaRef
self.__backgrounds = {}
# self.__bgSmoothed = None # Constant background spectrum due to fading, EDFA profile, etc.
# self.__bgTuned = None # Constant background taking into account resonance tuning
# self.__bgNulled = None # Constant background using resonance shapes
self.peakfinderOptions = {}
self.filtShapesForConvolution = None
[docs] def rawSpect(self, avgCnt=1):
if self.osa is None:
raise Exception('This is a phony instance with no spectrum analyzer present')
return self.osa.spectrum(avgCnt)
[docs] def fgSpect(self, avgCnt=1, raw=None, bgType=None):
''' Returns the current spectrum with background removed.
Also plots so you can see what's going on, if visualize mode was specified
If raw is specified, does not sweep, just removes background
'''
if raw is None:
raw = self.rawSpect(avgCnt=avgCnt)
bg = self.getBgSpect(bgType)
if bgType is None and type(bg) is float and bg == 0:
self.setBgSmoothed(raw)
unbiased = raw - bg
return unbiased
[docs] def resonances(self, spect=None, avgCnt=1):
''' Returns the current wavelengths of detected peaks in order sorted by wavelength.
Uses the simple findPeaks function, but it could later use a convolutive peak finder for more accuracy.
:param spect: if this is specified, then a new spectrum will not be taken
'''
if spect is None:
spect = self.fgSpect(avgCnt=avgCnt)
# Standard peak finder
res = spect.findResonanceFeatures(
expectedCnt=self.nChan, isPeak=self.arePeaks, **self.peakfinderOptions)
# Advanced correlation based peak finder
if self.filtShapesForConvolution is not None:
fineRes, confidence = spect.refineResonanceWavelengths( # pylint: disable=unused-variable
self.filtShapesForConvolution, seedRes=res)
res = fineRes
lamSort = np.argsort([r.lam for r in res])
return res[lamSort]
[docs] def killResonances(self, spect=None, avgCnt=1, fwhmsAround=3.):
'''
'''
if spect is None:
spect = self.fgSpect(avgCnt=avgCnt)
processed = spect.copy()
for r in self.resonances(spect):
segmentAroundPeak = r.lam + r.fwhm * fwhmsAround / 2 * np.array([-1, 1])
processed = processed.deleteSegment(segmentAroundPeak)
return processed
[docs] def fgResPlot(self, spect=None, axis=None, avgCnt=1): # pylint: disable=unused-argument
''' Takes a foreground spectrum, plots it and its peaks.
Currently the axis input is unused.
'''
if spect is None:
spect = self.fgSpect(avgCnt)
res = self.resonances(spect)
spect.simplePlot()
for r in res:
r.simplePlot()
[docs] def setBgConst(self, raw=None):
''' Makes a background the maximum transmission observed '''
if raw is None:
raw = self.rawSpect()
self.__backgrounds['const'] = max(raw.ordi)
self.__backgrounds['smoothed'] = max(raw.ordi)
[docs] def setBgSmoothed(self, raw=None, smoothNm=None):
''' Attempts to find background using a low-pass filter.
Does not return. Stores results in the assistant variables.
'''
if raw is None:
raw = self.rawSpect()
if smoothNm is None:
smoothNm = self.bgSmoothDefault
if self.arePeaks:
logger.debug('Warning fake background spectrum is being used on the drop port')
self.setBgConst(raw)
else:
self.__backgrounds['smoothed'] = raw.lowPass(windowWidth=smoothNm)
[docs] def setBgTuned(self, base, displaced):
''' Insert the pieces of the displaced spectrum into where the peaks are
It is assumed that these spectra were taken with this object's fgSpect method
'''
if self.arePeaks:
logger.debug('Warning fake background spectrum is being used on the drop port')
self.__backgrounds['tuned'] = self.getBgSpect()
return
res = self.resonances(base)
baseRaw = base + self.getBgSpect()
displacedRaw = displaced + self.getBgSpect()
for r in res:
spliceWind = r.lam + 6 * r.fwhm * np.array([-1, 1]) / 2
baseRaw = baseRaw.splice(displacedRaw, segment=spliceWind)
self.__backgrounds['tuned'] = baseRaw
[docs] def setBgNulled(self, filtShapes, avgCnt=3):
''' Uses the peak shape information to null out resonances
This gives the best estimate of background INDEPENDENT of the tuning state.
It is assumed that the fine background taken by tuning is present, and the filter shapes were taken with that
spect should be a foreground spect, but be careful when it is also derived from bgNulled
'''
if self.arePeaks:
logger.debug('Warning, null-based background spectrum not used for drop port. Ignoring')
self.__backgrounds['nulled'] = self.getBgSpect()
else:
spect = self.fgSpect(avgCnt, bgType='tuned')
newBg = spect.copy()
for i, r in enumerate(self.resonances(newBg)):
nulledPiece = spect - filtShapes[i].db().shift(r.lam)
newBg = newBg.splice(nulledPiece)
self.__backgrounds['nulled'] = self.getBgSpect(bgType='tuned') - newBg
[docs] def getBgSpect(self, bgType=None):
preferredOrder = self.useBgs
if bgType is None:
for k in preferredOrder:
try:
return self.__backgrounds[k]
except KeyError:
pass
return 0
# raise Exception('No background spectrum has been taken')
elif bgType in preferredOrder:
try:
return self.__backgrounds[bgType]
except KeyError:
raise KeyError('Background of type \'' + bgType + '\' has not been taken yet.')
else:
raise ValueError('Invalid background token: ' + bgType +
'. Need ' + str(', '.join(preferredOrder)))