xrayscatteringtools 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ from .io import *
2
+ from .plotting import *
3
+ from .utils import *
File without changes
@@ -0,0 +1,204 @@
1
+ import numpy as np
2
+ from scipy.interpolate import InterpolatedUnivariateSpline
3
+ from scipy.optimize import curve_fit
4
+ from xrayscatteringtools.utils import theta2q
5
+ from xrayscatteringtools.calib.scattering_corrections import correction_factor
6
+
7
+ def run_geometry_calibration(
8
+ raw_image,
9
+ x,
10
+ y,
11
+ mask,
12
+ theory_q,
13
+ theory_Iq,
14
+ photon_energy_keV,
15
+ initial_guess={'amplitude' : 1,'x0': 0, 'y0': 0, 'z0': 90000},
16
+ polarization = 0,
17
+ mask_center=True,
18
+ mask_center_size=7500,
19
+ bounds = ([0, -10_000, -10_000, 30_000], [np.inf, 10_000, 10_000, 150_000])
20
+ ):
21
+ """
22
+ Perform geometry calibration on a raw detector image using a theoretical scattering pattern.
23
+
24
+ This function fits the detector geometry parameters (x0, y0, z0) by comparing the
25
+ measured image to a theoretical scattering pattern and applying corrections for
26
+ polarization (Thompson), geometry, and angle-of-scattering effects.
27
+
28
+ Parameters
29
+ ----------
30
+ raw_image : ndarray
31
+ Array of measured intensities from the detector.
32
+ x : ndarray
33
+ Array of x-coordinates for each pixel.
34
+ y : ndarray
35
+ Array of y-coordinates for each pixel.
36
+ mask : ndarray of bool
37
+ Boolean mask array indicating which pixels to include in the fit.
38
+ theory_q : ndarray
39
+ 1D array of scattering vector magnitudes corresponding to the theoretical pattern.
40
+ theory_Iq : ndarray
41
+ 1D array of theoretical scattering intensities at each q.
42
+ photon_energy_keV : float
43
+ Photon energy of the X-ray in keV.
44
+ initial_guess : dict, optional
45
+ Initial guess for fit parameters:
46
+ - 'amplitude' : float, scaling of the intensity
47
+ - 'x0', 'y0', 'z0' : float, initial guesses for detector geometry
48
+ Default: {'amplitude': 1, 'x0': 0, 'y0': 0, 'z0': 90000}.
49
+ polarization : float, optional
50
+ Polarization direction (-pi/2 to pi/2). Default is 0 (horizontally polarized).
51
+ mask_center : bool, optional
52
+ If True, exclude pixels near the center from the fit. Default is True.
53
+ mask_center_size : float, optional
54
+ Radius (in pixels) of the excluded central region. Default is 7500.
55
+ fit_bounds : tuple of array_like, optional
56
+ Lower and upper bounds for the fit parameters. Default is
57
+ ([0, -10000, -10000, 30000, -π/2], [inf, 10000, 10000, 150000, π/2]).
58
+
59
+ Returns
60
+ -------
61
+ fit : ndarray
62
+ 3D array representing the fitted detector image (reshaped to match input dimensions).
63
+ popt : ndarray
64
+ Optimized fit parameters: [amplitude, x0, y0, z0]. In units of microns.
65
+ pcov : ndarray
66
+ Covariance matrix of the optimized parameters.
67
+
68
+ Notes
69
+ -----
70
+ The returned z0 distance must be converted to millimeters when editing the producer. It should not be in microns.
71
+ The fitting applies:
72
+ - `thompson_correction` for polarization effects,
73
+ - `geometry_correction` for geometric distortions,
74
+ - `correction_factor` for angle-of-scattering corrections (Lingyu Ma et al 2024 J. Phys. B: At. Mol. Opt. Phys. 57 205602).
75
+
76
+ Examples
77
+ --------
78
+ >>> fit, popt, pcov = run_geometry_calibration(raw_image, x, y, mask, theory_q, theory_Iq, 12.7)
79
+ """
80
+ # Step one: generate an interpolation for the theory pattern.
81
+ theory_interpolation = InterpolatedUnivariateSpline(theory_q, theory_Iq, ext=3) # Extrapolation 3. Returns Boundary value if outside of range.
82
+
83
+ # Step two: Define initial guess, bounds, and fitting function using a wrapper
84
+ p0 = [initial_guess['amplitude'], initial_guess['x0'], initial_guess['y0'], initial_guess['z0']]
85
+ def fitting_function(xy, amplitude, x0, y0, z0):
86
+ return model(xy, amplitude, x0, y0, z0, polarization, photon_energy_keV, theory_interpolation)
87
+
88
+ # Step three: Masking and formatting variables
89
+ center_mask = np.ones_like(raw_image, dtype=bool)
90
+ if mask_center:
91
+ center_mask[np.sqrt(x**2 + y**2) < mask_center_size] = False
92
+
93
+ masked_data = np.ravel(raw_image[mask & center_mask])
94
+ x_masked = np.ravel(x[mask & center_mask])
95
+ y_masked = np.ravel(y[mask & center_mask])
96
+ xy_masked = [x_masked, y_masked]
97
+
98
+ # Step four: fitting the data
99
+ popt, pcov = curve_fit(fitting_function, xy_masked, masked_data, p0=p0, bounds=bounds)
100
+ fit = fitting_function([np.ravel(x), np.ravel(y)], *popt).reshape(raw_image.shape)
101
+ return fit, popt, pcov
102
+
103
+ def model(xy, amplitude, x0, y0, z0, phi0, photon_energy_keV, theory_interpolation):
104
+ """
105
+ Calculate the theoretical detector image for given geometry parameters.
106
+
107
+ Parameters
108
+ ----------
109
+ xy : list of ndarray
110
+ [x, y] 2D arrays of pixel coordinates.
111
+ amplitude : float
112
+ Scaling factor for intensity.
113
+ x0, y0, z0 : float
114
+ Detector geometry parameters (pixel offsets and distance).
115
+ phi0 : float
116
+ Azimuthal rotation angle in radians.
117
+ photon_energy_keV : float
118
+ Photon energy in keV.
119
+ theory_interpolation : callable
120
+ Interpolated function for theoretical scattering intensities versus q.
121
+
122
+ Returns
123
+ -------
124
+ fit : ndarray
125
+ Flattened array of predicted intensities for each pixel.
126
+
127
+ """
128
+ # Pull out the x and y arrays from the input
129
+ x = xy[0]
130
+ y = xy[1]
131
+ # Center the arrays around x0 and y0
132
+ centered_x = x - x0
133
+ centered_y = y - y0
134
+ # Calculate the r array
135
+ r_matrix = np.sqrt(centered_x**2 + centered_y**2)
136
+ theta_matrix = np.arctan(r_matrix/z0)
137
+ q_matrix = theta2q(theta_matrix, photon_energy_keV)
138
+
139
+ # Calculate corrections
140
+ corrections = np.ones_like(q_matrix)
141
+ corrections *= thompson_correction(centered_x, centered_y, z0, phi0) # Polarization
142
+ corrections *= geometry_correction(centered_x, centered_y, z0) # Geometry
143
+ corrections *= correction_factor(q_matrix, photon_energy_keV) # Angle-Of-Scattering, Lingyu Ma et al 2024 J. Phys. B: At. Mol. Opt. Phys. 57 205602
144
+ fit = amplitude * theory_interpolation(q_matrix) * corrections
145
+ return fit
146
+
147
+ def thompson_correction(x, y, z0, phi0):
148
+ """
149
+ Calculate the Thompson polarization correction for each pixel.
150
+
151
+ Parameters
152
+ ----------
153
+ x, y : ndarray
154
+ Pixel coordinates relative to detector origin.
155
+ z0 : float
156
+ Detector distance along beam axis.
157
+ phi0 : float
158
+ Azimuthal rotation angle in radians.
159
+
160
+ Returns
161
+ -------
162
+ correction : ndarray
163
+ Thompson correction factor for each pixel.
164
+
165
+ Notes
166
+ -----
167
+ See Lingyu Ma et al 2024 J. Phys. B: At. Mol. Opt. Phys. 57 205602
168
+
169
+ """
170
+ # Calculate the Thompson scattering correction factor
171
+ r_matrix = np.sqrt(x**2 + y**2)
172
+ theta = np.arctan(r_matrix/z0)
173
+ phi = np.arctan2(y, x) + phi0
174
+ correction = np.sin(phi)**2+np.cos(theta)**2*np.cos(phi)**2
175
+ return correction
176
+
177
+ def geometry_correction(x, y, z0):
178
+ """
179
+ Compute geometric correction factor (cos^3(theta)) for a detector.
180
+
181
+ Parameters
182
+ ----------
183
+ x, y : ndarray
184
+ Pixel coordinates relative to detector origin.
185
+ z0 : float
186
+ Detector distance along beam axis.
187
+
188
+ Returns
189
+ -------
190
+ correction : ndarray
191
+ Geometric correction factor for each pixel.
192
+
193
+ Notes
194
+ -----
195
+ The geometry correction comes from the inverse square law and the effective area of the pixel.
196
+ The inverse square law accounts for cos^2(theta), and the effective area is another cos(theta).
197
+ See Lingyu Ma et al 2024 J. Phys. B: At. Mol. Opt. Phys. 57 205602
198
+
199
+
200
+ """
201
+ r_matrix = np.sqrt(x**2 + y**2)
202
+ theta = np.arctan(r_matrix / z0)
203
+ correction = np.cos(theta) ** 3
204
+ return correction
@@ -0,0 +1,488 @@
1
+ from scipy.interpolate import InterpolatedUnivariateSpline
2
+ from xrayscatteringtools.utils import q2theta
3
+ import numpy as np
4
+
5
+ def correction_factor(
6
+ q_arr,
7
+ keV,
8
+ L=2.4e-3,
9
+ tSi=318.5e-6,
10
+ tK=8e-6,
11
+ tAl=4.5e-6,
12
+ tBe=100e-6,
13
+ rBe=250e-6,
14
+ tP=125e-6,
15
+ rP=125e-6
16
+ ):
17
+ """
18
+ Calculate the total X-ray scattering correction factor for a given setup.
19
+
20
+ The total correction factor accounts for absorption and scattering
21
+ from different materials in the beam path, including Si, Kapton,
22
+ Al, Be, and the gas cell itself.
23
+
24
+ Parameters
25
+ ----------
26
+ qbins : array-like
27
+ Array of momentum transfer (q) values in Å⁻¹ or equivalent units.
28
+ keV : float
29
+ Photon energy in keV.
30
+ L : float, optional
31
+ Gas cell length in meters. Default is 2.4e-3 m.
32
+ tSi : float, optional
33
+ Thickness of silicon in meters. Default is 318.5e-6 m.
34
+ tK : float, optional
35
+ Thickness of Kapton in meters. Default is 8e-6 m.
36
+ tAl : float, optional
37
+ Thickness of aluminum in meters. Default is 4.5e-6 m.
38
+ tBe : float, optional
39
+ Thickness of beryllium in meters. Default is 100e-6 m.
40
+ rBe : float, optional
41
+ Radius of the Be window in meters. Default is 250e-6 m.
42
+ tP : float, optional
43
+ Thickness of the gas cell in meters. Default is 125e-6 m.
44
+ rP : float, optional
45
+ Hole radius of the gas cell platinum pinhole in meters. Default is 125e-6 m.
46
+
47
+ Returns
48
+ -------
49
+ numpy.ndarray
50
+ Array of total correction factors corresponding to each q value.
51
+
52
+ Notes
53
+ -----
54
+ The total correction factor is computed as the product of individual
55
+ material corrections:
56
+
57
+ total_correction = Si_correction * KaptonHN_correction *
58
+ Al_correction * Be_correction * cell_correction
59
+ """
60
+ return (
61
+ Si_correction(q_arr, keV, tSi) *
62
+ KaptonHN_correction(q_arr, keV, tK) *
63
+ Al_correction(q_arr, keV, tAl) *
64
+ Be_correction(q_arr, keV, tBe, rBe, L) *
65
+ cell_correction(q_arr, keV, tP, rP, L)
66
+ )
67
+
68
+ def Si_correction(q_arr, keV, tSi=318.5e-6):
69
+ """
70
+ Calculate the Silicon correction factor.
71
+
72
+ Parameters
73
+ ----------
74
+ q_arr : array-like
75
+ Array of momentum transfer (q) values.
76
+ keV : float
77
+ Photon energy in keV.
78
+ tSi : float, optional
79
+ Silicon thickness in meters. Default is 318.5e-6 m.
80
+
81
+ Returns
82
+ -------
83
+ numpy.ndarray
84
+ Silicon correction factor for each q value.
85
+
86
+ Notes
87
+ -----
88
+ Uses the angle-dependent absorption formula:
89
+ nSi = (1 - exp(-tSi / (λ_Si * cos(theta)))) / (1 - exp(-tSi / λ_Si))
90
+ where λ_Si is the X-ray attenuation length for silicon at the given energy.
91
+ """
92
+ Silen = Si_attenuation_length(keV)
93
+ thetas = q2theta(q_arr, keV) # In this implementation, the theta here is the same as 2theta in Ma et al.
94
+ nSi = (1 - np.exp(- tSi / (Silen * np.cos(thetas)))) / (1 - np.exp(- tSi / Silen))
95
+ return nSi
96
+
97
+ def KaptonHN_correction(q_arr, keV, tK=8e-6):
98
+ """
99
+ Calculate the Kapton (HN) correction factor.
100
+
101
+ Parameters
102
+ ----------
103
+ q_arr : array-like
104
+ Array of momentum transfer (q) values.
105
+ keV : float
106
+ Photon energy in keV.
107
+ tK : float, optional
108
+ Kapton thickness in meters. Default is 8e-6 m.
109
+
110
+ Returns
111
+ -------
112
+ numpy.ndarray
113
+ Kapton correction factor for each q value.
114
+
115
+ Notes
116
+ -----
117
+ Uses the angle-dependent exponential attenuation:
118
+ nK = exp(-tK / (λ_K * cos(theta))) / exp(-tK / λ_K)
119
+ where λ_K is the X-ray attenuation length for Kapton at the given energy.
120
+ """
121
+ Klen = KaptonHN_attenuation_length(keV)
122
+ thetas = q2theta(q_arr, keV) # In this implementation, the theta here is the same as 2theta in Ma et al.
123
+ nK = np.exp(- tK / (Klen * np.cos(thetas))) / np.exp(- tK / Klen)
124
+ return nK
125
+
126
+ def Al_correction(q_arr, keV, tAl=4.5e-6):
127
+ """
128
+ Calculate the Aluminum correction factor.
129
+
130
+ Parameters
131
+ ----------
132
+ q_arr : array-like
133
+ Array of momentum transfer (q) values.
134
+ keV : float
135
+ Photon energy in keV.
136
+ tAl : float, optional
137
+ Aluminum thickness in meters. Default is 4.5e-6 m.
138
+
139
+ Returns
140
+ -------
141
+ numpy.ndarray
142
+ Aluminum correction factor for each q value.
143
+
144
+ Notes
145
+ -----
146
+ Uses the angle-dependent exponential attenuation:
147
+ nAl = exp(-tAl / (λ_Al * cos(theta))) / exp(-tAl / λ_Al)
148
+ where λ_Al is the X-ray attenuation length for Aluminum at the given energy.
149
+ """
150
+ Allen = Al_attenuation_length(keV)
151
+ thetas = q2theta(q_arr, keV) # In this implementation, the theta here is the same as 2theta in Ma et al.
152
+ nAl = np.exp(- tAl / (Allen * np.cos(thetas))) / np.exp(- tAl / Allen)
153
+ return nAl
154
+
155
+
156
+ def Be_correction(q_arr, keV, tBe=100e-6, rBe=250e-6, L=2.4e-3):
157
+ """
158
+ Calculate the Beryllium correction factor considering window geometry.
159
+
160
+ Parameters
161
+ ----------
162
+ q_arr : array-like
163
+ Array of momentum transfer (q) values.
164
+ keV : float
165
+ Photon energy in keV.
166
+ tBe : float, optional
167
+ Beryllium thickness in meters. Default is 100e-6 m.
168
+ rBe : float, optional
169
+ Radius of the Be window in meters. Default is 250e-6 m.
170
+ L : float, optional
171
+ Gas cell length in meters. Default is 2.4e-3 m.
172
+
173
+ Returns
174
+ -------
175
+ numpy.ndarray
176
+ Beryllium correction factor for each q value.
177
+
178
+ Notes
179
+ -----
180
+ Accounts for partial path length through the Be window depending on
181
+ scattering angle.
182
+ """
183
+ # Compute attenuation length and scattering angles
184
+ Belen = Be_attenuation_length(keV)
185
+ thetas = q2theta(q_arr, keV) # same shape as q_arr
186
+
187
+ # Compute partial path length through Be window
188
+ xBe = np.minimum(rBe / np.tan(thetas), L)
189
+
190
+ # Avoid division by zero for theta=0
191
+ xBe = np.nan_to_num(xBe, nan=L, posinf=L, neginf=L)
192
+
193
+ # Compute correction factor
194
+ nBe = xBe / L + (L - xBe) / L * np.exp(-tBe / (Belen * np.cos(thetas)))
195
+ return nBe
196
+
197
+ def cell_correction(q_arr, keV, tP=125e-6, rP=125e-6, L=2.4e-3):
198
+ """
199
+ Calculate the gas cell geometry correction factor.
200
+
201
+ Parameters
202
+ ----------
203
+ q_arr : array-like
204
+ Array of momentum transfer (q) values.
205
+ keV : float
206
+ Photon energy in keV.
207
+ tP : float, optional
208
+ Gas cell thickness in meters. Default is 125e-6 m.
209
+ rP : float, optional
210
+ Radius of the gas cell window in meters. Default is 125e-6 m.
211
+ L : float, optional
212
+ Gas cell length in meters. Default is 2.4e-3 m.
213
+
214
+ Returns
215
+ -------
216
+ numpy.ndarray
217
+ Cell geometry correction factor for each q value.
218
+
219
+ Notes
220
+ -----
221
+ Accounts for the angle-dependent path length and geometry of the gas cell.
222
+ """
223
+ # Compute scattering angles
224
+ thetas = q2theta(q_arr, keV) # same shape as q_arr
225
+
226
+ # Precompute helper array
227
+ xmax = tP - rP / np.tan(thetas)
228
+
229
+ # Mask to separate the two cases
230
+ cond = np.tan(thetas) >= (rP / tP)
231
+
232
+ # Initialize output
233
+ nCell = np.empty_like(q_arr, dtype=float)
234
+
235
+ # Case 1: tan(theta) >= rP/tP
236
+ nCell[cond] = 1 + (rP / (L * np.tan(thetas[cond])))
237
+
238
+ # Case 2: tan(theta) < rP/tP
239
+ num = tP + (rP * xmax[~cond]) / (xmax[~cond] - rP)
240
+ nCell[~cond] = 1 + num / L
241
+
242
+ # Handle any division-by-zero or invalid values safely
243
+ nCell = np.nan_to_num(nCell, nan=1.0, posinf=1.0, neginf=1.0)
244
+ return nCell
245
+
246
+
247
+ def Si_attenuation_length(keV):
248
+ """
249
+ Calculate the X-ray attenuation length of Silicon (Si) in meters.
250
+
251
+ Parameters
252
+ ----------
253
+ keV : float
254
+ Photon energy in keV.
255
+
256
+ Returns
257
+ -------
258
+ float
259
+ Attenuation length of Silicon in meters.
260
+
261
+ Notes
262
+ -----
263
+ Uses a spline interpolation of tabulated data. The input energy in keV
264
+ is converted to eV for the interpolation, and the returned value is converted
265
+ from microns to meters.
266
+ """
267
+ E_values = [
268
+ 6000.00, 6140.00, 6280.00, 6420.00, 6560.00, 6700.00, 6840.00, 6980.00,
269
+ 7120.00, 7260.00, 7400.00, 7540.00, 7680.00, 7820.00, 7960.00, 8100.00,
270
+ 8240.00, 8380.00, 8520.00, 8660.00, 8800.00, 8940.00, 9080.00, 9220.00,
271
+ 9360.00, 9500.00, 9640.00, 9780.00, 9920.00, 10060.0, 10200.0, 10340.0,
272
+ 10480.0, 10620.0, 10760.0, 10900.0, 11040.0, 11180.0, 11320.0, 11460.0,
273
+ 11600.0, 11740.0, 11880.0, 12020.0, 12160.0, 12300.0, 12440.0, 12580.0,
274
+ 12720.0, 12860.0, 13000.0, 13140.0, 13280.0, 13420.0, 13560.0, 13700.0,
275
+ 13840.0, 13980.0, 14120.0, 14260.0, 14400.0, 14540.0, 14680.0, 14820.0,
276
+ 14960.0, 15100.0, 15240.0, 15380.0, 15520.0, 15660.0, 15800.0, 15940.0,
277
+ 16080.0, 16220.0, 16360.0, 16500.0, 16640.0, 16780.0, 16920.0, 17060.0,
278
+ 17200.0, 17340.0, 17480.0, 17620.0, 17760.0, 17900.0, 18040.0, 18180.0,
279
+ 18320.0, 18460.0, 18600.0, 18740.0, 18880.0, 19020.0, 19160.0, 19300.0,
280
+ 19440.0, 19580.0, 19720.0, 19860.0, 20000.0
281
+ ] # eV
282
+
283
+ length = [
284
+ 30.2985, 32.3736, 34.5409, 36.8058, 39.1665, 41.6272, 44.1903, 46.8565,
285
+ 49.6295, 52.5100, 55.5013, 58.6048, 61.8219, 65.1565, 68.6095, 72.1834,
286
+ 75.8799, 79.7013, 83.6497, 87.7275, 91.9370, 96.2790, 100.757, 105.373,
287
+ 110.128, 115.025, 120.067, 125.254, 130.590, 136.076, 141.715, 147.509,
288
+ 153.460, 159.570, 165.839, 172.274, 178.872, 185.637, 192.574, 199.679,
289
+ 206.960, 214.417, 222.051, 229.865, 237.861, 246.040, 254.404, 262.959,
290
+ 271.704, 280.639, 289.770, 299.096, 308.619, 318.347, 328.274, 338.404,
291
+ 348.743, 359.292, 370.049, 381.017, 392.198, 403.601, 415.221, 427.057,
292
+ 439.120, 451.410, 463.930, 476.677, 489.653, 502.866, 516.312, 529.997,
293
+ 543.916, 558.080, 572.484, 587.133, 602.027, 617.169, 632.558, 648.196,
294
+ 664.092, 680.241, 696.650, 713.315, 730.242, 747.427, 764.877, 782.593,
295
+ 800.574, 818.826, 837.346, 856.145, 875.212, 894.559, 914.172, 934.073,
296
+ 954.240, 974.706, 995.446, 1016.48, 1037.80
297
+ ] # Micron
298
+ Mu_Spline = InterpolatedUnivariateSpline(E_values, length)
299
+ return Mu_Spline(keV*1000) * 1e-6 # Convert to m
300
+
301
+ def Al_attenuation_length(keV):
302
+ """
303
+ Calculate the X-ray attenuation length of Aluminum (Al) for a given photon energy.
304
+
305
+ Parameters
306
+ ----------
307
+ keV : float
308
+ Photon energy in keV.
309
+
310
+ Returns
311
+ -------
312
+ float
313
+ Attenuation length of Al in meters.
314
+
315
+ Notes
316
+ -----
317
+ Uses a spline interpolation of tabulated data to compute the attenuation
318
+ length. Tabulated values are converted from microns to meters.
319
+ """
320
+ E_values = [
321
+ 6000.00, 6140.00, 6280.00, 6420.00, 6560.00, 6700.00, 6840.00, 6980.00,
322
+ 7120.00, 7260.00, 7400.00, 7540.00, 7680.00, 7820.00, 7960.00, 8100.00,
323
+ 8240.00, 8380.00, 8520.00, 8660.00, 8800.00, 8940.00, 9080.00, 9220.00,
324
+ 9360.00, 9500.00, 9640.00, 9780.00, 9920.00, 10060.0, 10200.0, 10340.0,
325
+ 10480.0, 10620.0, 10760.0, 10900.0, 11040.0, 11180.0, 11320.0, 11460.0,
326
+ 11600.0, 11740.0, 11880.0, 12020.0, 12160.0, 12300.0, 12440.0, 12580.0,
327
+ 12720.0, 12860.0, 13000.0, 13140.0, 13280.0, 13420.0, 13560.0, 13700.0,
328
+ 13840.0, 13980.0, 14120.0, 14260.0, 14400.0, 14540.0, 14680.0, 14820.0,
329
+ 14960.0, 15100.0, 15240.0, 15380.0, 15520.0, 15660.0, 15800.0, 15940.0,
330
+ 16080.0, 16220.0, 16360.0, 16500.0, 16640.0, 16780.0, 16920.0, 17060.0,
331
+ 17200.0, 17340.0, 17480.0, 17620.0, 17760.0, 17900.0, 18040.0, 18180.0,
332
+ 18320.0, 18460.0, 18600.0, 18740.0, 18880.0, 19020.0, 19160.0, 19300.0,
333
+ 19440.0, 19580.0, 19720.0, 19860.0, 20000.0
334
+ ] # eV
335
+
336
+ length = [
337
+ 33.5963, 35.9138, 38.3384, 40.8699, 43.5113, 46.2649, 49.1331, 52.1183,
338
+ 55.2230, 58.4497, 61.8005, 65.2780, 68.8848, 72.6228, 76.4952, 80.5027,
339
+ 84.6497, 88.9378, 93.3698, 97.9478, 102.673, 107.549, 112.580, 117.765,
340
+ 123.108, 128.611, 134.276, 140.108, 146.102, 152.272, 158.613, 165.131,
341
+ 171.824, 178.696, 185.752, 192.991, 200.418, 208.034, 215.842, 223.844,
342
+ 232.042, 240.437, 249.034, 257.835, 266.841, 276.056, 285.482, 295.117,
343
+ 304.970, 315.038, 325.324, 335.837, 346.572, 357.532, 368.723, 380.144,
344
+ 391.796, 403.686, 415.811, 428.178, 440.785, 453.638, 466.735, 480.075,
345
+ 493.674, 507.531, 521.648, 536.026, 550.660, 565.562, 580.723, 596.154,
346
+ 611.848, 627.816, 644.054, 660.571, 677.362, 694.432, 711.784, 729.418,
347
+ 747.339, 765.542, 784.034, 802.811, 821.888, 841.256, 860.921, 880.880,
348
+ 901.138, 921.698, 942.559, 963.730, 985.201, 1006.98, 1029.06, 1051.47,
349
+ 1074.17, 1097.20, 1120.53, 1144.28, 1168.39
350
+ ] # Micron
351
+ Mu_Spline = InterpolatedUnivariateSpline(E_values, length)
352
+ return Mu_Spline(keV*1000) * 1e-6 # Convert to meters
353
+
354
+ def Be_attenuation_length(keV):
355
+ """
356
+ Calculate the X-ray attenuation length of Beryllium (Be) for a given photon energy.
357
+
358
+ Parameters
359
+ ----------
360
+ keV : float
361
+ Photon energy in keV.
362
+
363
+ Returns
364
+ -------
365
+ float
366
+ Attenuation length of Be in meters.
367
+
368
+ Notes
369
+ -----
370
+ Computed via spline interpolation of tabulated attenuation data (in microns),
371
+ which is converted to meters.
372
+ """
373
+ E_values = [
374
+ 6000.00, 6140.00, 6280.00, 6420.00, 6560.00, 6700.00, 6840.00, 6980.00,
375
+ 7120.00, 7260.00, 7400.00, 7540.00, 7680.00, 7820.00, 7960.00, 8100.00,
376
+ 8240.00, 8380.00, 8520.00, 8660.00, 8800.00, 8940.00, 9080.00, 9220.00,
377
+ 9360.00, 9500.00, 9640.00, 9780.00, 9920.00, 10060.0, 10200.0, 10340.0,
378
+ 10480.0, 10620.0, 10760.0, 10900.0, 11040.0, 11180.0, 11320.0, 11460.0,
379
+ 11600.0, 11740.0, 11880.0, 12020.0, 12160.0, 12300.0, 12440.0, 12580.0,
380
+ 12720.0, 12860.0, 13000.0, 13140.0, 13280.0, 13420.0, 13560.0, 13700.0,
381
+ 13840.0, 13980.0, 14120.0, 14260.0, 14400.0, 14540.0, 14680.0, 14820.0,
382
+ 14960.0, 15100.0, 15240.0, 15380.0, 15520.0, 15660.0, 15800.0, 15940.0,
383
+ 16080.0, 16220.0, 16360.0, 16500.0, 16640.0, 16780.0, 16920.0, 17060.0,
384
+ 17200.0, 17340.0, 17480.0, 17620.0, 17760.0, 17900.0, 18040.0, 18180.0,
385
+ 18320.0, 18460.0, 18600.0, 18740.0, 18880.0, 19020.0, 19160.0, 19300.0,
386
+ 19440.0, 19580.0, 19720.0, 19860.0, 20000.0
387
+ ]
388
+
389
+ length = [
390
+ 2236.49, 2400.58, 2571.82, 2751.11, 2938.12, 3132.45, 3334.08, 3542.98,
391
+ 3759.08, 3982.32, 4212.59, 4449.79, 4693.79, 4944.48, 5201.64, 5465.44,
392
+ 5735.56, 6012.75, 6296.96, 6586.83, 6882.13, 7182.60, 7487.94, 7797.85,
393
+ 8112.04, 8430.21, 8752.01, 9077.13, 9405.23, 9737.18, 10073.2, 10411.6,
394
+ 10752.0, 11094.1, 11437.5, 11782.0, 12127.2, 12472.7, 12818.3, 13163.7,
395
+ 13508.5, 13852.4, 14195.1, 14536.5, 14876.0, 15213.7, 15549.8, 15885.7,
396
+ 16218.9, 16549.2, 16876.3, 17200.0, 17520.2, 17836.7, 18149.3, 18457.8,
397
+ 18762.0, 19062.0, 19357.5, 19648.4, 19934.7, 20216.2, 20492.9, 20764.7,
398
+ 21031.4, 21302.6, 21572.9, 21838.7, 22099.9, 22356.7, 22608.8, 22856.4,
399
+ 23099.4, 23337.8, 23572.2, 23803.1, 24029.4, 24251.3, 24468.5, 24681.1,
400
+ 24889.3, 25093.0, 25292.3, 25487.3, 25678.0, 25864.3, 26046.5, 26224.5,
401
+ 26398.4, 26568.2, 26734.0, 26895.9, 27054.0, 27208.2, 27358.8, 27505.5,
402
+ 27648.7, 27788.3, 27924.5, 28057.2, 28186.6
403
+ ]
404
+
405
+ Mu_Spline = InterpolatedUnivariateSpline(E_values, length)
406
+ return Mu_Spline(keV*1000) * 1e-6 # Convert to m
407
+
408
+ def KaptonHN_attenuation_length(keV):
409
+ """
410
+ Calculate the X-ray attenuation length of Kapton HN for a given photon energy.
411
+
412
+ Parameters
413
+ ----------
414
+ keV : float
415
+ Photon energy in keV.
416
+
417
+ Returns
418
+ -------
419
+ float
420
+ Attenuation length of Kapton HN in meters.
421
+
422
+ Notes
423
+ -----
424
+ Uses spline interpolation of tabulated attenuation lengths (in microns),
425
+ then converts them to meters.
426
+ """
427
+ E_values = [
428
+ 6000.00, 6140.00, 6280.00, 6420.00, 6560.00, 6700.00, 6840.00, 6980.00,
429
+ 7120.00, 7260.00, 7400.00, 7540.00, 7680.00, 7820.00, 7960.00, 8100.00,
430
+ 8240.00, 8380.00, 8520.00, 8660.00, 8800.00, 8940.00, 9080.00, 9220.00,
431
+ 9360.00, 9500.00, 9640.00, 9780.00, 9920.00, 10060.0, 10200.0, 10340.0,
432
+ 10480.0, 10620.0, 10760.0, 10900.0, 11040.0, 11180.0, 11320.0, 11460.0,
433
+ 11600.0, 11740.0, 11880.0, 12020.0, 12160.0, 12300.0, 12440.0, 12580.0,
434
+ 12720.0, 12860.0, 13000.0, 13140.0, 13280.0, 13420.0, 13560.0, 13700.0,
435
+ 13840.0, 13980.0, 14120.0, 14260.0, 14400.0, 14540.0, 14680.0, 14820.0,
436
+ 14960.0, 15100.0, 15240.0, 15380.0, 15520.0, 15660.0, 15800.0, 15940.0,
437
+ 16080.0, 16220.0, 16360.0, 16500.0, 16640.0, 16780.0, 16920.0, 17060.0,
438
+ 17200.0, 17340.0, 17480.0, 17620.0, 17760.0, 17900.0, 18040.0, 18180.0,
439
+ 18320.0, 18460.0, 18600.0, 18740.0, 18880.0, 19020.0, 19160.0, 19300.0,
440
+ 19440.0, 19580.0, 19720.0, 19860.0, 20000.0
441
+ ]
442
+
443
+ length = [
444
+ 482.639, 518.357, 555.868, 595.186, 636.377, 679.478, 724.515, 771.554,
445
+ 820.639, 871.805, 925.074, 980.527, 1038.17, 1098.07, 1160.24, 1224.76,
446
+ 1291.66, 1360.97, 1432.71, 1506.92, 1583.63, 1662.89, 1744.71, 1829.12,
447
+ 1916.14, 2005.81, 2098.15, 2193.20, 2290.93, 2391.48, 2494.84, 2601.00,
448
+ 2709.95, 2821.71, 2936.29, 3053.69, 3173.94, 3297.03, 3422.98, 3551.74,
449
+ 3683.35, 3817.78, 3955.02, 4095.08, 4237.92, 4383.57, 4531.99, 4683.10,
450
+ 4836.97, 4993.50, 5152.65, 5314.53, 5478.98, 5645.95, 5815.49, 5987.49,
451
+ 6161.89, 6338.73, 6517.89, 6699.35, 6883.04, 7068.94, 7256.91, 7446.90,
452
+ 7638.99, 7833.72, 8030.71, 8229.62, 8430.37, 8632.92, 8837.16, 9043.03,
453
+ 9250.44, 9459.37, 9669.72, 9881.46, 10094.5, 10308.7, 10524.0, 10740.4,
454
+ 10957.8, 11176.1, 11395.2, 11615.0, 11835.5, 12056.6, 12278.2, 12500.2,
455
+ 12722.5, 12945.2, 13168.0, 13391.0, 13614.1, 13837.0, 14060.0, 14282.6,
456
+ 14505.1, 14727.2, 14948.9, 15160.9, 15367.5
457
+ ]
458
+
459
+ Mu_Spline = InterpolatedUnivariateSpline(E_values, length)
460
+ return Mu_Spline(keV*1000) * 1e-6 # Convert to m
461
+
462
+ def J4M_efficiency(theta, keV, tSi = 318.5e-6, tAl = 4.5e-6, tK = 8e-6):
463
+ """
464
+ Calculate the detector efficiency of the Jungfrau4M detector for a given photon energy.
465
+
466
+ Parameters
467
+ ----------
468
+ theta : float
469
+ Scattering angle in radians.
470
+ keV : float
471
+ Photon energy in keV.
472
+ tSi : float, optional
473
+ Thickness of the silicon sensor in meters. Default is 318.5e-6.
474
+ tAl : float, optional
475
+ Thickness (total) of the aluminum layer + sputter coating in meters. Default is 4.5e-6.
476
+ tK : float, optional
477
+ Thickness of the kapton layer in meters. Default is 8e-6.
478
+
479
+ Returns
480
+ -------
481
+ float
482
+ Detector efficiency of the Jungfrau4M detector, from 0-1.
483
+
484
+ Notes
485
+ -----
486
+ Uses spline interpolation of tabulated values to calculate the detector efficiency.
487
+ """
488
+ return (1-np.exp(-tSi /(np.cos(theta) * Si_attenuation_length(keV))))*(np.exp(-tK /(np.cos(theta) *KaptonHN_attenuation_length(keV)))) * (np.exp(-tAl /(np.cos(theta) * Al_attenuation_length(keV))))