ChessAnalysisPipeline 0.0.12__py3-none-any.whl → 0.0.14__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.
Potentially problematic release.
This version of ChessAnalysisPipeline might be problematic. Click here for more details.
- CHAP/__init__.py +2 -0
- CHAP/common/__init__.py +7 -2
- CHAP/common/models/map.py +95 -70
- CHAP/common/processor.py +844 -153
- CHAP/common/reader.py +168 -131
- CHAP/common/writer.py +166 -96
- CHAP/edd/__init__.py +2 -0
- CHAP/edd/models.py +94 -48
- CHAP/edd/processor.py +625 -169
- CHAP/edd/utils.py +186 -6
- CHAP/pipeline.py +35 -3
- CHAP/runner.py +40 -13
- CHAP/tomo/models.py +18 -9
- CHAP/tomo/processor.py +1134 -902
- CHAP/utils/fit.py +98 -45
- CHAP/utils/general.py +196 -63
- CHAP/utils/scanparsers.py +403 -94
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/RECORD +23 -23
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/WHEEL +1 -1
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.12.dist-info → ChessAnalysisPipeline-0.0.14.dist-info}/top_level.txt +0 -0
CHAP/edd/processor.py
CHANGED
|
@@ -28,8 +28,8 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
28
28
|
data,
|
|
29
29
|
config=None,
|
|
30
30
|
save_figures=False,
|
|
31
|
-
outputdir='.',
|
|
32
31
|
inputdir='.',
|
|
32
|
+
outputdir='.',
|
|
33
33
|
interactive=False):
|
|
34
34
|
"""Return the calculated value of the DV length.
|
|
35
35
|
|
|
@@ -63,6 +63,7 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
63
63
|
data, 'edd.models.DiffractionVolumeLengthConfig',
|
|
64
64
|
inputdir=inputdir)
|
|
65
65
|
except Exception as data_exc:
|
|
66
|
+
self.logger.error(data_exc)
|
|
66
67
|
self.logger.info('No valid DVL config in input pipeline data, '
|
|
67
68
|
+ 'using config parameter instead.')
|
|
68
69
|
try:
|
|
@@ -134,27 +135,30 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
134
135
|
|
|
135
136
|
self.logger.info(
|
|
136
137
|
'Interactively select a mask in the matplotlib figure')
|
|
138
|
+
|
|
137
139
|
fig, mask, include_bin_ranges = select_mask_1d(
|
|
138
140
|
np.sum(mca_data, axis=0),
|
|
139
|
-
x
|
|
141
|
+
x=np.linspace(0, detector.max_energy_kev, detector.num_bins),
|
|
140
142
|
label='Sum of MCA spectra over all scan points',
|
|
141
143
|
preselected_index_ranges=detector.include_bin_ranges,
|
|
142
144
|
title='Click and drag to select data range to include when '
|
|
143
145
|
'measuring diffraction volume length',
|
|
144
|
-
xlabel='
|
|
146
|
+
xlabel='Uncalibrated Energy (keV)',
|
|
145
147
|
ylabel='MCA intensity (counts)',
|
|
146
148
|
min_num_index_ranges=1,
|
|
147
149
|
interactive=interactive)
|
|
148
|
-
detector.
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
detector.include_energy_ranges = detector.get_energy_ranges(
|
|
151
|
+
include_bin_ranges)
|
|
152
|
+
self.logger.debug(
|
|
153
|
+
'Mask selected. Including detector energy ranges: '
|
|
154
|
+
+ str(detector.include_energy_ranges))
|
|
151
155
|
if save_figures:
|
|
152
156
|
fig.savefig(os.path.join(
|
|
153
157
|
outputdir, f'{detector.detector_name}_dvl_mask.png'))
|
|
154
158
|
plt.close()
|
|
155
|
-
if detector.
|
|
159
|
+
if not detector.include_energy_ranges:
|
|
156
160
|
raise ValueError(
|
|
157
|
-
'No value provided for
|
|
161
|
+
'No value provided for include_energy_ranges. '
|
|
158
162
|
+ 'Provide them in the Diffraction Volume Length '
|
|
159
163
|
+ 'Measurement Configuration, or re-run the pipeline '
|
|
160
164
|
+ 'with the --interactive flag.')
|
|
@@ -184,26 +188,21 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
184
188
|
fit = Fit.fit_data(masked_sum, ('constant', 'gaussian'), x=x)
|
|
185
189
|
|
|
186
190
|
# Calculate / manually select diffraction volume length
|
|
187
|
-
dvl = fit.best_values['sigma'] * detector.sigma_to_dvl_factor
|
|
191
|
+
dvl = fit.best_values['sigma'] * detector.sigma_to_dvl_factor \
|
|
192
|
+
- dvl_config.sample_thickness
|
|
193
|
+
detector.fit_amplitude = fit.best_values['amplitude']
|
|
194
|
+
detector.fit_center = scan_center + fit.best_values['center']
|
|
195
|
+
detector.fit_sigma = fit.best_values['sigma']
|
|
188
196
|
if detector.measurement_mode == 'manual':
|
|
189
197
|
if interactive:
|
|
190
198
|
_, _, dvl_bounds = select_mask_1d(
|
|
191
199
|
masked_sum, x=x,
|
|
192
200
|
label='Total (masked & normalized)',
|
|
193
|
-
#RV TODO ref_data=[
|
|
194
|
-
# ((x, fit.best_fit),
|
|
195
|
-
# {'label': 'gaussian fit (to total)'}),
|
|
196
|
-
# ((x, masked_max),
|
|
197
|
-
# {'label': 'maximum (masked)'}),
|
|
198
|
-
# ((x, unmasked_sum),
|
|
199
|
-
# {'label': 'total (unmasked)'})
|
|
200
|
-
# ],
|
|
201
201
|
preselected_index_ranges=[
|
|
202
202
|
(index_nearest(x, -dvl/2), index_nearest(x, dvl/2))],
|
|
203
203
|
title=('Click and drag to indicate the boundary '
|
|
204
204
|
'of the diffraction volume'),
|
|
205
|
-
xlabel=(
|
|
206
|
-
+ ' (offset from scan "center")'),
|
|
205
|
+
xlabel=('Beam Direction (offset from scan "center")'),
|
|
207
206
|
ylabel='MCA intensity (normalized)',
|
|
208
207
|
min_num_index_ranges=1,
|
|
209
208
|
max_num_index_ranges=1)
|
|
@@ -221,27 +220,28 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
221
220
|
|
|
222
221
|
fig, ax = plt.subplots()
|
|
223
222
|
ax.set_title(f'Diffraction Volume ({detector.detector_name})')
|
|
224
|
-
ax.set_xlabel(
|
|
225
|
-
+ ' (offset from scan "center")')
|
|
223
|
+
ax.set_xlabel('Beam Direction (offset from scan "center")')
|
|
226
224
|
ax.set_ylabel('MCA intensity (normalized)')
|
|
227
225
|
ax.plot(x, masked_sum, label='total (masked & normalized)')
|
|
228
226
|
ax.plot(x, fit.best_fit, label='gaussian fit (to total)')
|
|
229
227
|
ax.plot(x, masked_max, label='maximum (masked)')
|
|
230
228
|
ax.plot(x, unmasked_sum, label='total (unmasked)')
|
|
231
|
-
ax.axvspan(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
229
|
+
ax.axvspan(
|
|
230
|
+
fit.best_values['center']- dvl/2.,
|
|
231
|
+
fit.best_values['center'] + dvl/2.,
|
|
232
|
+
color='gray', alpha=0.5,
|
|
233
|
+
label=f'diffraction volume ({detector.measurement_mode})')
|
|
235
234
|
ax.legend()
|
|
236
|
-
|
|
237
|
-
0,
|
|
235
|
+
plt.figtext(
|
|
236
|
+
0.5, 0.95,
|
|
238
237
|
f'Diffraction volume length: {dvl:.2f}',
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
fontsize='x-large',
|
|
239
|
+
horizontalalignment='center',
|
|
240
|
+
verticalalignment='bottom')
|
|
242
241
|
if save_figures:
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
fig.tight_layout(rect=(0, 0, 1, 0.95))
|
|
243
|
+
figfile = os.path.join(
|
|
244
|
+
outputdir, f'{detector.detector_name}_dvl.png')
|
|
245
245
|
plt.savefig(figfile)
|
|
246
246
|
self.logger.info(f'Saved figure to {figfile}')
|
|
247
247
|
if interactive:
|
|
@@ -249,6 +249,348 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
249
249
|
|
|
250
250
|
return dvl
|
|
251
251
|
|
|
252
|
+
|
|
253
|
+
class LatticeParameterRefinementProcessor(Processor):
|
|
254
|
+
"""Processor to get a refined estimate for a sample's lattice
|
|
255
|
+
parameters"""
|
|
256
|
+
def process(self,
|
|
257
|
+
data,
|
|
258
|
+
config=None,
|
|
259
|
+
save_figures=False,
|
|
260
|
+
outputdir='.',
|
|
261
|
+
inputdir='.',
|
|
262
|
+
interactive=False):
|
|
263
|
+
"""Given a strain analysis configuration, return a copy
|
|
264
|
+
contining refined values for the materials' lattice
|
|
265
|
+
parameters."""
|
|
266
|
+
ceria_calibration_config = self.get_config(
|
|
267
|
+
data, 'edd.models.MCACeriaCalibrationConfig', inputdir=inputdir)
|
|
268
|
+
try:
|
|
269
|
+
strain_analysis_config = self.get_config(
|
|
270
|
+
data, 'edd.models.StrainAnalysisConfig', inputdir=inputdir)
|
|
271
|
+
except Exception as data_exc:
|
|
272
|
+
# Local modules
|
|
273
|
+
from CHAP.edd.models import StrainAnalysisConfig
|
|
274
|
+
|
|
275
|
+
self.logger.info('No valid strain analysis config in input '
|
|
276
|
+
+ 'pipeline data, using config parameter instead')
|
|
277
|
+
try:
|
|
278
|
+
strain_analysis_config = StrainAnalysisConfig(
|
|
279
|
+
**config, inputdir=inputdir)
|
|
280
|
+
except Exception as dict_exc:
|
|
281
|
+
raise RuntimeError from dict_exc
|
|
282
|
+
|
|
283
|
+
if len(strain_analysis_config.materials) > 1:
|
|
284
|
+
msg = 'Not implemented for multiple materials'
|
|
285
|
+
self.logger.error('Not implemented for multiple materials')
|
|
286
|
+
raise NotImplementedError(msg)
|
|
287
|
+
|
|
288
|
+
lattice_parameters = self.refine_lattice_parameters(
|
|
289
|
+
strain_analysis_config, ceria_calibration_config, 0,
|
|
290
|
+
interactive, save_figures, outputdir)
|
|
291
|
+
self.logger.debug(f'Refined lattice parameters: {lattice_parameters}')
|
|
292
|
+
|
|
293
|
+
strain_analysis_config.materials[0].lattice_parameters = \
|
|
294
|
+
lattice_parameters
|
|
295
|
+
return strain_analysis_config.dict()
|
|
296
|
+
|
|
297
|
+
def refine_lattice_parameters(
|
|
298
|
+
self, strain_analysis_config, ceria_calibration_config,
|
|
299
|
+
detector_i, interactive, save_figures, outputdir):
|
|
300
|
+
"""Return refined values for the lattice parameters of the
|
|
301
|
+
materials indicated in `strain_analysis_config`. Method: given
|
|
302
|
+
a scan of a material, fit the peaks of each MCA
|
|
303
|
+
spectrum. Based on those fitted peak locations, calculate the
|
|
304
|
+
lattice parameters that would produce them. Return the
|
|
305
|
+
avearaged value of the calculated lattice parameters across
|
|
306
|
+
all spectra.
|
|
307
|
+
|
|
308
|
+
:param strain_analysis_config: Strain analysis configuration
|
|
309
|
+
:type strain_analysis_config: CHAP.edd.models.StrainAnalysisConfig
|
|
310
|
+
:param detector_i: Index of the detector in
|
|
311
|
+
`strain_analysis_config` whose data will be used for the
|
|
312
|
+
refinement
|
|
313
|
+
:type detector_i: int
|
|
314
|
+
:param interactive: Boolean to indicate whether interactive
|
|
315
|
+
matplotlib figures should be presented
|
|
316
|
+
:type interactive: bool
|
|
317
|
+
:param save_figures: Boolean to indicate whether figures
|
|
318
|
+
indicating the selection should be saved
|
|
319
|
+
:type save_figures: bool
|
|
320
|
+
:param outputdir: Where to save figures (if `save_figures` is
|
|
321
|
+
`True`)
|
|
322
|
+
:type outputdir: str
|
|
323
|
+
:returns: List of refined lattice parameters for materials in
|
|
324
|
+
`strain_analysis_config`
|
|
325
|
+
:rtype: list[numpy.ndarray]
|
|
326
|
+
"""
|
|
327
|
+
import numpy as np
|
|
328
|
+
from CHAP.edd.utils import get_unique_hkls_ds, get_spectra_fits
|
|
329
|
+
|
|
330
|
+
self.add_detector_calibrations(
|
|
331
|
+
strain_analysis_config, ceria_calibration_config)
|
|
332
|
+
|
|
333
|
+
detector = strain_analysis_config.detectors[detector_i]
|
|
334
|
+
mca_bin_energies = self.get_mca_bin_energies(strain_analysis_config)
|
|
335
|
+
mca_data = strain_analysis_config.mca_data()
|
|
336
|
+
hkls, ds = get_unique_hkls_ds(
|
|
337
|
+
strain_analysis_config.materials,
|
|
338
|
+
tth_tol=detector.hkl_tth_tol,
|
|
339
|
+
tth_max=detector.tth_max)
|
|
340
|
+
|
|
341
|
+
self.select_material_params(
|
|
342
|
+
strain_analysis_config, detector_i, mca_data, mca_bin_energies,
|
|
343
|
+
interactive, save_figures, outputdir)
|
|
344
|
+
self.logger.debug(
|
|
345
|
+
'Starting lattice parameters: '
|
|
346
|
+
+ str(strain_analysis_config.materials[0].lattice_parameters))
|
|
347
|
+
self.select_fit_mask_hkls(
|
|
348
|
+
strain_analysis_config, detector_i, mca_data, mca_bin_energies,
|
|
349
|
+
hkls, ds,
|
|
350
|
+
interactive, save_figures, outputdir)
|
|
351
|
+
|
|
352
|
+
(uniform_fit_centers, uniform_fit_centers_errors,
|
|
353
|
+
uniform_fit_amplitudes, uniform_fit_amplitudes_errors,
|
|
354
|
+
uniform_fit_sigmas, uniform_fit_sigmas_errors,
|
|
355
|
+
uniform_best_fit, uniform_residuals,
|
|
356
|
+
uniform_redchi, uniform_success,
|
|
357
|
+
unconstrained_fit_centers, unconstrained_fit_centers_errors,
|
|
358
|
+
unconstrained_fit_amplitudes, unconstrained_fit_amplitudes_errors,
|
|
359
|
+
unconstrained_fit_sigmas, unconstrained_fit_sigmas_errors,
|
|
360
|
+
unconstrained_best_fit, unconstrained_residuals,
|
|
361
|
+
unconstrained_redchi, unconstrained_success) = \
|
|
362
|
+
self.get_spectra_fits(
|
|
363
|
+
strain_analysis_config, detector_i,
|
|
364
|
+
mca_data, mca_bin_energies, hkls, ds)
|
|
365
|
+
|
|
366
|
+
# Get the interplanar spacings measured for each fit HKL peak
|
|
367
|
+
# at every point in the map.
|
|
368
|
+
from scipy.constants import physical_constants
|
|
369
|
+
hc = 1e7 * physical_constants['Planck constant in eV/Hz'][0] \
|
|
370
|
+
* physical_constants['speed of light in vacuum'][0]
|
|
371
|
+
d_measured = hc / \
|
|
372
|
+
(2.0 \
|
|
373
|
+
* unconstrained_fit_centers \
|
|
374
|
+
* np.sin(np.radians(detector.tth_calibrated / 2.0)))
|
|
375
|
+
# Convert interplanar spacings to lattice parameters
|
|
376
|
+
self.logger.warning('Implemented for cubic materials only!')
|
|
377
|
+
fit_hkls = np.asarray([hkls[i] for i in detector.hkl_indices])
|
|
378
|
+
Rs = np.sqrt(np.sum(fit_hkls**2, 1))
|
|
379
|
+
a_measured = Rs[:, None] * d_measured
|
|
380
|
+
# Average all computed lattice parameters for every fit HKL
|
|
381
|
+
# peak at every point in the map to get the refined estimate
|
|
382
|
+
# for the material's lattice parameter
|
|
383
|
+
a_refined = float(np.mean(a_measured))
|
|
384
|
+
return [a_refined, a_refined, a_refined, 90.0, 90.0, 90.0]
|
|
385
|
+
|
|
386
|
+
def get_mca_bin_energies(self, strain_analysis_config):
|
|
387
|
+
"""Return a list of the MCA bin energies for each detector.
|
|
388
|
+
|
|
389
|
+
:param strain_analysis_config: Strain analysis configuration
|
|
390
|
+
containing a list of detectors to return the bin energies
|
|
391
|
+
for.
|
|
392
|
+
:type strain_analysis_config: CHAP.edd.models.StrainAnalysisConfig
|
|
393
|
+
:returns: List of MCA bin energies
|
|
394
|
+
:rtype: list[numpy.ndarray]
|
|
395
|
+
"""
|
|
396
|
+
mca_bin_energies = []
|
|
397
|
+
for i, detector in enumerate(strain_analysis_config.detectors):
|
|
398
|
+
mca_bin_energies.append(
|
|
399
|
+
detector.slope_calibrated
|
|
400
|
+
* np.linspace(0, detector.max_energy_kev, detector.num_bins)
|
|
401
|
+
+ detector.intercept_calibrated)
|
|
402
|
+
return mca_bin_energies
|
|
403
|
+
|
|
404
|
+
def add_detector_calibrations(
|
|
405
|
+
self, strain_analysis_config, ceria_calibration_config):
|
|
406
|
+
"""Add calibrated quantities to the detectors configured in
|
|
407
|
+
`strain_analysis_config`, modifying `strain_analysis_config`
|
|
408
|
+
in place.
|
|
409
|
+
|
|
410
|
+
:param strain_analysis_config: Strain analysisi configuration
|
|
411
|
+
containing a list of detectors to add calibration values
|
|
412
|
+
to.
|
|
413
|
+
:type strain_analysis_config: CHAP.edd.models.StrainAnalysisConfig
|
|
414
|
+
:param ceria_calibration_config: Configuration of a completed
|
|
415
|
+
ceria calibration containing a list of detector swith the
|
|
416
|
+
same names as those in `strain_analysis_config`
|
|
417
|
+
:returns: None"""
|
|
418
|
+
for detector in strain_analysis_config.detectors:
|
|
419
|
+
calibration = [
|
|
420
|
+
d for d in ceria_calibration_config.detectors \
|
|
421
|
+
if d.detector_name == detector.detector_name][0]
|
|
422
|
+
detector.add_calibration(calibration)
|
|
423
|
+
|
|
424
|
+
def select_fit_mask_hkls(
|
|
425
|
+
self, strain_analysis_config, detector_i,
|
|
426
|
+
mca_data, mca_bin_energies, hkls, ds,
|
|
427
|
+
interactive, save_figures, outputdir):
|
|
428
|
+
"""Select the maks & HKLs to use for fitting for each
|
|
429
|
+
detector. Update `strain_analysis_config` with new values for
|
|
430
|
+
`hkl_indices` and `include_bin_ranges` if needed.
|
|
431
|
+
|
|
432
|
+
:param strain_analysis_config: Strain analysis configuration
|
|
433
|
+
:type strain_analysis_config: CHAP.edd.models.StrainAnalysisConfig
|
|
434
|
+
:param detector_i: Index of the detector in
|
|
435
|
+
`strain_analysis_config` to select mask & HKLs for.
|
|
436
|
+
:type detector_i: int
|
|
437
|
+
:param mca_data: List of maps of MCA spectra for all detectors
|
|
438
|
+
in `strain_analysis_config`
|
|
439
|
+
:type mca_data: list[numpy.ndarray]
|
|
440
|
+
:param mca_bin_energies: List of MCA bin energies for all
|
|
441
|
+
detectors in `strain_analysis_config`
|
|
442
|
+
:type mca_bin_energies: list[numpy.ndarray]
|
|
443
|
+
:param hkls: Nominal HKL peak energy locations for the
|
|
444
|
+
material in `strain_analysis_config`
|
|
445
|
+
:type hkls: list[float]
|
|
446
|
+
:param ds: Nominal d-spacing for the material in
|
|
447
|
+
`strain_analysis_config`
|
|
448
|
+
:type ds: list[float]
|
|
449
|
+
:param interactive: Boolean to indicate whether interactive
|
|
450
|
+
matplotlib figures should be presented
|
|
451
|
+
:type interactive: bool
|
|
452
|
+
:param save_figures: Boolean to indicate whether figures
|
|
453
|
+
indicating the selection should be saved
|
|
454
|
+
:type save_figures: bool
|
|
455
|
+
:param outputdir: Where to save figures (if `save_figures` is
|
|
456
|
+
`True`)
|
|
457
|
+
:type outputdir: str
|
|
458
|
+
:returns: None
|
|
459
|
+
"""
|
|
460
|
+
if not interactive and not save_figures:
|
|
461
|
+
return
|
|
462
|
+
import matplotlib.pyplot as plt
|
|
463
|
+
import numpy as np
|
|
464
|
+
from CHAP.edd.utils import select_mask_and_hkls
|
|
465
|
+
|
|
466
|
+
detector = strain_analysis_config.detectors[detector_i]
|
|
467
|
+
fig, include_bin_ranges, hkl_indices = \
|
|
468
|
+
select_mask_and_hkls(
|
|
469
|
+
mca_bin_energies[detector_i],
|
|
470
|
+
np.sum(mca_data[detector_i], axis=0),
|
|
471
|
+
hkls, ds,
|
|
472
|
+
detector.tth_calibrated,
|
|
473
|
+
detector.include_bin_ranges, detector.hkl_indices,
|
|
474
|
+
detector.detector_name, mca_data[detector_i],
|
|
475
|
+
calibration_bin_ranges=detector.calibration_bin_ranges,
|
|
476
|
+
label='Sum of all spectra in the map',
|
|
477
|
+
interactive=interactive)
|
|
478
|
+
detector.include_energy_ranges = detector.get_energy_ranges(
|
|
479
|
+
include_bin_ranges)
|
|
480
|
+
detector.hkl_indices = hkl_indices
|
|
481
|
+
if save_figures:
|
|
482
|
+
fig.savefig(os.path.join(
|
|
483
|
+
outputdir,
|
|
484
|
+
f'{detector.detector_name}_strainanalysis_'
|
|
485
|
+
'fit_mask_hkls.png'))
|
|
486
|
+
plt.close()
|
|
487
|
+
|
|
488
|
+
def select_material_params(self, strain_analysis_config, detector_i,
|
|
489
|
+
mca_data, mca_bin_energies,
|
|
490
|
+
interactive, save_figures, outputdir):
|
|
491
|
+
"""Select initial material parameters to use for determining
|
|
492
|
+
nominal HKL peak locations. Modify `strain_analysis_config` in
|
|
493
|
+
place if needed.
|
|
494
|
+
|
|
495
|
+
:param strain_analysis_config: Strain analysis configuration
|
|
496
|
+
:type strain_analysis_config: CHAP.edd.models.StrainAnalysisConfig
|
|
497
|
+
:param detector_i: Index of the detector in
|
|
498
|
+
`strain_analysis_config` to select mask & HKLs for.
|
|
499
|
+
:type detector_i: int
|
|
500
|
+
:param mca_data: List of maps of MCA spectra for all detectors
|
|
501
|
+
in `strain_analysis_config`
|
|
502
|
+
:type mca_data: list[numpy.ndarray]
|
|
503
|
+
:param mca_bin_energies: List of MCA bin energies for all
|
|
504
|
+
detectors in `strain_analysis_config`
|
|
505
|
+
:type mca_bin_energies: list[numpy.ndarray]
|
|
506
|
+
:param interactive: Boolean to indicate whether interactive
|
|
507
|
+
matplotlib figures should be presented
|
|
508
|
+
:type interactive: bool
|
|
509
|
+
:param save_figures: Boolean to indicate whether figures
|
|
510
|
+
indicating the selection should be saved
|
|
511
|
+
:type save_figures: bool
|
|
512
|
+
:param outputdir: Where to save figures (if `save_figures` is
|
|
513
|
+
`True`)
|
|
514
|
+
:type outputdir: str
|
|
515
|
+
:returns: None
|
|
516
|
+
"""
|
|
517
|
+
if not interactive and not save_figures:
|
|
518
|
+
return
|
|
519
|
+
from CHAP.edd.utils import select_material_params
|
|
520
|
+
import matplotlib.pyplot as plt
|
|
521
|
+
import numpy as np
|
|
522
|
+
fig, strain_analysis_config.materials = select_material_params(
|
|
523
|
+
mca_bin_energies[detector_i], np.sum(mca_data[detector_i], axis=0),
|
|
524
|
+
strain_analysis_config.detectors[detector_i].tth_calibrated,
|
|
525
|
+
strain_analysis_config.materials,
|
|
526
|
+
label='Sum of all spectra in the map', interactive=interactive)
|
|
527
|
+
self.logger.debug(
|
|
528
|
+
f'materials: {strain_analysis_config.materials}')
|
|
529
|
+
if save_figures:
|
|
530
|
+
detector_name = \
|
|
531
|
+
strain_analysis_config.detectors[detector_i].detector_name
|
|
532
|
+
fig.savefig(os.path.join(
|
|
533
|
+
outputdir,
|
|
534
|
+
f'{detector_name}_strainanalysis_'
|
|
535
|
+
'material_config.png'))
|
|
536
|
+
plt.close()
|
|
537
|
+
|
|
538
|
+
def get_spectra_fits(
|
|
539
|
+
self, strain_analysis_config, detector_i,
|
|
540
|
+
mca_data, mca_bin_energies, hkls, ds):
|
|
541
|
+
"""Return uniform and unconstrained fit results for all
|
|
542
|
+
spectra from a single detector.
|
|
543
|
+
|
|
544
|
+
:param strain_analysis_config: Strain analysis configuration
|
|
545
|
+
:type strain_analysis_config: CHAP.edd.models.StrainAnalysisConfig
|
|
546
|
+
:param detector_i: Index of the detector in
|
|
547
|
+
`strain_analysis_config` to select mask & HKLs for.
|
|
548
|
+
:type detector_i: int
|
|
549
|
+
:param mca_data: List of maps of MCA spectra for all detectors
|
|
550
|
+
in `strain_analysis_config`
|
|
551
|
+
:type mca_data: list[numpy.ndarray]
|
|
552
|
+
:param mca_bin_energies: List of MCA bin energies for all
|
|
553
|
+
detectors in `strain_analysis_config`
|
|
554
|
+
:type mca_bin_energies: list[numpy.ndarray]
|
|
555
|
+
:param hkls: Nominal HKL peak energy locations for the
|
|
556
|
+
material in `strain_analysis_config`
|
|
557
|
+
:type hkls: list[float]
|
|
558
|
+
:param ds: Nominal d-spacing for the material in
|
|
559
|
+
`strain_analysis_config`
|
|
560
|
+
:type ds: list[float]
|
|
561
|
+
:returns: Uniform and unconstrained centers, amplitdues,
|
|
562
|
+
sigmas (and errors for all three), best fits, residuals
|
|
563
|
+
between the best fits and the input spectra, reduced chi,
|
|
564
|
+
and fit success statuses.
|
|
565
|
+
:rtype: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
566
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
567
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
568
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
569
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
570
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
571
|
+
numpy.ndarray, numpy.ndarray]
|
|
572
|
+
"""
|
|
573
|
+
from CHAP.edd.utils import get_peak_locations, get_spectra_fits
|
|
574
|
+
detector = strain_analysis_config.detectors[detector_i]
|
|
575
|
+
self.logger.debug(
|
|
576
|
+
f'Fitting spectra from detector {detector.detector_name}')
|
|
577
|
+
mask = detector.mca_mask()
|
|
578
|
+
energies = mca_bin_energies[detector_i][mask]
|
|
579
|
+
intensities = np.empty(
|
|
580
|
+
(*strain_analysis_config.map_config.shape, len(energies)),
|
|
581
|
+
dtype='uint16')
|
|
582
|
+
for j, map_index in \
|
|
583
|
+
enumerate(np.ndindex(strain_analysis_config.map_config.shape)):
|
|
584
|
+
intensities[map_index] = \
|
|
585
|
+
mca_data[detector_i][j].astype('uint16')[mask]
|
|
586
|
+
fit_hkls = np.asarray([hkls[i] for i in detector.hkl_indices])
|
|
587
|
+
fit_ds = np.asarray([ds[i] for i in detector.hkl_indices])
|
|
588
|
+
peak_locations = get_peak_locations(
|
|
589
|
+
fit_ds, detector.tth_calibrated)
|
|
590
|
+
return get_spectra_fits(
|
|
591
|
+
intensities, energies, peak_locations, detector)
|
|
592
|
+
|
|
593
|
+
|
|
252
594
|
class MCACeriaCalibrationProcessor(Processor):
|
|
253
595
|
"""A Processor using a CeO2 scan to obtain tuned values for the
|
|
254
596
|
bragg diffraction angle and linear correction parameters for MCA
|
|
@@ -259,8 +601,8 @@ class MCACeriaCalibrationProcessor(Processor):
|
|
|
259
601
|
data,
|
|
260
602
|
config=None,
|
|
261
603
|
save_figures=False,
|
|
262
|
-
outputdir='.',
|
|
263
604
|
inputdir='.',
|
|
605
|
+
outputdir='.',
|
|
264
606
|
interactive=False):
|
|
265
607
|
"""Return tuned values for 2&theta and linear correction
|
|
266
608
|
parameters for the MCA channel energies.
|
|
@@ -361,7 +703,6 @@ class MCACeriaCalibrationProcessor(Processor):
|
|
|
361
703
|
mca_bin_energies = np.linspace(
|
|
362
704
|
0, detector.max_energy_kev, detector.num_bins)
|
|
363
705
|
mca_data = calibration_config.mca_data(detector)
|
|
364
|
-
|
|
365
706
|
if interactive or save_figures:
|
|
366
707
|
# Third party modules
|
|
367
708
|
import matplotlib.pyplot as plt
|
|
@@ -389,27 +730,29 @@ class MCACeriaCalibrationProcessor(Processor):
|
|
|
389
730
|
detector.tth_initial_guess, detector.include_bin_ranges,
|
|
390
731
|
detector.hkl_indices, detector.detector_name,
|
|
391
732
|
flux_energy_range=calibration_config.flux_file_energy_range,
|
|
733
|
+
label='MCA data',
|
|
392
734
|
interactive=interactive)
|
|
393
|
-
detector.
|
|
735
|
+
detector.include_energy_ranges = detector.get_energy_ranges(
|
|
736
|
+
include_bin_ranges)
|
|
394
737
|
detector.hkl_indices = hkl_indices
|
|
395
738
|
if save_figures:
|
|
396
739
|
fig.savefig(os.path.join(
|
|
397
|
-
|
|
740
|
+
outputdir,
|
|
398
741
|
f'{detector.detector_name}_calibration_fit_mask_hkls.png'))
|
|
399
742
|
plt.close()
|
|
400
743
|
self.logger.debug(f'tth_initial_guess = {detector.tth_initial_guess}')
|
|
401
744
|
self.logger.debug(
|
|
402
|
-
f'
|
|
403
|
-
if detector.
|
|
745
|
+
f'include_energy_ranges = {detector.include_energy_ranges}')
|
|
746
|
+
if not detector.include_energy_ranges:
|
|
404
747
|
raise ValueError(
|
|
405
|
-
'No value provided for
|
|
406
|
-
'Provide them in the MCA Ceria Calibration Configuration
|
|
748
|
+
'No value provided for include_energy_ranges. '
|
|
749
|
+
'Provide them in the MCA Ceria Calibration Configuration '
|
|
407
750
|
'or re-run the pipeline with the --interactive flag.')
|
|
408
|
-
if detector.hkl_indices
|
|
751
|
+
if not detector.hkl_indices:
|
|
409
752
|
raise ValueError(
|
|
410
753
|
'No value provided for hkl_indices. Provide them in '
|
|
411
|
-
'the detector\'s MCA Ceria Calibration Configuration
|
|
412
|
-
'
|
|
754
|
+
'the detector\'s MCA Ceria Calibration Configuration or '
|
|
755
|
+
're-run the pipeline with the --interactive flag.')
|
|
413
756
|
mca_mask = detector.mca_mask()
|
|
414
757
|
fit_mca_energies = mca_bin_energies[mca_mask]
|
|
415
758
|
fit_mca_intensities = mca_data[mca_mask]
|
|
@@ -422,52 +765,59 @@ class MCACeriaCalibrationProcessor(Processor):
|
|
|
422
765
|
|
|
423
766
|
# Get the HKLs and lattice spacings that will be used for
|
|
424
767
|
# fitting
|
|
768
|
+
# Restrict the range for the centers with some margin to
|
|
769
|
+
# prevent having centers near the edge of the fitting range
|
|
770
|
+
delta = 0.1 * (fit_mca_energies[-1]-fit_mca_energies[0])
|
|
771
|
+
centers_range = (
|
|
772
|
+
max(0.0, fit_mca_energies[0]-delta), fit_mca_energies[-1]+delta)
|
|
425
773
|
fit_hkls = np.asarray([hkls[i] for i in detector.hkl_indices])
|
|
426
774
|
fit_ds = np.asarray([ds[i] for i in detector.hkl_indices])
|
|
427
775
|
c_1 = fit_hkls[:,0]**2 + fit_hkls[:,1]**2 + fit_hkls[:,2]**2
|
|
428
776
|
tth = detector.tth_initial_guess
|
|
777
|
+
fit_E0 = get_peak_locations(fit_ds, tth)
|
|
429
778
|
for iter_i in range(calibration_config.max_iter):
|
|
430
|
-
self.logger.debug(
|
|
431
|
-
|
|
779
|
+
self.logger.debug(
|
|
780
|
+
f'Tuning tth: iteration no. {iter_i}, starting value = {tth} ')
|
|
432
781
|
|
|
433
782
|
# Perform the uniform fit first
|
|
434
783
|
|
|
435
784
|
# Get expected peak energy locations for this iteration's
|
|
436
785
|
# starting value of tth
|
|
437
|
-
|
|
786
|
+
_fit_E0 = get_peak_locations(fit_ds, tth)
|
|
438
787
|
|
|
439
788
|
# Run the uniform fit
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
789
|
+
fit = Fit(fit_mca_intensities, x=fit_mca_energies)
|
|
790
|
+
fit.create_multipeak_model(
|
|
791
|
+
_fit_E0, fit_type='uniform', background=detector.background,
|
|
792
|
+
centers_range=centers_range)
|
|
793
|
+
fit.fit()
|
|
445
794
|
|
|
446
795
|
# Extract values of interest from the best values for the
|
|
447
796
|
# uniform fit parameters
|
|
797
|
+
uniform_best_fit = fit.best_fit
|
|
798
|
+
uniform_residual = fit.residual
|
|
448
799
|
uniform_fit_centers = [
|
|
449
|
-
|
|
800
|
+
fit.best_values[f'peak{i+1}_center']
|
|
450
801
|
for i in range(len(fit_hkls))]
|
|
451
|
-
uniform_a =
|
|
802
|
+
uniform_a = fit.best_values['scale_factor']
|
|
452
803
|
uniform_strain = np.log(
|
|
453
804
|
(uniform_a
|
|
454
805
|
/ calibration_config.material.lattice_parameters)) # CeO2 is cubic, so this is fine here.
|
|
455
806
|
|
|
456
807
|
# Next, perform the unconstrained fit
|
|
457
808
|
|
|
458
|
-
# Use the peak
|
|
809
|
+
# Use the peak parameters from the uniform fit as the
|
|
459
810
|
# initial guesses for peak locations in the unconstrained
|
|
460
811
|
# fit
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
uniform_fit_centers, fit_type='unconstrained',
|
|
464
|
-
)#background='constant')
|
|
465
|
-
unconstrained_fit.fit()
|
|
812
|
+
fit.create_multipeak_model(fit_type='unconstrained')
|
|
813
|
+
fit.fit()
|
|
466
814
|
|
|
467
815
|
# Extract values of interest from the best values for the
|
|
468
816
|
# unconstrained fit parameters
|
|
817
|
+
unconstrained_best_fit = fit.best_fit
|
|
818
|
+
unconstrained_residual = fit.residual
|
|
469
819
|
unconstrained_fit_centers = np.array(
|
|
470
|
-
[
|
|
820
|
+
[fit.best_values[f'peak{i+1}_center']
|
|
471
821
|
for i in range(len(fit_hkls))])
|
|
472
822
|
unconstrained_a = np.sqrt(c_1)*abs(get_peak_locations(
|
|
473
823
|
unconstrained_fit_centers, tth))
|
|
@@ -509,13 +859,13 @@ class MCACeriaCalibrationProcessor(Processor):
|
|
|
509
859
|
axs[0,0].text(hkl_E, 1, str(fit_hkls[i])[1:-1],
|
|
510
860
|
ha='right', va='top', rotation=90,
|
|
511
861
|
transform=axs[0,0].get_xaxis_transform())
|
|
512
|
-
axs[0,0].plot(fit_mca_energies,
|
|
513
|
-
|
|
514
|
-
axs[0,0].plot(fit_mca_energies,
|
|
515
|
-
|
|
862
|
+
axs[0,0].plot(fit_mca_energies, uniform_best_fit,
|
|
863
|
+
label='Single Strain')
|
|
864
|
+
axs[0,0].plot(fit_mca_energies, unconstrained_best_fit,
|
|
865
|
+
label='Unconstrained')
|
|
516
866
|
#axs[0,0].plot(fit_mca_energies, MISSING?, label='least squares')
|
|
517
867
|
axs[0,0].plot(fit_mca_energies, fit_mca_intensities,
|
|
518
|
-
|
|
868
|
+
label='Flux-Corrected & Masked MCA Data')
|
|
519
869
|
axs[0,0].legend()
|
|
520
870
|
|
|
521
871
|
# Lower left axes: fit residuals
|
|
@@ -523,10 +873,10 @@ class MCACeriaCalibrationProcessor(Processor):
|
|
|
523
873
|
axs[1,0].set_xlabel('Energy (keV)')
|
|
524
874
|
axs[1,0].set_ylabel('Residual (a.u)')
|
|
525
875
|
axs[1,0].plot(fit_mca_energies,
|
|
526
|
-
|
|
876
|
+
uniform_residual,
|
|
527
877
|
label='Single Strain')
|
|
528
878
|
axs[1,0].plot(fit_mca_energies,
|
|
529
|
-
|
|
879
|
+
unconstrained_residual,
|
|
530
880
|
label='Unconstrained')
|
|
531
881
|
axs[1,0].legend()
|
|
532
882
|
|
|
@@ -556,6 +906,19 @@ class MCACeriaCalibrationProcessor(Processor):
|
|
|
556
906
|
color='C1', label='Unconstrained: Linear Fit')
|
|
557
907
|
axs[1,1].legend()
|
|
558
908
|
|
|
909
|
+
# Add a text box showing final calibrated values
|
|
910
|
+
axs[1,1].text(
|
|
911
|
+
0.98, 0.02,
|
|
912
|
+
'Calibrated Values:\n\n'
|
|
913
|
+
+ f'Takeoff Angle:\n {tth:.5f}$^\circ$\n\n'
|
|
914
|
+
+ f'Slope:\n {slope:.5f}\n\n'
|
|
915
|
+
+ f'Intercept:\n {intercept:.5f} $keV$',
|
|
916
|
+
ha='right', va='bottom', ma='left',
|
|
917
|
+
transform=axs[1,1].transAxes,
|
|
918
|
+
bbox=dict(boxstyle='round',
|
|
919
|
+
ec=(1., 0.5, 0.5),
|
|
920
|
+
fc=(1., 0.8, 0.8, 0.8)))
|
|
921
|
+
|
|
559
922
|
fig.tight_layout()
|
|
560
923
|
|
|
561
924
|
if save_figures:
|
|
@@ -568,6 +931,141 @@ class MCACeriaCalibrationProcessor(Processor):
|
|
|
568
931
|
return float(tth), float(slope), float(intercept)
|
|
569
932
|
|
|
570
933
|
|
|
934
|
+
class MCAEnergyCalibrationProcessor(Processor):
|
|
935
|
+
"""Processor to return parameters for linearly transforming MCA
|
|
936
|
+
channel indices to energies (in keV). Procedure: provide a
|
|
937
|
+
spectrum from the MCA element to be calibrated and the theoretical
|
|
938
|
+
location of at least one peak present in that spectrum (peak
|
|
939
|
+
locations must be given in keV). It is strongly recommended to use
|
|
940
|
+
the location of fluorescence peaks whenever possible, _not_
|
|
941
|
+
diffraction peaks, as this Processor does not account for
|
|
942
|
+
2&theta."""
|
|
943
|
+
def process(self,
|
|
944
|
+
data,
|
|
945
|
+
max_energy,
|
|
946
|
+
peak_energies,
|
|
947
|
+
peak_initial_guesses=None,
|
|
948
|
+
peak_center_fit_delta=2.0,
|
|
949
|
+
fit_ranges=None,
|
|
950
|
+
save_figures=False,
|
|
951
|
+
interactive=False,
|
|
952
|
+
outputdir='.'):
|
|
953
|
+
"""Fit the specified peaks in the MCA spectrum provided. Using
|
|
954
|
+
the difference between the provided peak locations and the fit
|
|
955
|
+
centers of those peaks, compute linear correction parameters
|
|
956
|
+
to convert MCA channel indices to energies in keV. Return
|
|
957
|
+
those parameters as a dictionary.
|
|
958
|
+
|
|
959
|
+
:param data: An MCA spectrum
|
|
960
|
+
:type data: PipelineData
|
|
961
|
+
:param max_energy: The (uncalibrated) maximum energy measured
|
|
962
|
+
by the MCA spectrum provided.
|
|
963
|
+
:type max_energy: float
|
|
964
|
+
:param peak_energies: Theoretical locations of peaks to use
|
|
965
|
+
for calibrating the MCA channel energies. It is _strongly_
|
|
966
|
+
recommended to use fluorescence peaks.
|
|
967
|
+
:type peak_energies: list[float]
|
|
968
|
+
:param peak_initial_guesses: A list of values to use for the
|
|
969
|
+
initial guesses for peak locations when performing the fit
|
|
970
|
+
of the spectrum. Providing good values to this parameter
|
|
971
|
+
can greatly improve the quality of the spectrum fit when
|
|
972
|
+
the uncalibrated detector channel energies are too far off
|
|
973
|
+
to use the values in `peak_energies` for the initial
|
|
974
|
+
guesses for peak centers. Defaults to None.
|
|
975
|
+
:type peak_inital_guesses: Optional[list[float]]
|
|
976
|
+
:param peak_center_fit_delta: Set boundaries on the fit peak
|
|
977
|
+
centers when performing the fit. The min/max possible
|
|
978
|
+
values for the peak centers will be the values provided in
|
|
979
|
+
`peak_energies` (or `peak_initial_guesses`, if used) ±
|
|
980
|
+
`peak_center_fit_delta`. Defaults to 2.0.
|
|
981
|
+
:type peak_center_fit_delta: float
|
|
982
|
+
:param fit_ranges: Explicit ranges of MCA channel indices
|
|
983
|
+
(_not_ energies) to include when performing a fit of the
|
|
984
|
+
given peaks to the provied MCA spectrum. Use this
|
|
985
|
+
parameter or select it interactively by running a pipeline
|
|
986
|
+
with `config.interactive: True`. Defaults to []
|
|
987
|
+
:type fit_ranges: Optional[list[tuple[int, int]]]
|
|
988
|
+
:param save_figures: Save .pngs of plots for checking inputs &
|
|
989
|
+
outputs of this Processor, defaults to False.
|
|
990
|
+
:type save_figures: bool, optional
|
|
991
|
+
:param interactive: Allows for user interactions, defaults to
|
|
992
|
+
False.
|
|
993
|
+
:type interactive: bool, optional
|
|
994
|
+
:param outputdir: Directory to which any output figures will
|
|
995
|
+
be saved, defaults to '.'.
|
|
996
|
+
:type outputdir: str, optional
|
|
997
|
+
:returns: Dictionary containing linear energy correction
|
|
998
|
+
parameters for the MCA element
|
|
999
|
+
:rtype: dict[str, float]
|
|
1000
|
+
"""
|
|
1001
|
+
# Validate arguments: fit_ranges & interactive
|
|
1002
|
+
if not (fit_ranges or interactive):
|
|
1003
|
+
self.logger.exception(
|
|
1004
|
+
RuntimeError(
|
|
1005
|
+
'If `fit_ranges` is not explicitly provided, '
|
|
1006
|
+
+ self.__class__.__name__
|
|
1007
|
+
+ ' must be run with `interactive=True`.'))
|
|
1008
|
+
# Validate arguments: peak_energies & peak_initial_guesses
|
|
1009
|
+
if peak_initial_guesses is None:
|
|
1010
|
+
peak_initial_guesses = peak_energies
|
|
1011
|
+
else:
|
|
1012
|
+
from CHAP.utils.general import is_num_series
|
|
1013
|
+
is_num_series(peak_initial_guesses, raise_error=True)
|
|
1014
|
+
if len(peak_initial_guesses) != len(peak_energies):
|
|
1015
|
+
self.logger.exception(
|
|
1016
|
+
ValueError(
|
|
1017
|
+
'peak_initial_guesses must have the same number of '
|
|
1018
|
+
+ 'values as peak_energies'))
|
|
1019
|
+
|
|
1020
|
+
import matplotlib.pyplot as plt
|
|
1021
|
+
import numpy as np
|
|
1022
|
+
from CHAP.utils.fit import Fit
|
|
1023
|
+
from CHAP.utils.general import select_mask_1d
|
|
1024
|
+
|
|
1025
|
+
spectrum = self.unwrap_pipelinedata(data)[0]
|
|
1026
|
+
num_bins = len(spectrum)
|
|
1027
|
+
uncalibrated_energies = np.linspace(0, max_energy, num_bins)
|
|
1028
|
+
|
|
1029
|
+
fig, mask, fit_ranges = select_mask_1d(
|
|
1030
|
+
spectrum, x=uncalibrated_energies,
|
|
1031
|
+
preselected_index_ranges=fit_ranges,
|
|
1032
|
+
xlabel='Uncalibrated Energy', ylabel='Intensity',
|
|
1033
|
+
min_num_index_ranges=1, interactive=interactive)
|
|
1034
|
+
if save_figures:
|
|
1035
|
+
fig.savefig(os.path.join(
|
|
1036
|
+
outputdir, 'mca_energy_calibration_mask.png'))
|
|
1037
|
+
plt.close()
|
|
1038
|
+
self.logger.debug(f'Selected index ranges to fit: {fit_ranges}')
|
|
1039
|
+
|
|
1040
|
+
spectrum_fit = Fit(spectrum[mask], x=uncalibrated_energies[mask])
|
|
1041
|
+
for i, (peak_energy, initial_guess) in enumerate(
|
|
1042
|
+
zip(peak_energies, peak_initial_guesses)):
|
|
1043
|
+
spectrum_fit.add_model(
|
|
1044
|
+
'gaussian', prefix=f'peak{i+1}_', parameters=(
|
|
1045
|
+
{'name': 'amplitude', 'min': 0.0},
|
|
1046
|
+
{'name': 'center', 'value': initial_guess,
|
|
1047
|
+
'min': initial_guess - peak_center_fit_delta,
|
|
1048
|
+
'max': initial_guess + peak_center_fit_delta}
|
|
1049
|
+
))
|
|
1050
|
+
self.logger.debug('Fitting spectrum')
|
|
1051
|
+
spectrum_fit.fit()
|
|
1052
|
+
fit_peak_energies = [
|
|
1053
|
+
spectrum_fit.best_values[f'peak{i+1}_center']
|
|
1054
|
+
for i in range(len(peak_energies))]
|
|
1055
|
+
self.logger.debug(f'Fit peak centers: {fit_peak_energies}')
|
|
1056
|
+
|
|
1057
|
+
energy_fit = Fit.fit_data(
|
|
1058
|
+
peak_energies, 'linear', x=fit_peak_energies, nan_policy='omit')
|
|
1059
|
+
slope = energy_fit.best_values['slope']
|
|
1060
|
+
intercept = energy_fit.best_values['intercept']
|
|
1061
|
+
|
|
1062
|
+
# Rescale slope so results are a linear correction from
|
|
1063
|
+
# channel indices -> calibrated energies, not uncalibrated
|
|
1064
|
+
# energies -> calibrated energies
|
|
1065
|
+
slope = (max_energy / num_bins) * slope
|
|
1066
|
+
return({'slope': slope, 'intercept': intercept})
|
|
1067
|
+
|
|
1068
|
+
|
|
571
1069
|
class MCADataProcessor(Processor):
|
|
572
1070
|
"""A Processor to return data from an MCA, restuctured to
|
|
573
1071
|
incorporate the shape & metadata associated with a map
|
|
@@ -579,8 +1077,8 @@ class MCADataProcessor(Processor):
|
|
|
579
1077
|
data,
|
|
580
1078
|
config=None,
|
|
581
1079
|
save_figures=False,
|
|
582
|
-
outputdir='.',
|
|
583
1080
|
inputdir='.',
|
|
1081
|
+
outputdir='.',
|
|
584
1082
|
interactive=False):
|
|
585
1083
|
"""Process configurations for a map and MCA detector(s), and
|
|
586
1084
|
return the calibrated MCA data collected over the map.
|
|
@@ -681,8 +1179,8 @@ class StrainAnalysisProcessor(Processor):
|
|
|
681
1179
|
data,
|
|
682
1180
|
config=None,
|
|
683
1181
|
save_figures=False,
|
|
684
|
-
outputdir='.',
|
|
685
1182
|
inputdir='.',
|
|
1183
|
+
outputdir='.',
|
|
686
1184
|
interactive=False):
|
|
687
1185
|
"""Return strain analysis maps & associated metadata in an NXprocess.
|
|
688
1186
|
|
|
@@ -791,8 +1289,8 @@ class StrainAnalysisProcessor(Processor):
|
|
|
791
1289
|
from CHAP.edd.utils import (
|
|
792
1290
|
get_peak_locations,
|
|
793
1291
|
get_unique_hkls_ds,
|
|
1292
|
+
get_spectra_fits
|
|
794
1293
|
)
|
|
795
|
-
from CHAP.utils.fit import FitMap
|
|
796
1294
|
|
|
797
1295
|
def linkdims(nxgroup, field_dims=[]):
|
|
798
1296
|
if isinstance(field_dims, dict):
|
|
@@ -864,11 +1362,11 @@ class StrainAnalysisProcessor(Processor):
|
|
|
864
1362
|
# calibration_mask = detector.mca_mask()
|
|
865
1363
|
calibration_bin_ranges = detector.include_bin_ranges
|
|
866
1364
|
|
|
867
|
-
|
|
868
1365
|
tth = strain_analysis_config.detectors[0].tth_calibrated
|
|
869
1366
|
fig, strain_analysis_config.materials = select_material_params(
|
|
870
|
-
mca_bin_energies[0], mca_data[0]
|
|
1367
|
+
mca_bin_energies[0], np.sum(mca_data, axis=1)[0], tth,
|
|
871
1368
|
materials=strain_analysis_config.materials,
|
|
1369
|
+
label='Sum of all spectra in the map',
|
|
872
1370
|
interactive=interactive)
|
|
873
1371
|
self.logger.debug(
|
|
874
1372
|
f'materials: {strain_analysis_config.materials}')
|
|
@@ -888,14 +1386,18 @@ class StrainAnalysisProcessor(Processor):
|
|
|
888
1386
|
for i, detector in enumerate(strain_analysis_config.detectors):
|
|
889
1387
|
fig, include_bin_ranges, hkl_indices = \
|
|
890
1388
|
select_mask_and_hkls(
|
|
891
|
-
mca_bin_energies[i],
|
|
1389
|
+
mca_bin_energies[i],
|
|
1390
|
+
np.sum(mca_data[i], axis=0),
|
|
1391
|
+
hkls, ds,
|
|
892
1392
|
detector.tth_calibrated,
|
|
893
1393
|
detector.include_bin_ranges, detector.hkl_indices,
|
|
894
1394
|
detector.detector_name, mca_data[i],
|
|
895
1395
|
# calibration_mask=calibration_mask,
|
|
896
1396
|
calibration_bin_ranges=calibration_bin_ranges,
|
|
1397
|
+
label='Sum of all spectra in the map',
|
|
897
1398
|
interactive=interactive)
|
|
898
|
-
detector.
|
|
1399
|
+
detector.include_energy_ranges = detector.get_energy_ranges(
|
|
1400
|
+
include_bin_ranges)
|
|
899
1401
|
detector.hkl_indices = hkl_indices
|
|
900
1402
|
if save_figures:
|
|
901
1403
|
fig.savefig(os.path.join(
|
|
@@ -915,6 +1417,16 @@ class StrainAnalysisProcessor(Processor):
|
|
|
915
1417
|
tth_max=strain_analysis_config.detectors[0].tth_max)
|
|
916
1418
|
|
|
917
1419
|
for i, detector in enumerate(strain_analysis_config.detectors):
|
|
1420
|
+
if not detector.include_energy_ranges:
|
|
1421
|
+
raise ValueError(
|
|
1422
|
+
'No value provided for include_energy_ranges. '
|
|
1423
|
+
'Provide them in the MCA Ceria Calibration Configuration, '
|
|
1424
|
+
'or re-run the pipeline with the --interactive flag.')
|
|
1425
|
+
if not detector.hkl_indices:
|
|
1426
|
+
raise ValueError(
|
|
1427
|
+
'No value provided for hkl_indices. Provide them in '
|
|
1428
|
+
'the detector\'s MCA Ceria Calibration Configuration, or'
|
|
1429
|
+
' re-run the pipeline with the --interactive flag.')
|
|
918
1430
|
# Setup NXdata group
|
|
919
1431
|
self.logger.debug(
|
|
920
1432
|
f'Setting up NXdata group for {detector.detector_name}')
|
|
@@ -962,48 +1474,20 @@ class StrainAnalysisProcessor(Processor):
|
|
|
962
1474
|
fit_ds = np.asarray([ds[i] for i in detector.hkl_indices])
|
|
963
1475
|
peak_locations = get_peak_locations(
|
|
964
1476
|
fit_ds, detector.tth_calibrated)
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
background=detector.background,
|
|
980
|
-
fwhm_min=detector.fwhm_min,
|
|
981
|
-
fwhm_max=detector.fwhm_max)
|
|
982
|
-
fit.fit()
|
|
983
|
-
uniform_fit_centers = [
|
|
984
|
-
fit.best_values[
|
|
985
|
-
fit.best_parameters().index(f'peak{i+1}_center')]
|
|
986
|
-
for i in range(num_peak)]
|
|
987
|
-
uniform_fit_centers_errors = [
|
|
988
|
-
fit.best_errors[
|
|
989
|
-
fit.best_parameters().index(f'peak{i+1}_center')]
|
|
990
|
-
for i in range(num_peak)]
|
|
991
|
-
uniform_fit_amplitudes = [
|
|
992
|
-
fit.best_values[
|
|
993
|
-
fit.best_parameters().index(f'peak{i+1}_amplitude')]
|
|
994
|
-
for i in range(num_peak)]
|
|
995
|
-
uniform_fit_amplitudes_errors = [
|
|
996
|
-
fit.best_errors[
|
|
997
|
-
fit.best_parameters().index(f'peak{i+1}_amplitude')]
|
|
998
|
-
for i in range(num_peak)]
|
|
999
|
-
uniform_fit_sigmas = [
|
|
1000
|
-
fit.best_values[
|
|
1001
|
-
fit.best_parameters().index(f'peak{i+1}_sigma')]
|
|
1002
|
-
for i in range(num_peak)]
|
|
1003
|
-
uniform_fit_sigmas_errors = [
|
|
1004
|
-
fit.best_errors[
|
|
1005
|
-
fit.best_parameters().index(f'peak{i+1}_sigma')]
|
|
1006
|
-
for i in range(num_peak)]
|
|
1477
|
+
|
|
1478
|
+
(uniform_fit_centers, uniform_fit_centers_errors,
|
|
1479
|
+
uniform_fit_amplitudes, uniform_fit_amplitudes_errors,
|
|
1480
|
+
uniform_fit_sigmas, uniform_fit_sigmas_errors,
|
|
1481
|
+
uniform_best_fit, uniform_residuals,
|
|
1482
|
+
uniform_redchi, uniform_success,
|
|
1483
|
+
unconstrained_fit_centers, unconstrained_fit_centers_errors,
|
|
1484
|
+
unconstrained_fit_amplitudes, unconstrained_fit_amplitudes_errors,
|
|
1485
|
+
unconstrained_fit_sigmas, unconstrained_fit_sigmas_errors,
|
|
1486
|
+
unconstrained_best_fit, unconstrained_residuals,
|
|
1487
|
+
unconstrained_redchi, unconstrained_success) = \
|
|
1488
|
+
get_spectra_fits(
|
|
1489
|
+
det_nxdata.intensity.nxdata, energies,
|
|
1490
|
+
peak_locations, detector)
|
|
1007
1491
|
|
|
1008
1492
|
# Add uniform fit results to the NeXus structure
|
|
1009
1493
|
nxdetector.uniform_fit = NXcollection()
|
|
@@ -1015,10 +1499,10 @@ class StrainAnalysisProcessor(Processor):
|
|
|
1015
1499
|
linkdims(
|
|
1016
1500
|
fit_nxdata, {'axes': 'energy', 'index': len(map_config.shape)})
|
|
1017
1501
|
fit_nxdata.makelink(det_nxdata.energy)
|
|
1018
|
-
fit_nxdata.best_fit=
|
|
1019
|
-
fit_nxdata.residuals =
|
|
1020
|
-
fit_nxdata.redchi =
|
|
1021
|
-
fit_nxdata.success =
|
|
1502
|
+
fit_nxdata.best_fit= uniform_best_fit
|
|
1503
|
+
fit_nxdata.residuals = uniform_residuals
|
|
1504
|
+
fit_nxdata.redchi = uniform_redchi
|
|
1505
|
+
fit_nxdata.success = uniform_success
|
|
1022
1506
|
|
|
1023
1507
|
# Peak-by-peak results
|
|
1024
1508
|
# fit_nxgroup.fit_hkl_centers = NXdata()
|
|
@@ -1064,39 +1548,6 @@ class StrainAnalysisProcessor(Processor):
|
|
|
1064
1548
|
value=sigmas_error)
|
|
1065
1549
|
fit_nxgroup[hkl_name].sigmas.attrs['signal'] = 'values'
|
|
1066
1550
|
|
|
1067
|
-
# Perform second fit: do not assume uniform strain for all
|
|
1068
|
-
# HKLs, and use the fit peak centers from the uniform fit
|
|
1069
|
-
# as inital guesses
|
|
1070
|
-
self.logger.debug('Performing unconstrained fit')
|
|
1071
|
-
fit.create_multipeak_model(fit_type='unconstrained')
|
|
1072
|
-
fit.fit(rel_amplitude_cutoff=detector.rel_amplitude_cutoff)
|
|
1073
|
-
unconstrained_fit_centers = np.array(
|
|
1074
|
-
[fit.best_values[
|
|
1075
|
-
fit.best_parameters()\
|
|
1076
|
-
.index(f'peak{i+1}_center')]
|
|
1077
|
-
for i in range(num_peak)])
|
|
1078
|
-
unconstrained_fit_centers_errors = np.array(
|
|
1079
|
-
[fit.best_errors[
|
|
1080
|
-
fit.best_parameters()\
|
|
1081
|
-
.index(f'peak{i+1}_center')]
|
|
1082
|
-
for i in range(num_peak)])
|
|
1083
|
-
unconstrained_fit_amplitudes = [
|
|
1084
|
-
fit.best_values[
|
|
1085
|
-
fit.best_parameters().index(f'peak{i+1}_amplitude')]
|
|
1086
|
-
for i in range(num_peak)]
|
|
1087
|
-
unconstrained_fit_amplitudes_errors = [
|
|
1088
|
-
fit.best_errors[
|
|
1089
|
-
fit.best_parameters().index(f'peak{i+1}_amplitude')]
|
|
1090
|
-
for i in range(num_peak)]
|
|
1091
|
-
unconstrained_fit_sigmas = [
|
|
1092
|
-
fit.best_values[
|
|
1093
|
-
fit.best_parameters().index(f'peak{i+1}_sigma')]
|
|
1094
|
-
for i in range(num_peak)]
|
|
1095
|
-
unconstrained_fit_sigmas_errors = [
|
|
1096
|
-
fit.best_errors[
|
|
1097
|
-
fit.best_parameters().index(f'peak{i+1}_sigma')]
|
|
1098
|
-
for i in range(num_peak)]
|
|
1099
|
-
|
|
1100
1551
|
if interactive or save_figures:
|
|
1101
1552
|
# Third party modules
|
|
1102
1553
|
import matplotlib.animation as animation
|
|
@@ -1113,13 +1564,15 @@ class StrainAnalysisProcessor(Processor):
|
|
|
1113
1564
|
intensity.set_ydata(
|
|
1114
1565
|
det_nxdata.intensity.nxdata[map_index]
|
|
1115
1566
|
/ det_nxdata.intensity.nxdata[map_index].max())
|
|
1116
|
-
best_fit.set_ydata(
|
|
1117
|
-
|
|
1118
|
-
|
|
1567
|
+
best_fit.set_ydata(
|
|
1568
|
+
unconstrained_best_fit[map_index]
|
|
1569
|
+
/ unconstrained_best_fit[map_index].max())
|
|
1570
|
+
# residual.set_ydata(unconstrained_residuals[map_index])
|
|
1119
1571
|
index.set_text('\n'.join(f'{k}[{i}] = {v}'
|
|
1120
1572
|
for k, v in map_config.get_coords(map_index).items()))
|
|
1121
1573
|
if save_figures:
|
|
1122
|
-
plt.savefig(os.path.join(
|
|
1574
|
+
plt.savefig(os.path.join(
|
|
1575
|
+
path, f'frame_{str(i).zfill(num_digit)}.png'))
|
|
1123
1576
|
#return intensity, best_fit, residual, index
|
|
1124
1577
|
return intensity, best_fit, index
|
|
1125
1578
|
|
|
@@ -1130,12 +1583,12 @@ class StrainAnalysisProcessor(Processor):
|
|
|
1130
1583
|
/ det_nxdata.intensity.nxdata[map_index].max())
|
|
1131
1584
|
intensity, = ax.plot(
|
|
1132
1585
|
energies, data_normalized, 'b.', label='data')
|
|
1133
|
-
fit_normalized = (
|
|
1134
|
-
/
|
|
1586
|
+
fit_normalized = (unconstrained_best_fit[map_index]
|
|
1587
|
+
/ unconstrained_best_fit[map_index].max())
|
|
1135
1588
|
best_fit, = ax.plot(
|
|
1136
1589
|
energies, fit_normalized, 'k-', label='fit')
|
|
1137
1590
|
# residual, = ax.plot(
|
|
1138
|
-
# energies,
|
|
1591
|
+
# energies, unconstrained_residuals[map_index], 'r-',
|
|
1139
1592
|
# label='residual')
|
|
1140
1593
|
ax.set(
|
|
1141
1594
|
title='Unconstrained fits',
|
|
@@ -1145,8 +1598,9 @@ class StrainAnalysisProcessor(Processor):
|
|
|
1145
1598
|
index = ax.text(
|
|
1146
1599
|
0.05, 0.95, '', transform=ax.transAxes, va='top')
|
|
1147
1600
|
|
|
1148
|
-
|
|
1601
|
+
num_frame = int(det_nxdata.intensity.nxdata.size
|
|
1149
1602
|
/ det_nxdata.intensity.nxdata.shape[-1])
|
|
1603
|
+
num_digit = len(str(num_frame))
|
|
1150
1604
|
if not save_figures:
|
|
1151
1605
|
ani = animation.FuncAnimation(
|
|
1152
1606
|
fig, animate,
|
|
@@ -1154,16 +1608,18 @@ class StrainAnalysisProcessor(Processor):
|
|
|
1154
1608
|
/ det_nxdata.intensity.nxdata.shape[-1]),
|
|
1155
1609
|
interval=1000, blit=True, repeat=False)
|
|
1156
1610
|
else:
|
|
1157
|
-
for i in range(
|
|
1611
|
+
for i in range(num_frame):
|
|
1158
1612
|
animate(i)
|
|
1159
1613
|
|
|
1160
1614
|
plt.close()
|
|
1161
1615
|
plt.subplots_adjust(top=1, bottom=0, left=0, right=1)
|
|
1162
1616
|
|
|
1163
1617
|
frames = []
|
|
1164
|
-
for i in range(
|
|
1618
|
+
for i in range(num_frame):
|
|
1165
1619
|
frame = plt.imread(
|
|
1166
|
-
os.path.join(
|
|
1620
|
+
os.path.join(
|
|
1621
|
+
path,
|
|
1622
|
+
f'frame_{str(i).zfill(num_digit)}.png'))
|
|
1167
1623
|
im = plt.imshow(frame, animated=True)
|
|
1168
1624
|
if not i:
|
|
1169
1625
|
plt.imshow(frame)
|
|
@@ -1180,7 +1636,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
1180
1636
|
path = os.path.join(
|
|
1181
1637
|
outputdir,
|
|
1182
1638
|
f'{detector.detector_name}_strainanalysis_'
|
|
1183
|
-
'unconstrained_fits.
|
|
1639
|
+
'unconstrained_fits.gif')
|
|
1184
1640
|
ani.save(path)
|
|
1185
1641
|
plt.close()
|
|
1186
1642
|
|
|
@@ -1203,10 +1659,10 @@ class StrainAnalysisProcessor(Processor):
|
|
|
1203
1659
|
linkdims(
|
|
1204
1660
|
fit_nxdata, {'axes': 'energy', 'index': len(map_config.shape)})
|
|
1205
1661
|
fit_nxdata.makelink(det_nxdata.energy)
|
|
1206
|
-
fit_nxdata.best_fit=
|
|
1207
|
-
fit_nxdata.residuals =
|
|
1208
|
-
fit_nxdata.redchi =
|
|
1209
|
-
fit_nxdata.success =
|
|
1662
|
+
fit_nxdata.best_fit= unconstrained_best_fit
|
|
1663
|
+
fit_nxdata.residuals = unconstrained_residuals
|
|
1664
|
+
fit_nxdata.redchi = unconstrained_redchi
|
|
1665
|
+
fit_nxdata.success = unconstrained_success
|
|
1210
1666
|
|
|
1211
1667
|
# Peak-by-peak results
|
|
1212
1668
|
fit_nxgroup.fit_hkl_centers = NXdata()
|