Skip to content

Cookie box¤

extra.applications.CookieboxCalibration ¤

CookieboxCalibration(xgm_threshold: Union[str, float] = 0.0, auger_start_roi: Optional[int] = None, start_roi: Optional[int] = None, stop_roi: Optional[int] = None, interleaved: Optional[bool] = None, beta: float = 2.0, tilt: float = 0.0, P1: float = 1.0, count_threshold: float = 0, count_samples: int = 500)

Bases: SerializableMixin

Calibrate a set of eTOFs read out using an ADQ digitizer device.

eTOFs provide a 1D trace for all pulses, which can be transformed into traces per pulse by the AdqRawChannel Extra component. However, the trace sample axis is meaningless and needs to be converted into energy using a non-linear function. Usually one data analysis run is taken in which the undulator energy is scanned to obtain this map, which is then applied in the actual analysis run.

The objective is to take a calibration run (where an energy scan is made) and from that run calculate a) the map to be used to interpolate eTOF energy axis from "sample number" into eV; and b) calculate the transmission correction, which corrects the appropriate intensity of the data, given that the eTOF does not measure electrons with equal probability in all energy ranges.

The concrete steps taken when the object is setup (obj = CookieboxCalib() and obj.setup(run, energy_axis)) from a calibration run are: - From a calibration run, derive a sample number (proportional to time-of-flight) to energy non-linear map. - Estimate the transformation Jacobian and use it to correct the spectrum intensity distribution, so that the probability of photo-electrons observed in a range of energies agrees with the probability in the corresponding time-of-flight range. - Calculate a transmission correction due to the pulse energy as taken from the XGM: "Auger+Valence integral/XGM pulse energy".

The concrete steps taken when the obj.apply(other_run) is called with a run to be calibrated are: - Use derived non-linear map to interpolate energy axis per eTOF - Subtract per energy offset per eTOF. - Scale data by the inverse of the absolute value of the Jacobian per eTOF, following the change-of-variable theorem in statistics. - Divide by the transmission per eTOF.

The variables calculated can be visualized using obj.plot_diagnostics(tof_id) and similar other functions for validation and other cross-checks.

Example usage:

# select relevant data
# this is not needed, but useful to be sure all data is correctly matched
ts = "SQS_RR_UTC/TSYS/TIMESERVER:outputBunchPattern"
pes1 = 'SQS_DIGITIZER_UTC4/ADC/1:network'
pes2 = 'SQS_DIGITIZER_UTC5/ADC/1:network'
xgm_source_ctrl = "SQS_DIAG1_XGMD/XGM/DOOCS"
xgm_source = "SQS_DIAG1_XGMD/XGM/DOOCS:output"

energy_source = "SA3_XTD10_MONO/MDL/PHOTON_ENERGY"

# setup calibration runs
calib_run = [open_run(proposal=8697, run=r) for r in range(173, 185)]
calib_run = calib_run[0].union(*calib_run[1:])
calib_run = calib_run.select([ts,
                               pes1,
                               pes2,
                               xgm_source,
                               xgm_source_ctrl,
                               energy_source], require_all=True)
# set up AdqRawChannel object to read data from each eTOF
create_channel = lambda digi, ch: AdqRawChannel(calib_run,
                                                ch,
                                                digitizer=digi,
                                                first_pulse_offset=23300,
                                                single_pulse_length=400,
                                                interleaved=True,
                                                baseline=np.s_[:20000],
                                                )
tof_settings = {
       0: create_channel(pes1, "1_A"),
       1: create_channel(pes1, "1_C"),
       2: create_channel(pes1, "2_A"),
       3: create_channel(pes1, "2_C"),
       4: create_channel(pes1, "3_A"),
       5: create_channel(pes1, "3_C"),
       6: create_channel(pes1, "4_A"),
       7: create_channel(pes1, "4_C"),
       8: create_channel(pes2, "1_A"),
       9: create_channel(pes2, "1_C"),
       10: create_channel(pes2, "2_A"),
       11: create_channel(pes2, "2_C"),
       12: create_channel(pes2, "3_A"),
       13: create_channel(pes2, "3_C"),
       14: create_channel(pes2, "4_A"),
       15: create_channel(pes2, "4_C"),
       }

# define energy axis to interpolate to
energy_axis = np.linspace(968, 1026, 160)

# defie regions of interest
cal = CookieboxCalibration(
                # these were chosen by eye
                # if set to None, automatic discovery is used
                # but it may fail
                auger_start_roi=150,
                start_roi=200,
                stop_roi=320,
)

# do calibration
cal.setup(run=calib_run, energy_axis=energy_axis, tof_settings=tof_settings,
          xgm=XGM(calib_run, "SQS_DIAG1_XGMD/XGM/DOOCS"),
          scan=Scan(calib_run["SA3_XTD10_MONO/MDL/PHOTON_ENERGY", "actualEnergy"]))

