Calibration#
lauelab.calibration fits the rotation and translation of a detector to reflections of a crystal with a known lattice, and writes the result into a copy of a geometry file. The guide contains a complete synthetic workflow. The algorithm page defines the error that the fit minimizes and its weights.
The module consists of functions and has no configured object. A calibration is made a few times a year from one frame, so there is no configuration to reuse between calls. Each function returns a new value and keeps no state.
- lauelab.calibration.calibration_table(peaks, pattern, *, start=(0, 0), group=(1, 1), previous=None)[source]#
Return the calibration table of the peaks that a pattern indexed.
This replaces LaueGo’s
MakeCalibData, which matched each fitted peak to the nearest indexed peak. APatternrecords the peak of each reflection inpk_index, so no position matching is needed.- Parameters:
peaks – The peaks whose scattering vectors were indexed, in the same order: frame
(x, y)coordinates with shape(n, 2), or a structured array withfit_xandfit_yfields such asPeakSearch.peaks.pattern – One pattern returned by
index_orientations().start (tuple[int, int]) – Full-detector origin and pixel grouping of the frame, as
(x, y).group (tuple[int, int]) – Full-detector origin and pixel grouping of the frame, as
(x, y).previous (CalibrationTable | None) – An earlier table of the same frame. Measured energies, their sources, and Miller indices of rows with an energy are copied to the rows of the same peak (same full-detector position within 0.01 pixel).
- Returns:
CalibrationTable – One row per indexed peak, in the order of
pattern.pk_index.- Return type:
- class lauelab.calibration.CalibrationTable(hkl, pixels, energy_kev, energy_source, peak_index)[source]#
Reflections with known Miller indices and their measured values.
Each row is one reflection. A row is used when it has a pixel position, a measured energy, or both, as in LaueGo’s
CalibrationList.- Parameters:
hkl (numpy.ndarray) – Integer Miller indices with shape
(n, 3).pixels (numpy.ndarray) – Zero-based full-detector
(x, y)peak positions with shape(n, 2);NaNwhere the position was not measured.energy_kev (numpy.ndarray) – Measured photon energies in keV with shape
(n,);NaNwhere the energy was not measured.energy_source (tuple[str, ...]) – One free-text note per row, such as the scan in which the energy was measured; an empty string where there is none.
peak_index (numpy.ndarray) – Zero-based index of each row’s peak in the peak array that was indexed, with shape
(n,);-1for a row without a peak.
Notes
The arrays are read-only.
with_energy()andwith_hkl()return changed copies.
- lauelab.calibration.calibrate_detector(table, crystal, reciprocal, detector, *, fit_sample_rotation=True, azimuth_deg=90.0, max_nfev=2000)[source]#
Fit a detector’s rotation and translation to reflections of a known crystal.
This is LaueGo’s detector calibration (
OptimizeAllinCalibration.ipf). For each row oftable, the scattering vector calculated from the crystal and the sample rotation is compared with the measured one: its direction from the pixel position and its length from the measured energy. Peaks with a measured energy have a larger weight,min((n / n_energy)**2, 40).When
fit_sample_rotationis true (detector 0 in LaueGo), the sample rotation is fitted with the detector. A rotation of the sample and the detector together about the incident beam changes no measured value, so an extra term holds the detector azimuth atazimuth_deg, weighted 10 times. When it is false (the other detectors), the sample rotation ofreciprocalis fixed; use therefined_reciprocalof a calibration of detector 0.- Parameters:
table (CalibrationTable) – Measured reflections, such as the result of
calibration_table().crystal (Crystal | str | Path) – The calibration crystal, or the path of its crystal XML file.
reciprocal – Reciprocal basis rows in 1/nm, including 2π, of the sample orientation, such as
Pattern.reciprocal. Its rotation from the crystal reference basis is the starting sample rotation.detector (DetectorGeometry) – Starting detector geometry, from
Geometry.detector.fit_sample_rotation (bool) – Fit the sample rotation with the detector. At least one measured energy is then required, as in LaueGo.
azimuth_deg (float) – Detector azimuth about the incident beam in degrees, used only when
fit_sample_rotationis true. 90 is the 34-ID-E value of LaueGo’s beamline preference.max_nfev (int) – Maximum number of residual evaluations.
- Returns:
DetectorCalibration – The refined detector, sample rotation, per-row errors, and status. A fit that cannot run or does not converge returns a result whose status says why.
- Raises:
InputError – If an argument is malformed.
- Return type:
- class lauelab.calibration.DetectorCalibration(status, message, table, initial, refined, initial_sample_rotation, refined_sample_rotation, fit_sample_rotation, azimuth_deg, rotation_vector_sigma, translation_sigma_um, sample_rotation_sigma, error_start, error_end, n_evaluations, predicted_pixels, pixel_error, angle_error_deg, calculated_energy_kev, energy_error_ev, reference_reciprocal, crystal_name, crystal_source)[source]#
Result of
calibrate_detector().Lengths are in µm, rotation vectors are axis times angle in radians, and rotation matrices are proper, dimensionless
(3, 3)arrays. Per-row arrays follow the rows oftable.- status#
One of
"ok","insufficient_data","energy_mismatch"and"not_converged". Only"ok"results have refined values; the others repeat the initial values.- Type:
- table#
The measured reflections.
- initial, refined
The detector before and after the fit.
refined.rotation_vectorandrefined.translationare rounded as Igor rounds them: the rotation vector to 1e-8 rad, and the translation to the power of ten nearest 1 % of a pixel.
- initial_sample_rotation, refined_sample_rotation
Rotation from the crystal reference basis to the laboratory frame, so that
q = rotation @ reference.T @ hkl. They are equal when the sample rotation was not fitted.
- azimuth_deg#
Required detector azimuth about the incident beam in degrees, or None when the sample rotation was not fitted.
- Type:
float | None
- rotation_vector_sigma, translation_sigma_um, sample_rotation_sigma
Standard errors of the detector rotation vector (rad), translation (µm), and sample rotation about the laboratory x, y and z axes (rad), from the fit residuals and Jacobian. They depend on Igor’s relative weights and are not a calibrated measurement uncertainty.
NaNwhen not fitted or not determined.
- error_start, error_end
Igor’s error, the root of the weighted mean square of the reflection errors (dimensionless, near radians) combined with the azimuth error, before and after the fit.
- predicted_pixels#
Full-detector
(x, y)position of each reflection on the refined detector, with shape(n, 2);NaNoff the detector.- Type:
- pixel_error#
predicted_pixels - table.pixelswith shape(n, 2).- Type:
- angle_error_deg#
Angle between the calculated and the measured scattering vector of each row with a pixel, shape
(n,).- Type:
- calculated_energy_kev#
Energy of each reflection calculated from the refined sample rotation and the crystal, shape
(n,), as Igor’scalculated_keVcolumn.- Type:
- energy_error_ev#
Measured minus calculated energy in eV, shape
(n,).- Type:
- reference_reciprocal#
Reciprocal basis of the crystal reference orientation, rows in 1/nm including 2π.
- Type:
- property refined_reciprocal: numpy.ndarray#
Reciprocal basis rows (1/nm, including 2π) of the refined sample orientation.
Pass it to
calibrate_detector()to calibrate another detector with the sample rotation fixed.
- property sample_rotation_vector: numpy.ndarray#
Refined sample rotation as an axis-angle vector in radians (Igor’s rho).
- property rms_pixel_error: float#
Root-mean-square distance in pixels between predicted and measured positions.
- lauelab.calibration.surface_check(crystal, reciprocal, *, family=(0, 0, 1), expected_normal=(0.0, np.float64(0.7071067811865476), np.float64(-0.7071067811865476)))[source]#
Find where the surface reflection of an oriented crystal points.
The calibration standard at 34-ID-E is assumed to be a Si wafer with a (001) surface whose normal is 45 degrees between the reversed incident beam and the +y axis. Its surface reflection, such as 004, then lies between the incident beam and the beam diffracted toward the detector. A sample rotation applied in the wrong direction (the transpose of the rotation) moves it elsewhere.
- Parameters:
crystal (Crystal | str | Path) – The calibration crystal, or the path of its crystal XML file.
reciprocal – Reciprocal basis rows in 1/nm, including 2π, of the oriented crystal, such as
Pattern.reciprocalorDetectorCalibration.refined_reciprocal.family – Miller indices of the surface reflection. For a cubic crystal every signed permutation is a member of the family; otherwise
familyand its negative are.expected_normal – Expected direction of the surface normal in the 34-ID-E laboratory frame. It is normalized.
- Returns:
SurfaceCheck – The closest family member and its angles.
- Return type:
- class lauelab.calibration.SurfaceCheck(hkl, direction, expected_normal, exit_direction, angle_to_expected_deg, angle_to_incident_deg, angle_to_exit_deg, between)[source]#
Direction of the surface reflection of an oriented crystal.
- hkl#
The member of the requested family whose scattering vector is closest to the expected surface normal, shape
(3,).- Type:
- direction#
Its unit scattering vector in the 34-ID-E laboratory frame, shape
(3,).- Type:
- expected_normal#
The expected surface normal, a unit vector with shape
(3,).- Type:
- exit_direction#
Direction of the beam that the expected surface would reflect specularly, shape
(3,).- Type:
- between#
Whether
directionis closer to both the reversed incident beam and the exit direction than they are to each other, so that it lies between the two beams.- Type:
Notes
For a cubic crystal some member of a family such as {001} is always within about 55 degrees of any direction, so
betweencan be true for a wrong orientation.angle_to_expected_degshows the error: for the transpose of Igor’s perfect-Si rotation it is 29.9 degrees, against 1.1 degrees for the rotation itself.
- lauelab.calibration.write_geometry(source, destination, detectors, *, note, file_note=None, written=None)[source]#
Write a copy of a geometry file with calibrated detectors.
Only these parts of
sourcechange:<R>,<P>,<timeMeasured>and<note>of each detector whose<ID>equals thedetector_idof one ofdetectors;<dateWritten>,<timeWritten>and<EPOCH>;<fileNote>, iffile_noteis given.
Every other byte, including the other detectors, the wire and the sample, is copied. The C programs and Igor find elements by searching the text for their tags, so no new element names are added.
The file is written to a temporary file in the destination folder, read back with
load_geometry()and compared withdetectors, and then linked todestination.- Parameters:
source (str | Path) – The geometry XML file that the calibration started from.
destination (str | Path) – The new file. It must not exist.
detectors (Iterable[DetectorGeometry]) – Calibrated detectors, such as the
refineddetector of aDetectorCalibration.note (str) – Text of each calibrated detector’s
<note>: printable ASCII without<,>or&, at most 99 characters.file_note (str | None) – Text of the file’s
<fileNote>, with the same characters allowed.written (datetime.datetime | None) – Time recorded as the measurement and writing time. The default is the current local time.
- Returns:
pathlib.Path –
destination.- Raises:
InputError – If
sourceis not a geometry XML file with these detectors and unitsradianandmm, contains an XML tag inside a comment, or a note is not allowed.FileExistsError – If
destinationexists.
- Return type:
Path
- lauelab.calibration.fit_peak(image, x, y, *, half_width=10)[source]#
Fit a two-dimensional Gaussian to the peak nearest a chosen position.
This replaces LaueGo’s “Add Gaussian to pkList”. The fit region is the square of
2 * half_width + 1pixels centred on the brightest pixel withinhalf_widthof(x, y). The model is a constant background plus a Gaussian with independent widths along x and y.- Parameters:
- Returns:
numpy.ndarray or None – One peak with shape
(1,)and the fields ofPeakSearch.peaks, so it can be appended to them withnumpy.concatenate(), or None if no peak can be fitted: the fit fails, the amplitude is not positive, or the centre is not inside the fit region.intensis the Gaussian amplitude abovebackground,integralits integral,hwhm_xandhwhm_yits half widths at half maximum in pixels,tiltis zero, andchisqis the mean square fit residual.- Return type:
np.ndarray | None
- lauelab.calibration.peak_directions(detector, pixels)[source]#
Return unit scattering vectors of full-detector pixels for a source at the origin.
- Parameters:
detector (DetectorGeometry) – Detector geometry.
pixels – Zero-based full-detector
(x, y)coordinates with shape(n, 2).
- Returns:
numpy.ndarray – Unit scattering vectors in the 34-ID-E laboratory frame with shape
(n, 3). For a sample at depth zero these are the vectors thatpixels_to_q()returns.- Return type:
- lauelab.calibration.detector_azimuth_deg(detector)[source]#
Return the azimuth of a detector about the incident beam in degrees.
This is Igor’s
AzimuthAngle. For a detector far from the incident beam it is the azimuth of the detector’s z axis; for a detector close to forward or back scattering it is the azimuth of its +x pixel direction.
- lauelab.calibration.rotation_matrix(rotation_vector)[source]#
Return the rotation matrix of an axis-angle rotation vector in radians.
- lauelab.calibration.rotation_vector(matrix)[source]#
Return the axis-angle rotation vector in radians of a rotation matrix.
The angle is in
[0, pi], as in Igor’saxisOfMatrix.
- lauelab.calibration.matrix_from_igor(text)[source]#
Read a 3 x 3 matrix that Igor wrote in a wave note.
Igor writes a matrix such as
rotation_matrix0orrecip_lattice0as{{m00,m10,m20}{m01,m11,m21}{m02,m12,m22}}: each inner group is one column. Reading the groups as rows gives the transpose, which for a rotation is the inverse rotation.- Parameters:
text (str) – The matrix text, with or without spaces and commas between groups.
- Returns:
numpy.ndarray – The matrix with shape
(3, 3), so thatmatrix[i, j]is Igor’sm[i][j].- Raises:
InputError – If the text does not contain three groups of three numbers.
- Return type:
- lauelab.calibration.HC_KEV_NM = 1.239841984#
Convert a string or number to a floating-point number, if possible.
- lauelab.calibration.DEFAULT_SURFACE_NORMAL = (0.0, np.float64(0.7071067811865476), np.float64(-0.7071067811865476))#
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.
- lauelab.calibration.STATUSES = ('ok', 'insufficient_data', 'energy_mismatch', 'not_converged')#
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.
Units and shapes#
Quantity |
Convention |
|---|---|
Pixel positions |
Arrays of shape |
Translations |
µm |
Energies |
keV |
Energy errors |
eV |
Angles |
Degrees. Rotation vectors, the standard errors |
Rotation vectors |
Axis times angle in radians |
Reciprocal matrices |
Rows a*, b*, c* in |
Ownership and errors#
The arrays of a CalibrationTable, a DetectorCalibration and a SurfaceCheck are read-only.
Invalid arguments raise InputError. write_geometry() raises FileExistsError when the destination exists.
A fit that cannot run or does not converge raises no exception. calibrate_detector() returns a DetectorCalibration whose status is not "ok", and the refined values are equal to the initial values.