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 |
None
|
start_roi
|
Optional[int]
|
Start of the RoI in a pulse, relative to the |
None
|
stop_roi
|
Optional[int]
|
End of the RoI, relative to the |
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 ( |
500
|
to_file ¤
Dump all data needed for applying the calibration into an h5 file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
The output file name. |
required |
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: |
required |
xgm
|
XGM
|
The XGM object used to apply a pulse energy selection.
For example: |
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 ¤
Update the run object and recompute.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
DataCollection
|
The new run. |
required |
set_energy_axis ¤
Update the energy axis and recompute.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
ndarray
|
New value for the energy axis. |
required |
set_xgm_threshold ¤
set_tof_settings ¤
set_auger_start_roi ¤
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 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 ¤
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_tof_settings ¤
Update position of the first pulse offset if needed and create AdqRawChannel.
update_roi ¤
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
|
select_calibration_data ¤
Select data for calibration.
mask_calibration_point ¤
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 |
required |
energy
|
float
|
Energy value, in the same units as provided in |
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 for the photo-electron peak from calibration data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tof_id
|
int
|
The eTOF ID. |
required |
peak_tof ¤
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 ¤
plot_fit ¤
Diagnostics plots for fit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tof_id
|
int
|
eTOF ID. |
required |
ax
|
Axis in which to plot. |
None
|
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: |
None
|
|
kwargs_adq
|
Keyword arguments for the |
required | |
extra_kwargs_adq
|
Dict[str, Any]
|
Keyword arguments for the |
{}
|
Returns: An xarray DataArray with the traces containing axes ('trainId', 'pulseIndex', 'sample', 'tof').
calibrate ¤
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 |
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 ¤
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.