# save for later usage
cal.to_file('cookiebox_calib.h5')

# re-use later
cal_read = CookieboxCalibration.from_file('cookiebox_calib.h5')

# open new run and apply it
r188 = open_run(proposal=8697, run=188).select(cal_read.sources, require_all=True).select_trains(np.s_[:5])
r188_cal = cal_read.apply(r188)

# make a plot
plt.plot(r188_cal.sel(tof=4).mean('trainId').mean('pulseIndex').to_numpy())

Parameters:

Name Type Description Default
xgm_threshold Union[str, float]

Minimum threshold to ignore dark frames in the calibration data (in uJ). Can be 'median' to use the median over the run.

0.0
auger_start_roi Optional[int]

Start of the Auger and valence RoI in a pulse, relative to the first_pulse_offset. Use None to guess it.

None
start_roi Optional[int]

Start of the RoI in a pulse, relative to the first_pulse_offset. Use None to guess it.

None
stop_roi Optional[int]

End of the RoI, relative to the first_pulse_offset. Use None to guess it.

None
beta float

Beta parameter. For l=0 electrons, set to 2 for linear polarization, 0 to circular polarization.

2.0
tilt float

Tilt angle for linear or elliptical polarization. It is assumed eTOF 0 makes an angle of 0 deg and therefore, the tilt refers to that angle. Under such assumption, set to 0 for horizontal polarization, or np.pi/2 for vertical linear polarization, if eTOF 0 is aligned to the horizontal plane.

0.0
P1 float

First Stokes parameter. Set to 1 for linear or circular polarization.

1.0
count_threshold float

Threshold for counting photons. Ignored if zero or positive: set to a negative value to use it.

0
count_samples int

If using photon counting (count_threshold < 0), use this many samples to obtain the spectrum.

500

to_file ¤

to_file(filename: str)

Dump all data needed for applying the calibration into an h5 file.

Parameters:

Name Type Description Default
filename str

The output file name.

required

from_file classmethod ¤

from_file(filename: str)

Load setup saved with save previously.

setup ¤

setup(run: DataCollection, energy_axis: ndarray, tof_settings: Dict[int, AdqRawChannel], scan: Scan, xgm: XGM, tof_response: Dict[int, TOFAnalogResponse] = None, parallel=False, parallel_over_tofs=None)

Derive calibrations.

Parameters:

Name Type Description Default
run DataCollection

The calibration run.

required
energy_axis ndarray

Energy axis in eV to interpolate eTOF to.

required
tof_settings Dict[int, AdqRawChannel]

Dictionary with a TOF label as a key (0,1,2, ...). Each value is either a) a tuple containing the eTOF source name and channel in the format "1_A"; or b) the AdqRawChannel object for that eTOF.

required
scan Scan

Scan object created with the energy source scan. For example: Scan(calib_run["SA3_XTD10_MONO/MDL/PHOTON_ENERGY", "actualEnergy.value"])

required
xgm XGM

The XGM object used to apply a pulse energy selection. For example: XGM(run, "SQS_DIAG1_XGMD/XGM/DOOCS")

required
tof_response Dict[int, TOFAnalogResponse]

The response function object for deconvolution if that is desired.

None
parallel

Whether to paralellize data reading.

False
parallel_over_tofs

Whether to parallelize over eTOFs.

None

set_run ¤

set_run(value: DataCollection)

Update the run object and recompute.

Parameters:

Name Type Description Default
value DataCollection

The new run.

required

set_energy_axis ¤

set_energy_axis(value: ndarray)

Update the energy axis and recompute.

Parameters:

Name Type Description Default
value ndarray

New value for the energy axis.

required

set_xgm_threshold ¤

set_xgm_threshold(value: Union[float, str])

Update the XGM threshold value and recompute.

Parameters:

Name Type Description Default
value Union[float, str]

New XGM threshold value. May be number of "median" to take the median of XGM intensities.

required

set_tof_settings ¤

set_tof_settings(value: Dict[int, Union[Tuple[str, str], AdqRawChannel]])

Update the eTOF settings and recompute.

Parameters:

Name Type Description Default
value Dict[int, Union[Tuple[str, str], AdqRawChannel]]

The new eTOF settings as explained in the constructor docstring.

required

set_auger_start_roi ¤

set_auger_start_roi(value: int)

Set the start of the Auger RoI and recompute.

Parameters:

Name Type Description Default
value int

The new sample number for the start of the Auger RoI.

required

set_start_roi ¤

set_start_roi(value: int)

Set the start of the photo-electron RoI and recompute.

Parameters:

Name Type Description Default
value int

The new start of the RoI.

required

set_stop_roi ¤

set_stop_roi(value: int)

Update the end of the photo-electron RoI and recompute.

Parameters:

