Skip to content
Snippets Groups Projects

Functions for Viking spectrometer analysis

Merged Laurent Mercadier requested to merge viking into master
1 file
+ 122
45
Compare changes
  • Side-by-side
  • Inline
@@ -64,6 +64,55 @@ def plot_viking_calibration(spectra, x_pos, nrj, energy_calib,
# -----------------------------------------------------------------------------
# Viking class
class Viking:
"""The Viking analysis (spectrometer used in combination with Andor Newton
camera)
The objects of this class contain the meta-information about the settings
of the spectrometer, not the actual data, except possibly a dark image
for background subtraction.
The actual data is loaded into `xarray`s via the method `from_run()`,
and stays there.
Attributes
----------
PROPOSAL: int
the number of the proposal
X_RANGE: slice
the slice to take in the non-dispersive direction, in pixels.
Defaults to the entire width.
Y_RANGE: slice
the slice to take in the energy dispersive direction
USE_DARK: bool
whether to do dark subtraction. Is initially `False`, magically
switches to `True` if a dark has been loaded, but may be reset.
ENERGY_CALIB: 1D array (len=3)
The 2nd degree polynomial coefficients for calibration from pixel
to energy. Defaults to [0, 1, 0] (no calibration applied).
BL_POLY_DEG: int
the dgree of the polynomial used for baseline subtraction.
Defaults to 1.
BL_SIGNAL_RANGE: list
the dispersive-axis range, defined by an interval [min, max],
to avoid when fitting a polynomial for baseline subtraction.
Multiple ranges can be provided in the form
[[min1, max1], [min2, max2], ...].
FIELDS: list of str
the fields to be loaded from the data. Add additional fields if so
desired.
Example
-------
proposal = 2953
v = Viking(proposal)
v.X_RANGE = slice(0, 1900)
v.Y_RANGE = slice(38, 80)
v.ENERGY_CALIB = [1.47802667e-06, 2.30600328e-02, 5.15884589e+02]
v.BL_SIGNAL_RANGE = [500, 545]
"""
def __init__(self, proposalNB):
self.PROPOSAL = proposalNB
self.FIELDS = ['newton']
@@ -76,7 +125,8 @@ class Viking:
self.X_RANGE = slice(None, None)
self.Y_RANGE = slice(None, None)
# polynomial degree for background subtraction
self.POLY_DEG = 5
self.BL_POLY_DEG = 1
self.BL_SIGNAL_RANGE = []
def set_params(self, **params):
for key, value in params.items():
@@ -85,15 +135,39 @@ class Viking:
def get_params(self, *params):
if not params:
params = ('proposal', 'x_range', 'y_range', 'energy_calib',
'use_dark', 'poly_deg', 'fields')
'use_dark', 'bl_poly_deg', 'bl_signal_range', 'fields')
return {param: getattr(self, param.upper()) for param in params}
def from_run(self, runNB, proposal=None, add_attrs=True):
if proposal is None:
proposal = self.PROPOSAL
def from_run(self, runNB, add_attrs=True):
"""load a run
Load the run `runNB`. A thin wrapper around `toolbox_scs.load`.
Parameters
----------
runNB: int
the run number
add_attrs: bool
if True, adds the camera parameters as attributes to the dataset
(see get_camera_params())
Output
------
ds: xarray Dataset
the dataset containing the camera images
Example
-------
data = v.from_run(145) # load run 145
data1 = v.from_run(145) # load run 145
data2 = v.from_run(155) # load run 155
data = xarray.concat([data1, data2], 'trainId') # combine both
"""
roi = {'newton': {'newton': {'roi': (self.Y_RANGE, self.X_RANGE),
'dim': ['newt_y', 'newt_x']}}}
run, data = tb.load(proposal, runNB=runNB,
run, data = tb.load(self.PROPOSAL, runNB,
fields=self.FIELDS, rois=roi)
data['newton'] = data['newton'].astype(float)
data = data.assign_coords(newt_x=np.polyval(self.ENERGY_CALIB,
@@ -114,11 +188,19 @@ class Viking:
self.USE_DARK = True
def integrate(self, data):
'''
This function calculates the mean over the non-dispersive dimension
to create a spectrum. If the camera parameters are known, the
spectrum is multiplied by the number of photoelectrons per ADC count.
A new variable "spectrum" is added to the data.
'''
imgs = data['newton']
if self.USE_DARK:
imgs = imgs - self.dark_image
spectrum = imgs.mean(dim='newt_y')
data['spectrum'] = data.attrs['photoelectrons_per_count']*spectrum
if 'photoelectrons_per_count' in data.attrs:
spectrum *= data.attrs['photoelectrons_per_count']
data['spectrum'] = spectrum
return data
def get_camera_gain(self, run):
@@ -126,12 +208,12 @@ class Viking:
Get the preamp gain of the camera in the Viking spectrometer for
a specified run.
Parameters:
------
Parameters
----------
run: extra_data DataCollection
information on the run
Output:
Output
------
gain: int
"""
@@ -148,14 +230,14 @@ class Viking:
Sensitivity mode after analysis of runs 1204, 1207 and 1208,
proposal 2937.
Parameters:
------
run: extra_data DataCollection
information on the run
gain: int
the camera preamp gain
Parameters
----------
run: extra_data DataCollection
information on the run
gain: int
the camera preamp gain
Outputs:
Output
------
ret: float
photoelectrons per count
@@ -188,50 +270,45 @@ class Viking:
ret['photoelectrons_per_count'] = self.e_per_counts(run, ret['gain'])
return ret
def removePolyBaseline(self, data, signalRange=[515, 540]):
def removePolyBaseline(self, data):
"""
Removes a polynomial baseline to a signal, assuming a fixed
Removes a polynomial baseline to a spectrum, assuming a fixed
position for the signal.
Parameters
----------
x: array-like, shape(M,)
x-axis
spectra: array-like, shape(M,) or (N, M,)
the signals to subtract a baseline from. If 2d, the signals
are assumed to be stacked on the first axis.
deg: int
the polynomial degree for fitting a baseline
signalRange: list of type(x), length 2
the x-interval where to expect the signal. The baseline is fitted
to all regions except the one defined by the interval.
data: xarray Dataset
The Viking data containing the variable "spectrum"
Output
------
spectra_nobl: array-like, shape(M,) or (N, M,)
the baseline subtracted spectra
data: xarray Dataset
the original dataset with the added variable "spectrum_nobl"
containing the baseline subtracted spectra.
"""
if 'spectrum' not in data:
return
x = data.newt_x
spectra = data['spectrum']
mask = (x < signalRange[0]) | (x > signalRange[1])
if isinstance(x, xr.DataArray):
x_bl = x.where(mask, drop=True)
bl = spectra.sel(newt_x=x_bl)
else:
x_bl = x[mask]
if len(spectra.shape) == 1:
bl = spectra[mask]
mask = xr.ones_like(x, dtype=bool)
if len(self.BL_SIGNAL_RANGE) > 0:
if not hasattr(self.BL_SIGNAL_RANGE[0], '__len__'):
ranges = [self.BL_SIGNAL_RANGE]
else:
bl = spectra[:, mask]
fit = np.polyfit(x_bl, bl.T, self.POLY_DEG)
ranges = self.BL_SIGNAL_RANGE
for xrange in ranges:
mask = mask & ((x < xrange[0]) | (x > xrange[1]))
x_bl = x.where(mask, drop=True)
bl = spectra.sel(newt_x=x_bl)
fit = np.polyfit(x_bl, bl.T, self.BL_POLY_DEG)
if len(spectra.shape) == 1:
return spectra - np.polyval(fit, x)
final_bl = np.empty(spectra.shape)
for t in range(spectra.shape[0]):
final_bl[t] = np.polyval(fit[:, t], x)
data['spectrum_nobg'] = spectra - final_bl
return spectra - final_bl
data['spectrum_nobl'] = spectra - final_bl
return data
def xas(self, data, data_ref, thickness=1, plot=False,
plot_errors=True, xas_ylim=(-1, 3)):
@@ -264,7 +341,7 @@ class Viking:
I0, It, absorptionCoef and their associated errors.
"""
key = 'spectrum_nobg' if 'spectrum_nobg' in data else 'spectrum'
key = 'spectrum_nobl' if 'spectrum_nobl' in data else 'spectrum'
if data['newt_x'].equals(data_ref['newt_x']) is False:
return
spectrum = data[key].mean(dim='trainId')
@@ -303,7 +380,7 @@ class Viking:
"""
This routine determines the calibration coefficients to translate the
camera pixels into energy in eV. The Viking spectrometer is calibrated
using the beamline monochromator: spectra with various monochromatized
using the beamline monochromator: runs with various monochromatized
photon energy are recorded and their peak position on the detector are
determined by Gaussian fitting. The energy vs. position data is then
fitted to a second degree polynomial.
Loading