Name Type Description Default
value int

The end of the photo-electron RoI.

required

update_metadata ¤

update_metadata()

Read calibration XGM and metadata information.

update_tof_settings ¤

update_tof_settings()

Update position of the first pulse offset if needed and create AdqRawChannel.

update_roi ¤

update_roi(parallel=False, parallel_over_tofs=16)

Given calibrated data, apply a selection and find RoI if needed.

Parameters:

Name Type Description Default
parallel

Number of threads to use if parallelizing data reading.

False

update_fit_result ¤

update_fit_result()

Fit TOF data to a Gaussian and collect results.

update_calibration ¤

update_calibration()

Calculate calibration maps and transmission.

select_calibration_data ¤

select_calibration_data(parallel=False, parallel_over_tofs=None)

Select data for calibration.

mask_calibration_point ¤

mask_calibration_point(tof_id: int, energy: float, mask: bool = False, tol: float = 0.01)

If mask is False, the point at a given energy and eTOF is ignored when performing the fit.

Parameters:

Name Type Description Default
tof_id int

eTOF number, as in the tof_settings.

required
energy float

Energy value, in the same units as provided in scan.

required
mask bool

If True, keep the point. If False, remove it.

False
tol float

Tolerance for energy matching.

0.01

find_roi ¤

find_roi(tof_id: int)

Find RoI for the photo-electron peak from calibration data.

Parameters:

Name Type Description Default
tof_id int

The eTOF ID.

required

plot_calibration_data ¤

plot_calibration_data()

Plot data for checks.

peak_tof ¤

peak_tof(tof_id: int) -> TofFitResult

For each energy in tof tof_id, fit a Gaussian and store its parameters.

Parameters:

Name Type Description Default
tof_id int

The eTOF ID.

required

Returns: Energy vector used, means and std. dev. in the sample axis scale, and integral of Gaussian.

calculate_calibration_and_transmission ¤

calculate_calibration_and_transmission(tof_id: int)

Calculate transmissions.

Parameters:

Name Type Description Default
tof_id int

eTOF ID.

required

plot_calibrations ¤

plot_calibrations()

Diagnostics plots for finding the energy peaks in a scan.

plot_transmissions ¤

plot_transmissions()

Plot all transmissions in the same plot.

plot_offsets ¤

plot_offsets()

Plot all offset in the same plot.

plot_jacobians ¤

plot_jacobians(eV_per_sample: bool = True)

Plot all jacobians in the same plot.

plot_fit ¤

plot_fit(tof_id: int, ax=None)

Diagnostics plots for fit.

Parameters:

Name Type Description Default
tof_id int

eTOF ID.

required
ax

Axis in which to plot.

None

load_trace ¤

load_trace(run: DataCollection, **extra_kwargs_adq: Dict[str, Any]) -> xr.Dataset

See load_data.

load_data ¤

load_data(run: DataCollection, preprocess_fn=None, **extra_kwargs_adq: Dict[str, Any]) -> xr.Dataset

Only load region of interest for the same settings in a new run and output a Dataset with it. This is the recommended way to load data from a new run before applying the calibration.

Parameters:

Name Type Description Default
run DataCollection

The run to calibrate.

required
preprocess_fn

Function to apply to an xarray after reading the trace. For example: lambda x: x.mean('pulse')

None
kwargs_adq

Keyword arguments for the AdqRawChannel object if one wishes to override settings.

required
extra_kwargs_adq Dict[str, Any]

Keyword arguments for the AdqRawChannel object if one wishes to override settings.

{}

Returns: An xarray DataArray with the traces containing axes ('trainId', 'pulseIndex', 'sample', 'tof').

calibrate ¤

calibrate(trace: DataArray, subtract_offset: bool = False) -> xr.DataArray

Takes a trace separated with axes ('trainId', 'pulseIndex', 'sample', 'tof'), as given by load_trace and applies the calibration. The method apply provides a direct way to retrieve the trace and calbrate it. The methods load_trace and calibrate allow one to apply an intermediate processing step between them.

Parameters:

Name Type Description Default
trace DataArray

A pre-processed trace retrieved with the same AdqRawChannel settings as this calibration object. Its axes are expected to be ('trainId', 'pulseIndex', 'sample', 'tof'). It is recommended to use always load_trace to obtain this.

required
subtract_offset bool

Whether to subtract a offset.

False

the calibrated data as an xarray DataArray.

Type Description
DataArray

The axes of the output are ('trainId', 'pulseIndex', 'energy', 'tof').

apply ¤

apply(run: DataCollection) -> xr.DataArray

Collect trace from run consistently with the calibration settings and apply calibration, offset correction and transmission correction to a new analysis run. It is assumed it contains the same eTOF settings.

Parameters:

Name Type Description Default
run DataCollection

The run to calibrate.

required

Returns: The xarray Dataset.