ChessAnalysisPipeline 0.0.15__py3-none-any.whl → 0.0.16__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 +1 -1
- CHAP/common/__init__.py +4 -0
- CHAP/common/models/integration.py +29 -26
- CHAP/common/models/map.py +186 -255
- CHAP/common/processor.py +956 -160
- CHAP/common/reader.py +93 -27
- CHAP/common/writer.py +15 -5
- CHAP/edd/__init__.py +2 -2
- CHAP/edd/models.py +299 -449
- CHAP/edd/processor.py +639 -448
- CHAP/edd/reader.py +232 -15
- CHAP/giwaxs/__init__.py +8 -0
- CHAP/giwaxs/models.py +100 -0
- CHAP/giwaxs/processor.py +520 -0
- CHAP/giwaxs/reader.py +5 -0
- CHAP/giwaxs/writer.py +5 -0
- CHAP/pipeline.py +47 -9
- CHAP/runner.py +160 -71
- CHAP/tomo/models.py +25 -25
- CHAP/tomo/processor.py +51 -79
- CHAP/utils/general.py +18 -0
- CHAP/utils/models.py +76 -49
- CHAP/utils/parfile.py +10 -2
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/RECORD +29 -25
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/WHEEL +1 -1
- CHAP/utils/scanparsers.py +0 -1544
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.15.dist-info → ChessAnalysisPipeline-0.0.16.dist-info}/top_level.txt +0 -0
CHAP/edd/processor.py
CHANGED
|
@@ -26,13 +26,9 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
26
26
|
length of the diffraction volume for an EDD setup.
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
|
-
def process(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
save_figures=False,
|
|
33
|
-
inputdir='.',
|
|
34
|
-
outputdir='.',
|
|
35
|
-
interactive=False):
|
|
29
|
+
def process(
|
|
30
|
+
self, data, config=None, save_figures=False, inputdir='.',
|
|
31
|
+
outputdir='.', interactive=False):
|
|
36
32
|
"""Return the calculated value of the DV length.
|
|
37
33
|
|
|
38
34
|
:param data: Input configuration for the raw scan data & DVL
|
|
@@ -43,17 +39,17 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
43
39
|
`None`.
|
|
44
40
|
:type config: dict, optional
|
|
45
41
|
:param save_figures: Save .pngs of plots for checking inputs &
|
|
46
|
-
outputs of this Processor, defaults to False
|
|
42
|
+
outputs of this Processor, defaults to `False`.
|
|
47
43
|
:type save_figures: bool, optional
|
|
48
44
|
:param outputdir: Directory to which any output figures will
|
|
49
|
-
be saved, defaults to '.'
|
|
45
|
+
be saved, defaults to `'.'`.
|
|
50
46
|
:type outputdir: str, optional
|
|
51
47
|
:param inputdir: Input directory, used only if files in the
|
|
52
48
|
input configuration are not absolute paths,
|
|
53
|
-
defaults to '.'
|
|
49
|
+
defaults to `'.'`.
|
|
54
50
|
:type inputdir: str, optional
|
|
55
51
|
:param interactive: Allows for user interactions, defaults to
|
|
56
|
-
False
|
|
52
|
+
`False`.
|
|
57
53
|
:type interactive: bool, optional
|
|
58
54
|
:raises RuntimeError: Unable to get a valid DVL configuration.
|
|
59
55
|
:return: Complete DVL configuraiton dictionary.
|
|
@@ -87,12 +83,9 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
87
83
|
|
|
88
84
|
return dvl_config.dict()
|
|
89
85
|
|
|
90
|
-
def measure_dvl(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
save_figures=False,
|
|
94
|
-
outputdir='.',
|
|
95
|
-
interactive=False):
|
|
86
|
+
def measure_dvl(
|
|
87
|
+
self, dvl_config, detector, save_figures=False, outputdir='.',
|
|
88
|
+
interactive=False):
|
|
96
89
|
"""Return a measured value for the length of the diffraction
|
|
97
90
|
volume. Use the iron foil raster scan data provided in
|
|
98
91
|
`dvl_config` and fit a gaussian to the sum of all MCA channel
|
|
@@ -107,13 +100,13 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
107
100
|
:type detector:
|
|
108
101
|
CHAP.edd.models.MCAElementDiffractionVolumeLengthConfig
|
|
109
102
|
:param save_figures: Save .pngs of plots for checking inputs &
|
|
110
|
-
outputs of this Processor, defaults to False
|
|
103
|
+
outputs of this Processor, defaults to `False`.
|
|
111
104
|
:type save_figures: bool, optional
|
|
112
105
|
:param outputdir: Directory to which any output figures will
|
|
113
|
-
be saved, defaults to '.'
|
|
106
|
+
be saved, defaults to `'.'`.
|
|
114
107
|
:type outputdir: str, optional
|
|
115
108
|
:param interactive: Allows for user interactions, defaults to
|
|
116
|
-
False
|
|
109
|
+
`False`.
|
|
117
110
|
:type interactive: bool, optional
|
|
118
111
|
:raises ValueError: No value provided for included bin ranges
|
|
119
112
|
for the MCA detector element.
|
|
@@ -128,6 +121,7 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
128
121
|
)
|
|
129
122
|
|
|
130
123
|
# Get raw MCA data from raster scan
|
|
124
|
+
raise RuntimeError('DiffractionVolumeLengthProcessor not updated yet')
|
|
131
125
|
mca_data = dvl_config.mca_data(detector)
|
|
132
126
|
|
|
133
127
|
# Blank out data below bin 500 (~25keV) as well as the last bin
|
|
@@ -258,14 +252,10 @@ class DiffractionVolumeLengthProcessor(Processor):
|
|
|
258
252
|
|
|
259
253
|
class LatticeParameterRefinementProcessor(Processor):
|
|
260
254
|
"""Processor to get a refined estimate for a sample's lattice
|
|
261
|
-
parameters"""
|
|
262
|
-
def process(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
save_figures=False,
|
|
266
|
-
outputdir='.',
|
|
267
|
-
inputdir='.',
|
|
268
|
-
interactive=False):
|
|
255
|
+
parameters."""
|
|
256
|
+
def process(
|
|
257
|
+
self, data, config=None, save_figures=False, outputdir='.',
|
|
258
|
+
inputdir='.', interactive=False):
|
|
269
259
|
"""Given a strain analysis configuration, return a copy
|
|
270
260
|
contining refined values for the materials' lattice
|
|
271
261
|
parameters."""
|
|
@@ -304,6 +294,8 @@ class LatticeParameterRefinementProcessor(Processor):
|
|
|
304
294
|
raise NotImplementedError(msg)
|
|
305
295
|
|
|
306
296
|
# Collect the raw MCA data
|
|
297
|
+
raise RuntimeError(
|
|
298
|
+
'LatticeParameterRefinementProcessor not updated yet')
|
|
307
299
|
self.logger.debug(f'Reading data ...')
|
|
308
300
|
mca_data = strain_analysis_config.mca_data()
|
|
309
301
|
self.logger.debug(f'... done')
|
|
@@ -313,9 +305,7 @@ class LatticeParameterRefinementProcessor(Processor):
|
|
|
313
305
|
else:
|
|
314
306
|
mca_data_summed = np.mean(
|
|
315
307
|
mca_data, axis=tuple(np.arange(1, mca_data.ndim-1)))
|
|
316
|
-
effective_map_shape = mca_data.shape[1:-1]
|
|
317
308
|
self.logger.debug(f'mca_data_summed.shape: {mca_data_summed.shape}')
|
|
318
|
-
self.logger.debug(f'effective_map_shape: {effective_map_shape}')
|
|
319
309
|
|
|
320
310
|
# Create the NXroot object
|
|
321
311
|
nxroot = NXroot()
|
|
@@ -330,7 +320,8 @@ class LatticeParameterRefinementProcessor(Processor):
|
|
|
330
320
|
nxsubentry[map_config.title] = MapProcessor.get_nxentry(map_config)
|
|
331
321
|
nxsubentry[f'{map_config.title}_lat_par_refinement'] = NXprocess()
|
|
332
322
|
nxprocess = nxsubentry[f'{map_config.title}_lat_par_refinement']
|
|
333
|
-
nxprocess.strain_analysis_config = dumps(
|
|
323
|
+
nxprocess.strain_analysis_config = dumps(
|
|
324
|
+
strain_analysis_config.dict())
|
|
334
325
|
nxprocess.calibration_config = dumps(calibration_config.dict())
|
|
335
326
|
|
|
336
327
|
lattice_parameters = []
|
|
@@ -350,7 +341,8 @@ class LatticeParameterRefinementProcessor(Processor):
|
|
|
350
341
|
nxentry.lat_par_output = NXsubentry()
|
|
351
342
|
nxentry.lat_par_output.attrs['schema'] = 'yaml'
|
|
352
343
|
nxentry.lat_par_output.attrs['filename'] = 'lattice_parameters.yaml'
|
|
353
|
-
nxentry.lat_par_output.data = dumps(
|
|
344
|
+
nxentry.lat_par_output.data = dumps(
|
|
345
|
+
strain_analysis_config.dict())
|
|
354
346
|
|
|
355
347
|
return nxroot
|
|
356
348
|
|
|
@@ -366,7 +358,7 @@ class LatticeParameterRefinementProcessor(Processor):
|
|
|
366
358
|
Return the averaged value of the calculated lattice parameters
|
|
367
359
|
across all spectra.
|
|
368
360
|
|
|
369
|
-
:param strain_analysis_config: Strain analysis configuration
|
|
361
|
+
:param strain_analysis_config: Strain analysis configuration.
|
|
370
362
|
:type strain_analysis_config:
|
|
371
363
|
CHAP.edd.models.StrainAnalysisConfig
|
|
372
364
|
:param calibration_config: Energy calibration configuration.
|
|
@@ -374,21 +366,21 @@ class LatticeParameterRefinementProcessor(Processor):
|
|
|
374
366
|
:param detector: A single MCA detector element configuration.
|
|
375
367
|
:type detector: CHAP.edd.models.MCAElementStrainAnalysisConfig
|
|
376
368
|
:param mca_data: Raw specta for the current MCA detector.
|
|
377
|
-
:type mca_data:
|
|
369
|
+
:type mca_data: numpy.ndarray
|
|
378
370
|
:param mca_data_summed: Raw specta for the current MCA detector
|
|
379
371
|
summed over all data point.
|
|
380
|
-
:type mca_data_summed:
|
|
372
|
+
:type mca_data_summed: numpy.ndarray
|
|
381
373
|
:param nxsubentry: NeXus subentry to store the detailed refined
|
|
382
374
|
lattice parameters for each detector.
|
|
383
375
|
:type nxsubentry: nexusformat.nexus.NXprocess
|
|
384
376
|
:param interactive: Boolean to indicate whether interactive
|
|
385
|
-
matplotlib figures should be presented
|
|
377
|
+
matplotlib figures should be presented.
|
|
386
378
|
:type interactive: bool
|
|
387
379
|
:param save_figures: Boolean to indicate whether figures
|
|
388
|
-
indicating the selection should be saved
|
|
380
|
+
indicating the selection should be saved.
|
|
389
381
|
:type save_figures: bool
|
|
390
382
|
:param outputdir: Where to save figures (if `save_figures` is
|
|
391
|
-
`True`)
|
|
383
|
+
`True`).
|
|
392
384
|
:type outputdir: str
|
|
393
385
|
:returns: List of refined lattice parameters for materials in
|
|
394
386
|
`strain_analysis_config`
|
|
@@ -610,7 +602,8 @@ class LatticeParameterRefinementProcessor(Processor):
|
|
|
610
602
|
a_uniform_mean = float(a_uniform.mean())
|
|
611
603
|
d_unconstrained = get_peak_locations(
|
|
612
604
|
unconstrained_fit_centers, detector.tth_calibrated)
|
|
613
|
-
a_unconstrained = (
|
|
605
|
+
a_unconstrained = (
|
|
606
|
+
Rs_map * d_unconstrained.flatten()).reshape(d_unconstrained.shape)
|
|
614
607
|
a_unconstrained = np.moveaxis(a_unconstrained, 0, -1)
|
|
615
608
|
a_unconstrained_mean = float(a_unconstrained.mean())
|
|
616
609
|
self.logger.warning(
|
|
@@ -715,19 +708,11 @@ class MCAEnergyCalibrationProcessor(Processor):
|
|
|
715
708
|
the location of fluorescence peaks whenever possible, _not_
|
|
716
709
|
diffraction peaks, as this Processor does not account for
|
|
717
710
|
2&theta."""
|
|
718
|
-
def process(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
fwhm_max=None,
|
|
724
|
-
max_energy_kev=200.0,
|
|
725
|
-
background=None,
|
|
726
|
-
baseline=False,
|
|
727
|
-
save_figures=False,
|
|
728
|
-
interactive=False,
|
|
729
|
-
inputdir='.',
|
|
730
|
-
outputdir='.'):
|
|
711
|
+
def process(
|
|
712
|
+
self, data, config=None, centers_range=20, fwhm_min=None,
|
|
713
|
+
fwhm_max=None, max_energy_kev=200.0, background=None,
|
|
714
|
+
baseline=False, save_figures=False, interactive=False,
|
|
715
|
+
inputdir='.', outputdir='.'):
|
|
731
716
|
"""For each detector in the `MCAEnergyCalibrationConfig`
|
|
732
717
|
provided with `data`, fit the specified peaks in the MCA
|
|
733
718
|
spectrum specified. Using the difference between the provided
|
|
@@ -756,7 +741,7 @@ class MCAEnergyCalibrationProcessor(Processor):
|
|
|
756
741
|
when performing the fit, defaults to `None`.
|
|
757
742
|
:type fwhm_max: float, optional
|
|
758
743
|
:param max_energy_kev: Maximum channel energy of the MCA in
|
|
759
|
-
keV, defaults to 200.0
|
|
744
|
+
keV, defaults to `200.0`.
|
|
760
745
|
:type max_energy_kev: float, optional
|
|
761
746
|
:param background: Background model for peak fitting.
|
|
762
747
|
:type background: str, list[str], optional
|
|
@@ -780,14 +765,31 @@ class MCAEnergyCalibrationProcessor(Processor):
|
|
|
780
765
|
version of the calibrated configuration.
|
|
781
766
|
:rtype: dict
|
|
782
767
|
"""
|
|
768
|
+
# Third party modules
|
|
769
|
+
from nexusformat.nexus import (
|
|
770
|
+
NXentry,
|
|
771
|
+
NXroot,
|
|
772
|
+
)
|
|
773
|
+
|
|
783
774
|
# Local modules
|
|
784
|
-
from CHAP.edd.models import
|
|
775
|
+
from CHAP.edd.models import (
|
|
776
|
+
BaselineConfig,
|
|
777
|
+
MCAElementCalibrationConfig,
|
|
778
|
+
)
|
|
785
779
|
from CHAP.utils.general import (
|
|
786
780
|
is_int,
|
|
787
781
|
is_num,
|
|
788
782
|
is_str_series,
|
|
789
783
|
)
|
|
790
784
|
|
|
785
|
+
# Load the detector data
|
|
786
|
+
# FIX input a numpy and create/use NXobject to numpy proc
|
|
787
|
+
# FIX right now spec info is lost in output yaml, add to it?
|
|
788
|
+
nxroot = self.get_data(data, 'SpecReader')
|
|
789
|
+
if not isinstance(nxroot, NXroot):
|
|
790
|
+
raise RuntimeError('No valid NXroot data in input pipeline data')
|
|
791
|
+
nxentry = nxroot[nxroot.default]
|
|
792
|
+
|
|
791
793
|
# Load the validated energy calibration configuration
|
|
792
794
|
try:
|
|
793
795
|
calibration_config = self.get_config(
|
|
@@ -805,6 +807,21 @@ class MCAEnergyCalibrationProcessor(Processor):
|
|
|
805
807
|
except Exception as dict_exc:
|
|
806
808
|
raise RuntimeError from dict_exc
|
|
807
809
|
|
|
810
|
+
# Validate the detector configuration
|
|
811
|
+
available_detector_indices = [
|
|
812
|
+
int(d) for d in nxentry.detector_names]
|
|
813
|
+
if calibration_config.detectors is None:
|
|
814
|
+
calibration_config.detectors = [
|
|
815
|
+
MCAElementCalibrationConfig(detector_name=d)
|
|
816
|
+
for d in nxentry.detector_names]
|
|
817
|
+
else:
|
|
818
|
+
for detector in deepcopy(calibration_config.detectors):
|
|
819
|
+
index = int(detector.detector_name)
|
|
820
|
+
if index not in available_detector_indices:
|
|
821
|
+
self.logger.warning(
|
|
822
|
+
f'Skipping detector {int} (no raw data)')
|
|
823
|
+
calibration_config.detectors.remove(detector)
|
|
824
|
+
|
|
808
825
|
# Validate the fit index range
|
|
809
826
|
if calibration_config.fit_index_ranges is None and not interactive:
|
|
810
827
|
raise RuntimeError(
|
|
@@ -838,19 +855,54 @@ class MCAEnergyCalibrationProcessor(Processor):
|
|
|
838
855
|
except Exception as dict_exc:
|
|
839
856
|
raise RuntimeError from dict_exc
|
|
840
857
|
|
|
841
|
-
#
|
|
858
|
+
# Collect and sum the detector data
|
|
859
|
+
mca_data = []
|
|
860
|
+
for scan_name in nxentry.spec_scans:
|
|
861
|
+
for scan_number, scan_data in nxentry.spec_scans[scan_name].items():
|
|
862
|
+
mca_data.append(scan_data.data.data.nxdata)
|
|
863
|
+
summed_detector_data = np.asarray(mca_data).sum(axis=(0,1))
|
|
864
|
+
|
|
865
|
+
# Get the detectors' num_bins parameter
|
|
842
866
|
for detector in calibration_config.detectors:
|
|
867
|
+
if detector.num_bins is None:
|
|
868
|
+
detector.num_bins = summed_detector_data.shape[-1]
|
|
869
|
+
|
|
870
|
+
# Check each detector's include_energy_ranges field against the
|
|
871
|
+
# flux file, if available.
|
|
872
|
+
if calibration_config.flux_file is not None:
|
|
873
|
+
flux = np.loadtxt(flux_file)
|
|
874
|
+
flux_file_energies = flux[:,0]/1.e3
|
|
875
|
+
flux_e_min = flux_file_energies.min()
|
|
876
|
+
flux_e_max = flux_file_energies.max()
|
|
877
|
+
for detector in calibration_config.detectors:
|
|
878
|
+
for i, (det_e_min, det_e_max) in enumerate(
|
|
879
|
+
deepcopy(detector.include_energy_ranges)):
|
|
880
|
+
if det_e_min < flux_e_min or det_e_max > flux_e_max:
|
|
881
|
+
energy_range = [float(max(det_e_min, flux_e_min)),
|
|
882
|
+
float(min(det_e_max, flux_e_max))]
|
|
883
|
+
print(
|
|
884
|
+
f'WARNING: include_energy_ranges[{i}] out of range'
|
|
885
|
+
f' ({detector.include_energy_ranges[i]}): adjusted'
|
|
886
|
+
f' to {energy_range}')
|
|
887
|
+
detector.include_energy_ranges[i] = energy_range
|
|
888
|
+
|
|
889
|
+
# Calibrate detector channel energies based on fluorescence peaks
|
|
890
|
+
for detector in calibration_config.detectors:
|
|
891
|
+
index = available_detector_indices.index(
|
|
892
|
+
int(detector.detector_name))
|
|
843
893
|
if background is not None:
|
|
844
894
|
detector.background = background.copy()
|
|
845
895
|
if baseline:
|
|
846
|
-
detector.baseline = baseline.
|
|
896
|
+
detector.baseline = baseline.model_copy()
|
|
847
897
|
detector.energy_calibration_coeffs = self.calibrate(
|
|
848
|
-
calibration_config, detector,
|
|
849
|
-
|
|
898
|
+
calibration_config, detector, summed_detector_data[index],
|
|
899
|
+
centers_range, fwhm_min, fwhm_max, max_energy_kev,
|
|
900
|
+
save_figures, interactive, outputdir)
|
|
850
901
|
|
|
851
902
|
return calibration_config.dict()
|
|
852
903
|
|
|
853
|
-
def calibrate(
|
|
904
|
+
def calibrate(
|
|
905
|
+
self, calibration_config, detector, spectrum, centers_range,
|
|
854
906
|
fwhm_min, fwhm_max, max_energy_kev, save_figures, interactive,
|
|
855
907
|
outputdir):
|
|
856
908
|
"""Return energy_calibration_coeffs (a, b, and c) for
|
|
@@ -861,6 +913,8 @@ class MCAEnergyCalibrationProcessor(Processor):
|
|
|
861
913
|
:type calibration_config: MCAEnergyCalibrationConfig
|
|
862
914
|
:param detector: Configuration of the current detector.
|
|
863
915
|
:type detector: MCAElementCalibrationConfig
|
|
916
|
+
:param spectrum: Summed MCA spectrum for the current detector.
|
|
917
|
+
:type spectrum: numpy.ndarray
|
|
864
918
|
:param centers_range: Set boundaries on the peak centers in
|
|
865
919
|
MCA channels when performing the fit. The min/max
|
|
866
920
|
possible values for the peak centers will be the initial
|
|
@@ -873,7 +927,7 @@ class MCAEnergyCalibrationProcessor(Processor):
|
|
|
873
927
|
when performing the fit, defaults to `None`.
|
|
874
928
|
:type fwhm_max: float, optional
|
|
875
929
|
:param max_energy_kev: Maximum channel energy of the MCA in
|
|
876
|
-
keV, defaults to 200.0
|
|
930
|
+
keV, defaults to `200.0`.
|
|
877
931
|
:type max_energy_kev: float
|
|
878
932
|
:param save_figures: Save .pngs of plots for checking inputs &
|
|
879
933
|
outputs of this Processor.
|
|
@@ -904,7 +958,7 @@ class MCAEnergyCalibrationProcessor(Processor):
|
|
|
904
958
|
|
|
905
959
|
self.logger.info(f'Calibrating detector {detector.detector_name}')
|
|
906
960
|
|
|
907
|
-
|
|
961
|
+
# Get the MCA bin energies
|
|
908
962
|
uncalibrated_energies = np.linspace(
|
|
909
963
|
0, max_energy_kev, detector.num_bins)
|
|
910
964
|
bins = np.arange(detector.num_bins, dtype=np.int16)
|
|
@@ -1266,22 +1320,13 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1266
1320
|
energy calibration coefficients for an EDD experimental setup.
|
|
1267
1321
|
"""
|
|
1268
1322
|
|
|
1269
|
-
def process(
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
centers_range=20,
|
|
1277
|
-
fwhm_min=None,
|
|
1278
|
-
fwhm_max=None,
|
|
1279
|
-
background=None,
|
|
1280
|
-
baseline=False,
|
|
1281
|
-
save_figures=False,
|
|
1282
|
-
inputdir='.',
|
|
1283
|
-
outputdir='.',
|
|
1284
|
-
interactive=False):
|
|
1323
|
+
def process(
|
|
1324
|
+
self, data, config=None, tth_initial_guess=None,
|
|
1325
|
+
include_energy_ranges=None, calibration_method='iterate_tth',
|
|
1326
|
+
quadratic_energy_calibration=False, centers_range=20,
|
|
1327
|
+
fwhm_min=None, fwhm_max=None, background=None, baseline=False,
|
|
1328
|
+
save_figures=False, inputdir='.', outputdir='.',
|
|
1329
|
+
interactive=False):
|
|
1285
1330
|
"""Return the calibrated 2&theta value and the fine tuned
|
|
1286
1331
|
energy calibration coefficients to convert MCA channel
|
|
1287
1332
|
indices to MCA channel energies.
|
|
@@ -1290,8 +1335,8 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1290
1335
|
procedure.
|
|
1291
1336
|
:type data: list[PipelineData]
|
|
1292
1337
|
:param config: Initialization parameters for an instance of
|
|
1293
|
-
CHAP.edd.models.MCATthCalibrationConfig,
|
|
1294
|
-
None
|
|
1338
|
+
CHAP.edd.models.MCATthCalibrationConfig,
|
|
1339
|
+
defaults to `None`.
|
|
1295
1340
|
:type config: dict, optional
|
|
1296
1341
|
:param tth_initial_guess: Initial guess for 2&theta to supercede
|
|
1297
1342
|
the values from the energy calibration detector cofiguration
|
|
@@ -1329,23 +1374,29 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1329
1374
|
defaults to `False`.
|
|
1330
1375
|
:type baseline: bool, BaselineConfig, optional
|
|
1331
1376
|
:param save_figures: Save .pngs of plots for checking inputs &
|
|
1332
|
-
outputs of this Processor, defaults to False
|
|
1377
|
+
outputs of this Processor, defaults to `False`.
|
|
1333
1378
|
:type save_figures: bool, optional
|
|
1334
1379
|
:param outputdir: Directory to which any output figures will
|
|
1335
|
-
be saved, defaults to '.'
|
|
1380
|
+
be saved, defaults to `'.'`.
|
|
1336
1381
|
:type outputdir: str, optional
|
|
1337
1382
|
:param inputdir: Input directory, used only if files in the
|
|
1338
1383
|
input configuration are not absolute paths,
|
|
1339
|
-
defaults to '.'
|
|
1384
|
+
defaults to `'.'`.
|
|
1340
1385
|
:type inputdir: str, optional
|
|
1341
|
-
:param interactive: Allows for user interactions,
|
|
1342
|
-
False
|
|
1386
|
+
:param interactive: Allows for user interactions,
|
|
1387
|
+
defaults to `False`.
|
|
1343
1388
|
:type interactive: bool, optional
|
|
1344
1389
|
:raises RuntimeError: Invalid or missing input configuration.
|
|
1345
1390
|
:return: Original configuration with the tuned values for
|
|
1346
1391
|
2&theta and the linear correction parameters added.
|
|
1347
1392
|
:rtype: dict[str,float]
|
|
1348
1393
|
"""
|
|
1394
|
+
# Third party modules
|
|
1395
|
+
from nexusformat.nexus import (
|
|
1396
|
+
NXentry,
|
|
1397
|
+
NXroot,
|
|
1398
|
+
)
|
|
1399
|
+
|
|
1349
1400
|
# Local modules
|
|
1350
1401
|
from CHAP.edd.models import BaselineConfig
|
|
1351
1402
|
from CHAP.utils.general import (
|
|
@@ -1353,6 +1404,15 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1353
1404
|
is_str_series,
|
|
1354
1405
|
)
|
|
1355
1406
|
|
|
1407
|
+
# Load the detector data
|
|
1408
|
+
# FIX input a numpy and create/use NXobject to numpy proc
|
|
1409
|
+
# FIX right now spec info is lost in output yaml, add to it?
|
|
1410
|
+
nxroot = self.get_data(data, 'SpecReader')
|
|
1411
|
+
if not isinstance(nxroot, NXroot):
|
|
1412
|
+
raise RuntimeError('No valid NXroot data in input pipeline data')
|
|
1413
|
+
nxentry = nxroot[nxroot.default]
|
|
1414
|
+
|
|
1415
|
+
# Load the validated 2&theta calibration configuration
|
|
1356
1416
|
try:
|
|
1357
1417
|
calibration_config = self.get_config(
|
|
1358
1418
|
data, 'edd.models.MCATthCalibrationConfig',
|
|
@@ -1371,6 +1431,26 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1371
1431
|
except Exception as dict_exc:
|
|
1372
1432
|
raise RuntimeError from dict_exc
|
|
1373
1433
|
|
|
1434
|
+
# Validate the detector configuration
|
|
1435
|
+
if calibration_config.detectors is None:
|
|
1436
|
+
raise RuntimeError('No available calibrated detectors')
|
|
1437
|
+
available_detector_indices = [int(d) for d in nxentry.detector_names]
|
|
1438
|
+
detectors = []
|
|
1439
|
+
for detector in calibration_config.detectors:
|
|
1440
|
+
index = int(detector.detector_name)
|
|
1441
|
+
if index in available_detector_indices:
|
|
1442
|
+
detectors.append(detector)
|
|
1443
|
+
else:
|
|
1444
|
+
self.logger.warning(
|
|
1445
|
+
f'Skipping detector {name} (no energy calibration data)')
|
|
1446
|
+
|
|
1447
|
+
# Validate the fit index range
|
|
1448
|
+
if calibration_config.fit_index_ranges is None and not interactive:
|
|
1449
|
+
raise RuntimeError(
|
|
1450
|
+
'If `fit_index_ranges` is not explicitly provided, '
|
|
1451
|
+
+ self.__class__.__name__
|
|
1452
|
+
+ ' must be run with `interactive=True`.')
|
|
1453
|
+
|
|
1374
1454
|
# Validate the optional inputs
|
|
1375
1455
|
if not is_int(centers_range, gt=0, log=False):
|
|
1376
1456
|
RuntimeError(f'Invalid centers_range parameter ({centers_range})')
|
|
@@ -1392,10 +1472,41 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1392
1472
|
except Exception as dict_exc:
|
|
1393
1473
|
raise RuntimeError from dict_exc
|
|
1394
1474
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1475
|
+
# Collect and sum the detector data
|
|
1476
|
+
mca_data = []
|
|
1477
|
+
for scan_name in nxentry.spec_scans:
|
|
1478
|
+
for scan_number, scan_data in nxentry.spec_scans[scan_name].items():
|
|
1479
|
+
mca_data.append(scan_data.data.data.nxdata)
|
|
1480
|
+
summed_detector_data = np.asarray(mca_data).sum(axis=(0,1))
|
|
1481
|
+
|
|
1482
|
+
# Get the detectors' num_bins parameter
|
|
1483
|
+
for detector in detectors:
|
|
1484
|
+
if detector.num_bins is None:
|
|
1485
|
+
detector.num_bins = summed_detector_data.shape[-1]
|
|
1486
|
+
|
|
1487
|
+
# Check each detector's include_energy_ranges field against the
|
|
1488
|
+
# flux file, if available.
|
|
1489
|
+
if calibration_config.flux_file is not None:
|
|
1490
|
+
flux = np.loadtxt(flux_file)
|
|
1491
|
+
flux_file_energies = flux[:,0]/1.e3
|
|
1492
|
+
flux_e_min = flux_file_energies.min()
|
|
1493
|
+
flux_e_max = flux_file_energies.max()
|
|
1494
|
+
for detector in detectors:
|
|
1495
|
+
for i, (det_e_min, det_e_max) in enumerate(
|
|
1496
|
+
deepcopy(detector.include_energy_ranges)):
|
|
1497
|
+
if det_e_min < flux_e_min or det_e_max > flux_e_max:
|
|
1498
|
+
energy_range = [float(max(det_e_min, flux_e_min)),
|
|
1499
|
+
float(min(det_e_max, flux_e_max))]
|
|
1500
|
+
print(
|
|
1501
|
+
f'WARNING: include_energy_ranges[{i}] out of range'
|
|
1502
|
+
f' ({detector.include_energy_ranges[i]}): adjusted'
|
|
1503
|
+
f' to {energy_range}')
|
|
1504
|
+
detector.include_energy_ranges[i] = energy_range
|
|
1505
|
+
|
|
1506
|
+
# Calibrate detector channel energies
|
|
1507
|
+
for detector in detectors:
|
|
1508
|
+
index = available_detector_indices.index(
|
|
1509
|
+
int(detector.detector_name))
|
|
1399
1510
|
if tth_initial_guess is not None:
|
|
1400
1511
|
detector.tth_initial_guess = tth_initial_guess
|
|
1401
1512
|
if include_energy_ranges is not None:
|
|
@@ -1405,22 +1516,17 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1405
1516
|
if baseline:
|
|
1406
1517
|
detector.baseline = baseline
|
|
1407
1518
|
self.calibrate(
|
|
1408
|
-
calibration_config, detector,
|
|
1409
|
-
centers_range, fwhm_min,
|
|
1410
|
-
outputdir)
|
|
1519
|
+
calibration_config, detector, summed_detector_data[index],
|
|
1520
|
+
quadratic_energy_calibration, centers_range, fwhm_min,
|
|
1521
|
+
fwhm_max, save_figures, interactive, outputdir)
|
|
1411
1522
|
|
|
1412
1523
|
return calibration_config.dict()
|
|
1413
1524
|
|
|
1414
|
-
def calibrate(
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
fwhm_min=None,
|
|
1420
|
-
fwhm_max=None,
|
|
1421
|
-
save_figures=False,
|
|
1422
|
-
interactive=False,
|
|
1423
|
-
outputdir='.'):
|
|
1525
|
+
def calibrate(
|
|
1526
|
+
self, calibration_config, detector, spectrum,
|
|
1527
|
+
quadratic_energy_calibration=False, centers_range=20,
|
|
1528
|
+
fwhm_min=None, fwhm_max=None, save_figures=False,
|
|
1529
|
+
interactive=False, outputdir='.'):
|
|
1424
1530
|
"""Iteratively calibrate 2&theta by fitting selected peaks of
|
|
1425
1531
|
an MCA spectrum until the computed strain is sufficiently
|
|
1426
1532
|
small. Use the fitted peak locations to determine linear
|
|
@@ -1432,6 +1538,8 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1432
1538
|
CHAP.edd.models.MCATthCalibrationConfig
|
|
1433
1539
|
:param detector: A single MCA detector element configuration.
|
|
1434
1540
|
:type detector: CHAP.edd.models.MCAElementCalibrationConfig
|
|
1541
|
+
:param spectrum: Summed MCA spectrum for the current detector.
|
|
1542
|
+
:type spectrum: numpy.ndarray
|
|
1435
1543
|
:param quadratic_energy_calibration: Adds a quadratic term to
|
|
1436
1544
|
the detector channel index to energy conversion, defaults
|
|
1437
1545
|
to `False` (linear only).
|
|
@@ -1448,13 +1556,13 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1448
1556
|
when performing the fit, defaults to `None`.
|
|
1449
1557
|
:type fwhm_max: float, optional
|
|
1450
1558
|
:param save_figures: Save .pngs of plots for checking inputs &
|
|
1451
|
-
outputs of this Processor, defaults to False
|
|
1559
|
+
outputs of this Processor, defaults to `False`.
|
|
1452
1560
|
:type save_figures: bool, optional
|
|
1453
1561
|
:param interactive: Allows for user interactions, defaults to
|
|
1454
|
-
False
|
|
1562
|
+
`False`.
|
|
1455
1563
|
:type interactive: bool, optional
|
|
1456
1564
|
:param outputdir: Directory to which any output figures will
|
|
1457
|
-
be saved, defaults to '.'
|
|
1565
|
+
be saved, defaults to `'.'`.
|
|
1458
1566
|
:type outputdir: str, optional
|
|
1459
1567
|
:raises ValueError: No value provided for included bin ranges
|
|
1460
1568
|
or the fitted HKLs for the MCA detector element.
|
|
@@ -1485,14 +1593,13 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1485
1593
|
hkls, ds = calibration_config.material.unique_hkls_ds(
|
|
1486
1594
|
tth_tol=detector.hkl_tth_tol, tth_max=detector.tth_max)
|
|
1487
1595
|
|
|
1488
|
-
#
|
|
1596
|
+
# Get the MCA bin energies
|
|
1489
1597
|
mca_bin_energies = detector.energies
|
|
1490
|
-
mca_data = calibration_config.mca_data(detector)
|
|
1491
1598
|
|
|
1492
1599
|
# Blank out data below 25 keV as well as the last bin
|
|
1493
1600
|
energy_mask = np.where(mca_bin_energies >= 25.0, 1, 0)
|
|
1494
1601
|
energy_mask[-1] = 0
|
|
1495
|
-
|
|
1602
|
+
spectrum = spectrum*energy_mask
|
|
1496
1603
|
|
|
1497
1604
|
# Subtract the baseline
|
|
1498
1605
|
if detector.baseline:
|
|
@@ -1506,13 +1613,13 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1506
1613
|
else:
|
|
1507
1614
|
filename = None
|
|
1508
1615
|
baseline, baseline_config = ConstructBaseline.construct_baseline(
|
|
1509
|
-
|
|
1616
|
+
spectrum, mask=energy_mask, tol=detector.baseline.tol,
|
|
1510
1617
|
lam=detector.baseline.lam, max_iter=detector.baseline.max_iter,
|
|
1511
1618
|
title=f'Baseline for detector {detector.detector_name}',
|
|
1512
1619
|
xlabel='Energy (keV)', ylabel='Intensity (counts)',
|
|
1513
1620
|
interactive=interactive, filename=filename)
|
|
1514
1621
|
|
|
1515
|
-
|
|
1622
|
+
spectrum -= baseline
|
|
1516
1623
|
detector.baseline.lam = baseline_config['lambda']
|
|
1517
1624
|
detector.baseline.attrs['num_iter'] = baseline_config['num_iter']
|
|
1518
1625
|
detector.baseline.attrs['error'] = baseline_config['error']
|
|
@@ -1525,7 +1632,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1525
1632
|
else:
|
|
1526
1633
|
filename = None
|
|
1527
1634
|
tth_init = select_tth_initial_guess(
|
|
1528
|
-
mca_bin_energies,
|
|
1635
|
+
mca_bin_energies, spectrum, hkls, ds,
|
|
1529
1636
|
detector.tth_initial_guess, interactive, filename)
|
|
1530
1637
|
detector.tth_initial_guess = tth_init
|
|
1531
1638
|
self.logger.debug(f'tth_initial_guess = {detector.tth_initial_guess}')
|
|
@@ -1540,7 +1647,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1540
1647
|
else:
|
|
1541
1648
|
num_hkl_min = 1
|
|
1542
1649
|
include_bin_ranges, hkl_indices = select_mask_and_hkls(
|
|
1543
|
-
mca_bin_energies,
|
|
1650
|
+
mca_bin_energies, spectrum, hkls, ds,
|
|
1544
1651
|
detector.tth_initial_guess,
|
|
1545
1652
|
preselected_bin_ranges=detector.include_bin_ranges,
|
|
1546
1653
|
num_hkl_min=num_hkl_min, detector_name=detector.detector_name,
|
|
@@ -1572,7 +1679,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1572
1679
|
'the detector\'s MCA Tth Calibration Configuration or '
|
|
1573
1680
|
're-run the pipeline with the --interactive flag.')
|
|
1574
1681
|
mca_mask = detector.mca_mask()
|
|
1575
|
-
|
|
1682
|
+
spectrum_fit = spectrum[mca_mask]
|
|
1576
1683
|
|
|
1577
1684
|
# Correct raw MCA data for variable flux at different energies
|
|
1578
1685
|
flux_correct = \
|
|
@@ -1580,7 +1687,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1580
1687
|
if flux_correct is not None:
|
|
1581
1688
|
mca_intensity_weights = flux_correct(
|
|
1582
1689
|
mca_bin_energies[mca_mask])
|
|
1583
|
-
|
|
1690
|
+
spectrum_fit = spectrum_fit / mca_intensity_weights
|
|
1584
1691
|
|
|
1585
1692
|
# Get the fluorescence peak info
|
|
1586
1693
|
e_xrf = calibration_config.peak_energies
|
|
@@ -1677,7 +1784,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1677
1784
|
# Perform the fit
|
|
1678
1785
|
fit = FitProcessor()
|
|
1679
1786
|
result = fit.process(
|
|
1680
|
-
NXdata(NXfield(
|
|
1787
|
+
NXdata(NXfield(spectrum_fit, 'y'),
|
|
1681
1788
|
NXfield(np.arange(detector.num_bins)[mca_mask], 'x')),
|
|
1682
1789
|
{'parameters': parameters, 'models': models, 'method': 'trf'})
|
|
1683
1790
|
|
|
@@ -1747,7 +1854,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1747
1854
|
'fwhm_min': fwhm_min, 'fwhm_max': fwhm_max})
|
|
1748
1855
|
fit = FitProcessor()
|
|
1749
1856
|
result = fit.process(
|
|
1750
|
-
NXdata(NXfield(
|
|
1857
|
+
NXdata(NXfield(spectrum_fit, 'y'),
|
|
1751
1858
|
NXfield(mca_bins_fit, 'x')),
|
|
1752
1859
|
{'models': models, 'method': 'trf'})
|
|
1753
1860
|
|
|
@@ -1865,7 +1972,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1865
1972
|
'fwhm_min': fwhm_min, 'fwhm_max': fwhm_max})
|
|
1866
1973
|
fit = FitProcessor()
|
|
1867
1974
|
result = fit.process(
|
|
1868
|
-
NXdata(NXfield(
|
|
1975
|
+
NXdata(NXfield(spectrum_fit, 'y'),
|
|
1869
1976
|
NXfield(mca_bins_fit, 'x')),
|
|
1870
1977
|
{'models': models, 'method': 'trf'})
|
|
1871
1978
|
|
|
@@ -1885,7 +1992,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1885
1992
|
# for the fluorescense peaks and Bragg's law for the Bragg
|
|
1886
1993
|
# peaks for given material properties and a freely
|
|
1887
1994
|
# adjustable 2&theta angle and MCA energy axis calibration
|
|
1888
|
-
norm =
|
|
1995
|
+
norm = spectrum_fit.max()
|
|
1889
1996
|
pars_init = [tth_init, b_init, c_init]
|
|
1890
1997
|
for amp, sig in zip(amplitudes_init, sigmas_init):
|
|
1891
1998
|
pars_init += [amp/norm, sig]
|
|
@@ -1910,7 +2017,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1910
2017
|
result = minimize(
|
|
1911
2018
|
cost_function_combined, pars_init,
|
|
1912
2019
|
args=(
|
|
1913
|
-
mca_bins_fit,
|
|
2020
|
+
mca_bins_fit, spectrum_fit/norm,
|
|
1914
2021
|
quadratic_energy_calibration, ds_fit,
|
|
1915
2022
|
indices_unconstrained, e_xrf),
|
|
1916
2023
|
method='Nelder-Mead', bounds=bounds)
|
|
@@ -1936,7 +2043,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1936
2043
|
best_fit_uniform += gaussian(
|
|
1937
2044
|
mca_bins_fit, a_fit, b_fit, c_fit, amplitudes_fit[i],
|
|
1938
2045
|
sigmas_fit[i], e_peak)
|
|
1939
|
-
residual_uniform =
|
|
2046
|
+
residual_uniform = spectrum_fit - best_fit_uniform
|
|
1940
2047
|
e_bragg_uniform = e_bragg_fit
|
|
1941
2048
|
strain_uniform = 0.0
|
|
1942
2049
|
|
|
@@ -1978,7 +2085,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
1978
2085
|
fit = FitProcessor()
|
|
1979
2086
|
uniform_fit = fit.process(
|
|
1980
2087
|
NXdata(
|
|
1981
|
-
NXfield(
|
|
2088
|
+
NXfield(spectrum_fit, 'y'),
|
|
1982
2089
|
NXfield(mca_bin_energies_fit, 'x')),
|
|
1983
2090
|
{'models': models, 'method': 'trf'})
|
|
1984
2091
|
|
|
@@ -2102,7 +2209,7 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
2102
2209
|
'fwhm_min': fwhm_min, 'fwhm_max': fwhm_max})
|
|
2103
2210
|
fit = FitProcessor()
|
|
2104
2211
|
result = fit.process(
|
|
2105
|
-
NXdata(NXfield(
|
|
2212
|
+
NXdata(NXfield(spectrum_fit, 'y'),
|
|
2106
2213
|
NXfield(mca_energies_fit, 'x')),
|
|
2107
2214
|
{'models': models, 'method': 'trf'})
|
|
2108
2215
|
best_fit_unconstrained = result.best_fit
|
|
@@ -2134,11 +2241,11 @@ class MCATthCalibrationProcessor(Processor):
|
|
|
2134
2241
|
transform=axs[0,0].get_xaxis_transform())
|
|
2135
2242
|
if flux_correct is None:
|
|
2136
2243
|
axs[0,0].plot(
|
|
2137
|
-
mca_energies_fit,
|
|
2244
|
+
mca_energies_fit, spectrum_fit, marker='.', c='C2', ms=3,
|
|
2138
2245
|
ls='', label='MCA data')
|
|
2139
2246
|
else:
|
|
2140
2247
|
axs[0,0].plot(
|
|
2141
|
-
mca_energies_fit,
|
|
2248
|
+
mca_energies_fit, spectrum_fit, marker='.', c='C2', ms=3,
|
|
2142
2249
|
ls='', label='Flux-corrected MCA data')
|
|
2143
2250
|
if calibration_method == 'iterate_tth':
|
|
2144
2251
|
label_unconstrained = 'Unconstrained'
|
|
@@ -2257,13 +2364,9 @@ class MCADataProcessor(Processor):
|
|
|
2257
2364
|
transformed according to the results of a energy/tth calibration.
|
|
2258
2365
|
"""
|
|
2259
2366
|
|
|
2260
|
-
def process(
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
save_figures=False,
|
|
2264
|
-
inputdir='.',
|
|
2265
|
-
outputdir='.',
|
|
2266
|
-
interactive=False):
|
|
2367
|
+
def process(
|
|
2368
|
+
self, data, config=None, save_figures=False, inputdir='.',
|
|
2369
|
+
outputdir='.', interactive=False):
|
|
2267
2370
|
"""Process configurations for a map and MCA detector(s), and
|
|
2268
2371
|
return the calibrated MCA data collected over the map.
|
|
2269
2372
|
|
|
@@ -2358,9 +2461,10 @@ class MCADataProcessor(Processor):
|
|
|
2358
2461
|
class MCACalibratedDataPlotter(Processor):
|
|
2359
2462
|
"""Convenience Processor for quickly visualizing calibrated MCA
|
|
2360
2463
|
data from a single scan. Returns None!"""
|
|
2361
|
-
def process(
|
|
2362
|
-
|
|
2363
|
-
|
|
2464
|
+
def process(
|
|
2465
|
+
self, data, spec_file, scan_number, scan_step_index=None,
|
|
2466
|
+
material=None, save_figures=False, interactive=False,
|
|
2467
|
+
outputdir='.'):
|
|
2364
2468
|
"""Show a maplotlib figure of the MCA data fom the scan
|
|
2365
2469
|
provided on a calibrated energy axis. If `scan_step_index` is
|
|
2366
2470
|
None, a plot of the sum of all spectra across the whole scan
|
|
@@ -2372,7 +2476,8 @@ class MCACalibratedDataPlotter(Processor):
|
|
|
2372
2476
|
:type spec_file: str
|
|
2373
2477
|
:param scan_number: Scan number of interest.
|
|
2374
2478
|
:type scan_number: int
|
|
2375
|
-
:param scan_step_index: Scan step index of interest,
|
|
2479
|
+
:param scan_step_index: Scan step index of interest,
|
|
2480
|
+
defaults to `None`.
|
|
2376
2481
|
:type scan_step_index: int, optional
|
|
2377
2482
|
:param material: Material parameters to plot HKLs for.
|
|
2378
2483
|
:type material: dict
|
|
@@ -2391,7 +2496,7 @@ class MCACalibratedDataPlotter(Processor):
|
|
|
2391
2496
|
import matplotlib.pyplot as plt
|
|
2392
2497
|
|
|
2393
2498
|
# Local modules
|
|
2394
|
-
from
|
|
2499
|
+
from chess_scanparsers import SMBMCAScanParser as ScanParser
|
|
2395
2500
|
|
|
2396
2501
|
if material is not None:
|
|
2397
2502
|
self.logger.warning('Plotting HKL lines is not supported yet.')
|
|
@@ -2418,20 +2523,20 @@ class MCACalibratedDataPlotter(Processor):
|
|
|
2418
2523
|
ax.set_ylabel('Intenstiy (a.u)')
|
|
2419
2524
|
for detector in calibration_config.detectors:
|
|
2420
2525
|
if scan_step_index is None:
|
|
2421
|
-
|
|
2526
|
+
spectrum = np.sum(
|
|
2422
2527
|
scanparser.get_all_detector_data(detector.detector_name),
|
|
2423
2528
|
axis=0)
|
|
2424
2529
|
else:
|
|
2425
|
-
|
|
2530
|
+
spectrum = scanparser.get_detector_data(
|
|
2426
2531
|
detector.detector_name, scan_step_index=scan_step_index)
|
|
2427
|
-
ax.plot(detector.energies,
|
|
2532
|
+
ax.plot(detector.energies, spectrum,
|
|
2428
2533
|
label=f'Detector {detector.detector_name}')
|
|
2429
2534
|
ax.legend()
|
|
2430
2535
|
if interactive:
|
|
2431
2536
|
plt.show()
|
|
2432
2537
|
if save_figures:
|
|
2433
2538
|
fig.savefig(os.path.join(
|
|
2434
|
-
outputdir, f'
|
|
2539
|
+
outputdir, f'spectrum_{scanparser.scan_title}'))
|
|
2435
2540
|
plt.close()
|
|
2436
2541
|
return None
|
|
2437
2542
|
|
|
@@ -2440,41 +2545,50 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2440
2545
|
"""Processor that takes a map of MCA data and returns a map of
|
|
2441
2546
|
sample strains
|
|
2442
2547
|
"""
|
|
2443
|
-
def
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2548
|
+
def __init__(self):
|
|
2549
|
+
super().__init__()
|
|
2550
|
+
self._save_figures = False
|
|
2551
|
+
self._inputdir = '.'
|
|
2552
|
+
self._outputdir = '.'
|
|
2553
|
+
self._interactive = False
|
|
2554
|
+
self._detectors = []
|
|
2555
|
+
self._detector_indices = []
|
|
2556
|
+
self._nxdata = None
|
|
2557
|
+
|
|
2558
|
+
def process(
|
|
2559
|
+
self, data, config=None, find_peaks=False, skip_animation=False,
|
|
2560
|
+
save_figures=False, inputdir='.', outputdir='.',
|
|
2561
|
+
interactive=False):
|
|
2451
2562
|
"""Return strain analysis maps & associated metadata in an NXprocess.
|
|
2452
2563
|
|
|
2453
2564
|
:param data: Input data containing configurations for a map,
|
|
2454
2565
|
completed energy/tth calibration, and parameters for strain
|
|
2455
|
-
analysis
|
|
2566
|
+
analysis.
|
|
2456
2567
|
:type data: list[PipelineData]
|
|
2457
2568
|
:param config: Initialization parameters for an instance of
|
|
2458
2569
|
CHAP.edd.models.StrainAnalysisConfig, defaults to
|
|
2459
|
-
None
|
|
2570
|
+
`None`.
|
|
2460
2571
|
:type config: dict, optional
|
|
2461
2572
|
:param find_peaks: Exclude peaks where the average spectrum
|
|
2462
2573
|
is below the `rel_height_cutoff` (in the detector
|
|
2463
2574
|
configuration) cutoff relative to the maximum value of the
|
|
2464
2575
|
average spectrum, defaults to `False`.
|
|
2465
2576
|
:type find_peaks: bool, optional
|
|
2577
|
+
:param skip_animation: Skip the animation and plotting of
|
|
2578
|
+
the strain analysis fits, defaults to `False`.
|
|
2579
|
+
:type skip_animation: bool, optional
|
|
2466
2580
|
:param save_figures: Save .pngs of plots for checking inputs &
|
|
2467
|
-
outputs of this Processor, defaults to False
|
|
2581
|
+
outputs of this Processor, defaults to `False`.
|
|
2468
2582
|
:type save_figures: bool, optional
|
|
2469
|
-
:param outputdir: Directory to which any output figures will
|
|
2470
|
-
be saved, defaults to '.'.
|
|
2471
|
-
:type outputdir: str, optional
|
|
2472
2583
|
:param inputdir: Input directory, used only if files in the
|
|
2473
2584
|
input configuration are not absolute paths,
|
|
2474
|
-
defaults to '.'
|
|
2585
|
+
defaults to `'.'`.
|
|
2475
2586
|
:type inputdir: str, optional
|
|
2587
|
+
:param outputdir: Directory to which any output figures will
|
|
2588
|
+
be saved, defaults to `'.'`.
|
|
2589
|
+
:type outputdir: str, optional
|
|
2476
2590
|
:param interactive: Allows for user interactions, defaults to
|
|
2477
|
-
False
|
|
2591
|
+
`False`.
|
|
2478
2592
|
:type interactive: bool, optional
|
|
2479
2593
|
:raises RuntimeError: Unable to get a valid strain analysis
|
|
2480
2594
|
configuration.
|
|
@@ -2484,52 +2598,105 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2484
2598
|
:rtype: nexusformat.nexus.NXprocess
|
|
2485
2599
|
|
|
2486
2600
|
"""
|
|
2487
|
-
#
|
|
2488
|
-
|
|
2489
|
-
|
|
2601
|
+
# Third party modules
|
|
2602
|
+
from nexusformat.nexus import (
|
|
2603
|
+
NXentry,
|
|
2604
|
+
NXroot,
|
|
2605
|
+
)
|
|
2606
|
+
|
|
2607
|
+
self._save_figures = save_figures
|
|
2608
|
+
self._outputdir = outputdir
|
|
2609
|
+
self._interactive = interactive
|
|
2610
|
+
|
|
2611
|
+
# Load the detector data
|
|
2612
|
+
try:
|
|
2613
|
+
nxentry = self.get_data(data, 'MapProcessor')
|
|
2614
|
+
if not isinstance(nxentry, NXentry):
|
|
2615
|
+
raise RuntimeError(
|
|
2616
|
+
'No valid NXentry data in MapProcessor pipeline data')
|
|
2617
|
+
except:
|
|
2618
|
+
try:
|
|
2619
|
+
try:
|
|
2620
|
+
nxroot = self.get_data(data, 'NexusReader')
|
|
2621
|
+
except:
|
|
2622
|
+
nxroot = self.get_data(data, 'NexusWriter')
|
|
2623
|
+
if not isinstance(nxroot, NXroot):
|
|
2624
|
+
raise RuntimeError(
|
|
2625
|
+
'No valid NXroot data in NexusWriter pipeline data')
|
|
2626
|
+
nxentry = nxroot[nxroot.default]
|
|
2627
|
+
if not isinstance(nxentry, NXentry):
|
|
2628
|
+
raise RuntimeError(
|
|
2629
|
+
'No valid NXentry data in NexusWriter pipeline data')
|
|
2630
|
+
except:
|
|
2631
|
+
raise RuntimeError(
|
|
2632
|
+
'No valid detector data in input pipeline data')
|
|
2633
|
+
|
|
2634
|
+
# Load the validated calibration and strain analysis configuration
|
|
2490
2635
|
try:
|
|
2491
2636
|
strain_analysis_config = self.get_config(
|
|
2492
2637
|
data, 'edd.models.StrainAnalysisConfig', inputdir=inputdir)
|
|
2493
2638
|
except Exception as data_exc:
|
|
2494
|
-
# Local modules
|
|
2495
|
-
from CHAP.edd.models import StrainAnalysisConfig
|
|
2496
|
-
|
|
2497
2639
|
self.logger.info('No valid strain analysis config in input '
|
|
2498
|
-
|
|
2640
|
+
'pipeline data, using config parameter instead')
|
|
2499
2641
|
try:
|
|
2642
|
+
# Local modules
|
|
2643
|
+
from CHAP.edd.models import StrainAnalysisConfig
|
|
2644
|
+
|
|
2500
2645
|
strain_analysis_config = StrainAnalysisConfig(
|
|
2501
2646
|
**config, inputdir=inputdir)
|
|
2502
2647
|
except Exception as dict_exc:
|
|
2503
2648
|
raise RuntimeError from dict_exc
|
|
2504
2649
|
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2650
|
+
# Validate the detector configuration and load, validate and
|
|
2651
|
+
# add the calibration info to the detectors
|
|
2652
|
+
calibration_config = self.get_config(
|
|
2653
|
+
data, 'edd.models.MCATthCalibrationConfig', inputdir=inputdir)
|
|
2654
|
+
calibration_detector_indices = [
|
|
2655
|
+
int(d.detector_name) for d in calibration_config.detectors]
|
|
2656
|
+
if 'detector_names' in nxentry:
|
|
2657
|
+
available_detector_indices = [
|
|
2658
|
+
int(d) for d in nxentry.detector_names]
|
|
2659
|
+
else:
|
|
2660
|
+
available_detector_indices = calibration_detector_indices
|
|
2661
|
+
if strain_analysis_config.detectors is None:
|
|
2662
|
+
# Local modules:
|
|
2663
|
+
from CHAP.edd.models import MCAElementStrainAnalysisConfig
|
|
2664
|
+
|
|
2665
|
+
strain_analysis_config.detectors = [
|
|
2666
|
+
MCAElementStrainAnalysisConfig(**dict(d))
|
|
2667
|
+
for d in calibration_config.detectors
|
|
2668
|
+
if int(d.detector_name) in available_detector_indices]
|
|
2669
|
+
for detector in deepcopy(strain_analysis_config.detectors):
|
|
2670
|
+
index = int(detector.detector_name)
|
|
2671
|
+
if index not in available_detector_indices:
|
|
2672
|
+
self.logger.warning(
|
|
2673
|
+
f'Skipping detector {index} (no raw data)')
|
|
2674
|
+
strain_analysis_config.detectors.remove(detector)
|
|
2675
|
+
elif index in calibration_detector_indices:
|
|
2676
|
+
self._detectors.append(detector)
|
|
2677
|
+
self._detector_indices.append(
|
|
2678
|
+
available_detector_indices.index(index))
|
|
2679
|
+
calibration = [
|
|
2680
|
+
d for d in calibration_config.detectors
|
|
2681
|
+
if d.detector_name == detector.detector_name][0]
|
|
2682
|
+
detector.add_calibration(calibration)
|
|
2683
|
+
else:
|
|
2684
|
+
self.logger.warning(f'Skipping detector {index} '
|
|
2685
|
+
'(no energy/tth calibration data)')
|
|
2686
|
+
if not self._detectors:
|
|
2687
|
+
raise ValueError('Unable to match an available calibrated '
|
|
2688
|
+
'detector for the strain analysis')
|
|
2689
|
+
|
|
2690
|
+
return self.strain_analysis(
|
|
2691
|
+
nxentry, strain_analysis_config, find_peaks, skip_animation)
|
|
2692
|
+
|
|
2693
|
+
def strain_analysis(
|
|
2694
|
+
self, nxentry, strain_analysis_config, find_peaks, skip_animation):
|
|
2695
|
+
"""Return NXroot containing the strain maps.
|
|
2696
|
+
|
|
2697
|
+
:param nxentry: The strain analysis map, including the raw
|
|
2698
|
+
detector data.
|
|
2699
|
+
:type nxentry: nexusformat.nexus.NXentry
|
|
2533
2700
|
:param strain_analysis_config: Strain analysis processing
|
|
2534
2701
|
configuration.
|
|
2535
2702
|
:type strain_analysis_config:
|
|
@@ -2539,19 +2706,14 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2539
2706
|
configuration) cutoff relative to the maximum value of the
|
|
2540
2707
|
average spectrum, defaults to `False`.
|
|
2541
2708
|
:type find_peaks: bool, optional
|
|
2542
|
-
:param
|
|
2543
|
-
|
|
2544
|
-
:type
|
|
2545
|
-
:
|
|
2546
|
-
be saved, defaults to '.'.
|
|
2547
|
-
:type outputdir: str, optional
|
|
2548
|
-
:param interactive: Allows for user interactions, defaults to
|
|
2549
|
-
False.
|
|
2550
|
-
:type interactive: bool, optional
|
|
2551
|
-
:return: NXroot containing strain maps.
|
|
2709
|
+
:param skip_animation: Skip the animation and plotting of
|
|
2710
|
+
the strain analysis fits, defaults to `False`.
|
|
2711
|
+
:type skip_animation: bool, optional
|
|
2712
|
+
:return: The strain maps.
|
|
2552
2713
|
:rtype: nexusformat.nexus.NXroot
|
|
2553
2714
|
"""
|
|
2554
2715
|
# Third party modules
|
|
2716
|
+
from json import loads
|
|
2555
2717
|
from nexusformat.nexus import (
|
|
2556
2718
|
NXcollection,
|
|
2557
2719
|
NXdata,
|
|
@@ -2561,87 +2723,91 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2561
2723
|
NXprocess,
|
|
2562
2724
|
NXroot,
|
|
2563
2725
|
)
|
|
2726
|
+
from nexusformat.nexus.tree import NXlinkfield
|
|
2564
2727
|
|
|
2565
2728
|
# Local modules
|
|
2566
2729
|
from CHAP.common import MapProcessor
|
|
2730
|
+
from CHAP.common.models.map import MapConfig
|
|
2567
2731
|
from CHAP.edd.utils import (
|
|
2568
2732
|
get_peak_locations,
|
|
2733
|
+
get_spectra_fits,
|
|
2569
2734
|
get_unique_hkls_ds,
|
|
2570
|
-
get_spectra_fits
|
|
2571
2735
|
)
|
|
2572
|
-
if interactive or save_figures:
|
|
2573
|
-
from CHAP.edd.utils import (
|
|
2574
|
-
select_material_params,
|
|
2575
|
-
select_mask_and_hkls,
|
|
2576
|
-
)
|
|
2577
2736
|
|
|
2578
|
-
def linkdims(
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
if
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
axes = ['map_index']
|
|
2587
|
-
for dims in field_dims:
|
|
2588
|
-
axes.append(dims['axes'])
|
|
2589
|
-
nxgroup.attrs[f'map_index_indices'] = 0
|
|
2590
|
-
for dim in map_config.dims:
|
|
2737
|
+
def linkdims(
|
|
2738
|
+
nxgroup, nxdata_source, field_dims=[], oversampling_axis={}):
|
|
2739
|
+
source_axes = nxdata_source.axes
|
|
2740
|
+
if isinstance(source_axes, str):
|
|
2741
|
+
source_axes = [source_axes]
|
|
2742
|
+
axes = []
|
|
2743
|
+
for dim in source_axes:
|
|
2744
|
+
axes.append(dim)
|
|
2591
2745
|
if dim in oversampling_axis:
|
|
2592
2746
|
bin_name = dim.replace('fly_', 'bin_')
|
|
2593
2747
|
axes[axes.index(dim)] = bin_name
|
|
2594
2748
|
nxgroup[bin_name] = NXfield(
|
|
2595
2749
|
value=oversampling_axis[dim],
|
|
2596
|
-
units=
|
|
2750
|
+
units=nxdata_source[dim].units,
|
|
2597
2751
|
attrs={
|
|
2598
2752
|
'long_name':
|
|
2599
|
-
f'oversampled {
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
f'oversampled {
|
|
2753
|
+
f'oversampled {nxdata_source[dim].long_name}',
|
|
2754
|
+
'data_type': nxdata_source[dim].data_type,
|
|
2755
|
+
'local_name':
|
|
2756
|
+
f'oversampled {nxdata_source[dim].local_name}'})
|
|
2603
2757
|
else:
|
|
2604
|
-
|
|
2605
|
-
|
|
2758
|
+
if isinstance(nxdata_source[dim], NXlinkfield):
|
|
2759
|
+
nxgroup[dim] = nxdata_source[dim]
|
|
2760
|
+
else:
|
|
2761
|
+
nxgroup.makelink(nxdata_source[dim])
|
|
2762
|
+
if f'{dim}_indices' in nxdata_source.attrs:
|
|
2606
2763
|
nxgroup.attrs[f'{dim}_indices'] = \
|
|
2607
|
-
|
|
2608
|
-
nxgroup.attrs['axes'] = axes
|
|
2764
|
+
nxdata_source.attrs[f'{dim}_indices']
|
|
2609
2765
|
for dims in field_dims:
|
|
2610
|
-
|
|
2766
|
+
axes.append(dims['axis'])
|
|
2767
|
+
nxgroup.attrs[f'{dims["axis"]}_indices'] = dims['index']
|
|
2768
|
+
nxgroup.attrs['axes'] = axes
|
|
2611
2769
|
|
|
2612
|
-
if not
|
|
2770
|
+
if not self._interactive and not strain_analysis_config.materials:
|
|
2613
2771
|
raise ValueError(
|
|
2614
2772
|
'No material provided. Provide a material in the '
|
|
2615
2773
|
'StrainAnalysis Configuration, or re-run the pipeline with '
|
|
2616
2774
|
'the --interactive flag.')
|
|
2617
2775
|
|
|
2776
|
+
# Get the map configuration
|
|
2777
|
+
map_config = MapConfig(**loads(str(nxentry.map_config)))
|
|
2778
|
+
|
|
2618
2779
|
# Create the NXroot object
|
|
2619
2780
|
nxroot = NXroot()
|
|
2620
|
-
nxroot[map_config.title] =
|
|
2621
|
-
nxentry = nxroot[map_config.title]
|
|
2781
|
+
nxroot[map_config.title] = nxentry
|
|
2622
2782
|
nxroot[f'{map_config.title}_strainanalysis'] = NXprocess()
|
|
2623
2783
|
nxprocess = nxroot[f'{map_config.title}_strainanalysis']
|
|
2624
|
-
nxprocess.strain_analysis_config = dumps(
|
|
2784
|
+
nxprocess.strain_analysis_config = dumps(
|
|
2785
|
+
strain_analysis_config.dict())
|
|
2625
2786
|
|
|
2626
2787
|
# Setup plottable data group
|
|
2627
|
-
nxprocess.data = NXdata()
|
|
2628
|
-
nxprocess.default = 'data'
|
|
2629
|
-
nxdata = nxprocess.data
|
|
2630
|
-
linkdims(nxdata)
|
|
2631
2788
|
|
|
2632
2789
|
# Collect the raw MCA data
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2790
|
+
if (strain_analysis_config.sum_axes
|
|
2791
|
+
and map_config.attrs['scan_type'] > 2):
|
|
2792
|
+
# FIX? Could make this a processor
|
|
2793
|
+
mca_data = self._get_sum_axes_data(
|
|
2794
|
+
nxentry[nxentry.default],
|
|
2795
|
+
map_config.attrs.get('fly_axis_labels', [])).astype(np.float64)
|
|
2796
|
+
nxprocess.data = self._nxdata
|
|
2797
|
+
nxdata = nxprocess.data
|
|
2639
2798
|
else:
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2799
|
+
mca_data = np.asarray(
|
|
2800
|
+
nxentry[nxentry.default].nxsignal[:,self._detector_indices,:],
|
|
2801
|
+
dtype=np.float64)
|
|
2802
|
+
nxprocess.data = NXdata()
|
|
2803
|
+
self._nxdata = nxprocess.data
|
|
2804
|
+
linkdims(self._nxdata, nxentry.data)
|
|
2805
|
+
self._nxdata.makelink(nxentry.data['detector_data'])
|
|
2806
|
+
self._nxdata.attrs['signal'] = 'detector_data'
|
|
2807
|
+
nxprocess.default = 'data'
|
|
2808
|
+
self.logger.debug(f'mca_data.shape: {mca_data.shape}')
|
|
2809
|
+
mca_data_mean = np.mean(mca_data, axis=0)
|
|
2810
|
+
self.logger.debug(f'mca_data_mean.shape: {mca_data_mean.shape}')
|
|
2645
2811
|
|
|
2646
2812
|
# Check for oversampling axis and create the binned coordinates
|
|
2647
2813
|
oversampling_axis = {}
|
|
@@ -2650,6 +2816,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2650
2816
|
# Local modules
|
|
2651
2817
|
from CHAP.utils.general import rolling_average
|
|
2652
2818
|
|
|
2819
|
+
raise RuntimeError('oversampling needs testing')
|
|
2653
2820
|
fly_axis = map_config.attrs.get('fly_axis_labels')[0]
|
|
2654
2821
|
oversampling = strain_analysis_config.oversampling
|
|
2655
2822
|
oversampling_axis[fly_axis] = rolling_average(
|
|
@@ -2661,129 +2828,27 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2661
2828
|
num=oversampling.get('num'),
|
|
2662
2829
|
mode=oversampling.get('mode', 'valid'))
|
|
2663
2830
|
|
|
2664
|
-
#
|
|
2665
|
-
|
|
2666
|
-
baselines = []
|
|
2667
|
-
for i, detector in enumerate(strain_analysis_config.detectors):
|
|
2831
|
+
# Get the energy masks
|
|
2832
|
+
energy_masks = self._get_energy_and_masks()
|
|
2668
2833
|
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
# Get the MCA bin energies
|
|
2676
|
-
mca_bin_energies = detector.energies
|
|
2834
|
+
# Get and subtract the detector baselines
|
|
2835
|
+
baselines = self._get_baselines(mca_data_mean, energy_masks)
|
|
2836
|
+
if baselines:
|
|
2837
|
+
baselines = np.asarray(baselines)
|
|
2838
|
+
mca_data_mean -= baselines
|
|
2839
|
+
mca_data -= baselines
|
|
2677
2840
|
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2841
|
+
# Adjust the material properties
|
|
2842
|
+
self._adjust_material_props(
|
|
2843
|
+
mca_data_mean, strain_analysis_config.materials, energy_masks)
|
|
2681
2844
|
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
from CHAP.common.processor import ConstructBaseline
|
|
2687
|
-
|
|
2688
|
-
if isinstance(detector.baseline, bool):
|
|
2689
|
-
detector.baseline = BaselineConfig()
|
|
2690
|
-
if save_figures:
|
|
2691
|
-
filename = os.path.join(
|
|
2692
|
-
outputdir,
|
|
2693
|
-
f'{detector.detector_name}_strainanalysis_'
|
|
2694
|
-
'baseline.png')
|
|
2695
|
-
else:
|
|
2696
|
-
filename = None
|
|
2697
|
-
baseline, baseline_config = \
|
|
2698
|
-
ConstructBaseline.construct_baseline(
|
|
2699
|
-
mca_data_summed[i], mask=energy_mask,
|
|
2700
|
-
tol=detector.baseline.tol, lam=detector.baseline.lam,
|
|
2701
|
-
max_iter=detector.baseline.max_iter,
|
|
2702
|
-
title=
|
|
2703
|
-
f'Baseline for detector {detector.detector_name}',
|
|
2704
|
-
xlabel='Energy (keV)', ylabel='Intensity (counts)',
|
|
2705
|
-
interactive=interactive, filename=filename)
|
|
2706
|
-
|
|
2707
|
-
mca_data_summed[i] -= baseline
|
|
2708
|
-
baselines.append(baseline)
|
|
2709
|
-
detector.baseline.lam = baseline_config['lambda']
|
|
2710
|
-
detector.baseline.attrs['num_iter'] = \
|
|
2711
|
-
baseline_config['num_iter']
|
|
2712
|
-
detector.baseline.attrs['error'] = baseline_config['error']
|
|
2713
|
-
|
|
2714
|
-
# Interactively adjust the material properties based on the
|
|
2715
|
-
# first detector calibration information and/or save figure
|
|
2716
|
-
# ASK: extend to multiple detectors?
|
|
2717
|
-
if not i and (interactive or save_figures):
|
|
2718
|
-
|
|
2719
|
-
tth = detector.tth_calibrated
|
|
2720
|
-
if save_figures:
|
|
2721
|
-
filename = os.path.join(
|
|
2722
|
-
outputdir,
|
|
2723
|
-
f'{detector.detector_name}_strainanalysis_'
|
|
2724
|
-
'material_config.png')
|
|
2725
|
-
else:
|
|
2726
|
-
filename = None
|
|
2727
|
-
strain_analysis_config.materials = select_material_params(
|
|
2728
|
-
mca_bin_energies, mca_data_summed[i]*energy_mask, tth,
|
|
2729
|
-
preselected_materials=strain_analysis_config.materials,
|
|
2730
|
-
label='Sum of all spectra in the map',
|
|
2731
|
-
interactive=interactive, filename=filename)
|
|
2732
|
-
self.logger.debug(
|
|
2733
|
-
f'materials: {strain_analysis_config.materials}')
|
|
2734
|
-
|
|
2735
|
-
# Mask during calibration
|
|
2736
|
-
calibration_bin_ranges = calibration.include_bin_ranges
|
|
2737
|
-
|
|
2738
|
-
# Get the unique HKLs and lattice spacings for the strain
|
|
2739
|
-
# analysis materials
|
|
2740
|
-
hkls, ds = get_unique_hkls_ds(
|
|
2741
|
-
strain_analysis_config.materials,
|
|
2742
|
-
tth_tol=detector.hkl_tth_tol,
|
|
2743
|
-
tth_max=detector.tth_max)
|
|
2744
|
-
|
|
2745
|
-
# Interactively adjust the mask and HKLs used in the
|
|
2746
|
-
# strain analysis
|
|
2747
|
-
if save_figures:
|
|
2748
|
-
filename = os.path.join(
|
|
2749
|
-
outputdir,
|
|
2750
|
-
f'{detector.detector_name}_strainanalysis_'
|
|
2751
|
-
'fit_mask_hkls.png')
|
|
2752
|
-
else:
|
|
2753
|
-
filename = None
|
|
2754
|
-
include_bin_ranges, hkl_indices = \
|
|
2755
|
-
select_mask_and_hkls(
|
|
2756
|
-
mca_bin_energies, mca_data_summed[i]*energy_mask,
|
|
2757
|
-
hkls, ds, detector.tth_calibrated,
|
|
2758
|
-
preselected_bin_ranges=detector.include_bin_ranges,
|
|
2759
|
-
preselected_hkl_indices=detector.hkl_indices,
|
|
2760
|
-
detector_name=detector.detector_name,
|
|
2761
|
-
ref_map=mca_data[i]*energy_mask,
|
|
2762
|
-
calibration_bin_ranges=calibration_bin_ranges,
|
|
2763
|
-
label='Sum of all spectra in the map',
|
|
2764
|
-
interactive=interactive, filename=filename)
|
|
2765
|
-
detector.include_energy_ranges = \
|
|
2766
|
-
detector.get_include_energy_ranges(include_bin_ranges)
|
|
2767
|
-
detector.hkl_indices = hkl_indices
|
|
2768
|
-
self.logger.debug(
|
|
2769
|
-
f'include_energy_ranges for detector {detector.detector_name}:'
|
|
2770
|
-
f' {detector.include_energy_ranges}')
|
|
2771
|
-
self.logger.debug(
|
|
2772
|
-
f'hkl_indices for detector {detector.detector_name}:'
|
|
2773
|
-
f' {detector.hkl_indices}')
|
|
2774
|
-
if not detector.include_energy_ranges:
|
|
2775
|
-
raise ValueError(
|
|
2776
|
-
'No value provided for include_energy_ranges. '
|
|
2777
|
-
'Provide them in the MCA Tth Calibration Configuration, '
|
|
2778
|
-
'or re-run the pipeline with the --interactive flag.')
|
|
2779
|
-
if not detector.hkl_indices:
|
|
2780
|
-
raise ValueError(
|
|
2781
|
-
'No value provided for hkl_indices. Provide them in '
|
|
2782
|
-
'the detector\'s MCA Tth Calibration Configuration, or'
|
|
2783
|
-
' re-run the pipeline with the --interactive flag.')
|
|
2845
|
+
# Get the mask and HKLs used in the strain analysis
|
|
2846
|
+
self._get_mask_hkls(
|
|
2847
|
+
mca_data, mca_data_mean, strain_analysis_config.materials,
|
|
2848
|
+
energy_masks)
|
|
2784
2849
|
|
|
2785
2850
|
# Loop over the detectors to perform the strain analysis
|
|
2786
|
-
for
|
|
2851
|
+
for index, detector in enumerate(self._detectors):
|
|
2787
2852
|
|
|
2788
2853
|
self.logger.info(f'Analysing detector {detector.detector_name}')
|
|
2789
2854
|
|
|
@@ -2807,44 +2872,32 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2807
2872
|
nxdetector.data = NXdata()
|
|
2808
2873
|
det_nxdata = nxdetector.data
|
|
2809
2874
|
linkdims(
|
|
2810
|
-
det_nxdata,
|
|
2811
|
-
{'
|
|
2875
|
+
det_nxdata, self._nxdata,
|
|
2876
|
+
[{'axis': 'energy', 'index': mca_data.ndim-1}],
|
|
2812
2877
|
oversampling_axis=oversampling_axis)
|
|
2813
2878
|
mask = detector.mca_mask()
|
|
2814
2879
|
energies = mca_bin_energies[mask]
|
|
2815
2880
|
det_nxdata.energy = NXfield(value=energies, attrs={'units': 'keV'})
|
|
2816
|
-
det_nxdata.intensity = NXfield(
|
|
2817
|
-
dtype=np.float64,
|
|
2818
|
-
shape=(*effective_map_shape, len(energies)),
|
|
2819
|
-
attrs={'units': 'counts'})
|
|
2820
2881
|
det_nxdata.tth = NXfield(
|
|
2821
2882
|
dtype=np.float64,
|
|
2822
|
-
shape=
|
|
2823
|
-
attrs={'units':'degrees', 'long_name': '2\u03B8 (degrees)'}
|
|
2824
|
-
)
|
|
2883
|
+
shape=(mca_data.shape[0]),
|
|
2884
|
+
attrs={'units':'degrees', 'long_name': '2\u03B8 (degrees)'})
|
|
2825
2885
|
det_nxdata.uniform_microstrain = NXfield(
|
|
2826
2886
|
dtype=np.float64,
|
|
2827
|
-
shape=
|
|
2828
|
-
attrs={'long_name':
|
|
2829
|
-
'Strain from uniform fit(\u03BC\u03B5)'})
|
|
2887
|
+
shape=(mca_data.shape[0]),
|
|
2888
|
+
attrs={'long_name': 'Strain from uniform fit(\u03BC\u03B5)'})
|
|
2830
2889
|
det_nxdata.unconstrained_microstrain = NXfield(
|
|
2831
2890
|
dtype=np.float64,
|
|
2832
|
-
shape=
|
|
2833
|
-
attrs={'long_name':
|
|
2891
|
+
shape=(mca_data.shape[0]),
|
|
2892
|
+
attrs={'long_name':
|
|
2834
2893
|
'Strain from unconstrained fit(\u03BC\u03B5)'})
|
|
2835
2894
|
|
|
2836
|
-
#
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
(mca_data[i][map_index]-baselines[i]).astype(
|
|
2843
|
-
np.float64)[mask]
|
|
2844
|
-
else:
|
|
2845
|
-
det_nxdata.intensity[map_index] = \
|
|
2846
|
-
mca_data[i][map_index].astype(np.float64)[mask]
|
|
2847
|
-
det_nxdata.summed_intensity = det_nxdata.intensity.sum(axis=-1)
|
|
2895
|
+
# Add detector data
|
|
2896
|
+
det_nxdata.intensity = NXfield(
|
|
2897
|
+
value=np.asarray([mca_data[i,index,:].astype(np.float64)[mask]
|
|
2898
|
+
for i in range(mca_data.shape[0])]),
|
|
2899
|
+
attrs={'units': 'counts'})
|
|
2900
|
+
det_nxdata.summed_intensity = det_nxdata.intensity.sum(axis=0)
|
|
2848
2901
|
|
|
2849
2902
|
# Perform strain analysis
|
|
2850
2903
|
self.logger.debug(
|
|
@@ -2868,9 +2921,9 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2868
2921
|
from CHAP.utils.general import index_nearest
|
|
2869
2922
|
|
|
2870
2923
|
peaks = find_peaks_scipy(
|
|
2871
|
-
|
|
2924
|
+
mca_data_mean[index],
|
|
2872
2925
|
height=(detector.rel_height_cutoff
|
|
2873
|
-
*
|
|
2926
|
+
* mca_data_mean[index][mask].max()),
|
|
2874
2927
|
width=5)
|
|
2875
2928
|
heights = peaks[1]['peak_heights']
|
|
2876
2929
|
widths = peaks[1]['widths']
|
|
@@ -2900,7 +2953,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2900
2953
|
continue
|
|
2901
2954
|
|
|
2902
2955
|
# Perform the fit
|
|
2903
|
-
self.logger.debug(f'Fitting {detector.detector_name} ...')
|
|
2956
|
+
self.logger.debug(f'Fitting detector {detector.detector_name} ...')
|
|
2904
2957
|
(uniform_fit_centers, uniform_fit_centers_errors,
|
|
2905
2958
|
uniform_fit_amplitudes, uniform_fit_amplitudes_errors,
|
|
2906
2959
|
uniform_fit_sigmas, uniform_fit_sigmas_errors,
|
|
@@ -2924,8 +2977,8 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2924
2977
|
fit_nxgroup.results = NXdata()
|
|
2925
2978
|
fit_nxdata = fit_nxgroup.results
|
|
2926
2979
|
linkdims(
|
|
2927
|
-
fit_nxdata,
|
|
2928
|
-
{'
|
|
2980
|
+
fit_nxdata, self._nxdata,
|
|
2981
|
+
[{'axis': 'energy', 'index': mca_data.ndim-1}],
|
|
2929
2982
|
oversampling_axis=oversampling_axis)
|
|
2930
2983
|
fit_nxdata.makelink(det_nxdata.energy)
|
|
2931
2984
|
fit_nxdata.best_fit = uniform_best_fit
|
|
@@ -2936,7 +2989,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2936
2989
|
# Peak-by-peak results
|
|
2937
2990
|
# fit_nxgroup.fit_hkl_centers = NXdata()
|
|
2938
2991
|
# fit_nxdata = fit_nxgroup.fit_hkl_centers
|
|
2939
|
-
# linkdims(fit_nxdata)
|
|
2992
|
+
# linkdims(fit_nxdata, self._nxdata)
|
|
2940
2993
|
for (hkl, center_guess, centers_fit, centers_error,
|
|
2941
2994
|
amplitudes_fit, amplitudes_error, sigmas_fit,
|
|
2942
2995
|
sigmas_error) in zip(
|
|
@@ -2952,7 +3005,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2952
3005
|
'keV'
|
|
2953
3006
|
# Report HKL peak centers
|
|
2954
3007
|
fit_nxgroup[hkl_name].centers = NXdata()
|
|
2955
|
-
linkdims(fit_nxgroup[hkl_name].centers)
|
|
3008
|
+
linkdims(fit_nxgroup[hkl_name].centers, self._nxdata)
|
|
2956
3009
|
fit_nxgroup[hkl_name].centers.values = NXfield(
|
|
2957
3010
|
value=centers_fit, attrs={'units': 'keV'})
|
|
2958
3011
|
fit_nxgroup[hkl_name].centers.errors = NXfield(
|
|
@@ -2962,7 +3015,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2962
3015
|
# fit_nxgroup[f'{hkl_name}/centers/values'], name=hkl_name)
|
|
2963
3016
|
# Report HKL peak amplitudes
|
|
2964
3017
|
fit_nxgroup[hkl_name].amplitudes = NXdata()
|
|
2965
|
-
linkdims(fit_nxgroup[hkl_name].amplitudes)
|
|
3018
|
+
linkdims(fit_nxgroup[hkl_name].amplitudes, self._nxdata)
|
|
2966
3019
|
fit_nxgroup[hkl_name].amplitudes.values = NXfield(
|
|
2967
3020
|
value=amplitudes_fit, attrs={'units': 'counts'})
|
|
2968
3021
|
fit_nxgroup[hkl_name].amplitudes.errors = NXfield(
|
|
@@ -2970,62 +3023,61 @@ class StrainAnalysisProcessor(Processor):
|
|
|
2970
3023
|
fit_nxgroup[hkl_name].amplitudes.attrs['signal'] = 'values'
|
|
2971
3024
|
# Report HKL peak FWHM
|
|
2972
3025
|
fit_nxgroup[hkl_name].sigmas = NXdata()
|
|
2973
|
-
linkdims(fit_nxgroup[hkl_name].sigmas)
|
|
3026
|
+
linkdims(fit_nxgroup[hkl_name].sigmas, self._nxdata)
|
|
2974
3027
|
fit_nxgroup[hkl_name].sigmas.values = NXfield(
|
|
2975
3028
|
value=sigmas_fit, attrs={'units': 'keV'})
|
|
2976
3029
|
fit_nxgroup[hkl_name].sigmas.errors = NXfield(
|
|
2977
3030
|
value=sigmas_error)
|
|
2978
3031
|
fit_nxgroup[hkl_name].sigmas.attrs['signal'] = 'values'
|
|
2979
3032
|
|
|
2980
|
-
if
|
|
3033
|
+
if ((self._interactive or self._save_figures)
|
|
3034
|
+
and not skip_animation):
|
|
2981
3035
|
# Third party modules
|
|
2982
3036
|
import matplotlib.animation as animation
|
|
2983
3037
|
import matplotlib.pyplot as plt
|
|
2984
3038
|
|
|
2985
|
-
if
|
|
3039
|
+
if self._save_figures:
|
|
2986
3040
|
path = os.path.join(
|
|
2987
|
-
|
|
3041
|
+
self._outputdir,
|
|
2988
3042
|
f'{detector.detector_name}_strainanalysis_'
|
|
2989
3043
|
'unconstrained_fits')
|
|
2990
3044
|
if not os.path.isdir(path):
|
|
2991
3045
|
os.mkdir(path)
|
|
2992
3046
|
|
|
2993
3047
|
def animate(i):
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3048
|
+
norm = det_nxdata.intensity.nxdata[i].max()
|
|
3049
|
+
intensity.set_ydata(det_nxdata.intensity.nxdata[i] / norm)
|
|
3050
|
+
best_fit.set_ydata(unconstrained_best_fit[i] / norm)
|
|
3051
|
+
axes = self._nxdata.attrs['axes']
|
|
3052
|
+
if isinstance(axes, str):
|
|
3053
|
+
axes = [axes]
|
|
3000
3054
|
index.set_text('\n'.join(
|
|
3001
3055
|
[f'norm = {int(norm)}'] +
|
|
3002
3056
|
['relative norm = '
|
|
3003
3057
|
f'{(norm / det_nxdata.intensity.max()):.5f}'] +
|
|
3004
|
-
[f'{
|
|
3005
|
-
for
|
|
3006
|
-
if
|
|
3058
|
+
[f'{dim}[{i}] = {self._nxdata[dim][i]}'
|
|
3059
|
+
for dim in axes]))
|
|
3060
|
+
if self._save_figures:
|
|
3007
3061
|
plt.savefig(os.path.join(
|
|
3008
3062
|
path, f'frame_{str(i).zfill(num_digit)}.png'))
|
|
3009
3063
|
return intensity, best_fit, index
|
|
3010
3064
|
|
|
3011
3065
|
fig, ax = plt.subplots()
|
|
3012
|
-
effective_map_shape
|
|
3013
|
-
map_index = np.unravel_index(0, effective_map_shape)
|
|
3014
3066
|
data_normalized = (
|
|
3015
|
-
det_nxdata.intensity.nxdata[
|
|
3016
|
-
/ det_nxdata.intensity.nxdata[
|
|
3067
|
+
det_nxdata.intensity.nxdata[0]
|
|
3068
|
+
/ det_nxdata.intensity.nxdata[0].max())
|
|
3017
3069
|
intensity, = ax.plot(
|
|
3018
3070
|
energies, data_normalized, 'b.', label='data')
|
|
3019
|
-
if unconstrained_best_fit[
|
|
3071
|
+
if unconstrained_best_fit[0].max():
|
|
3020
3072
|
fit_normalized = (
|
|
3021
|
-
unconstrained_best_fit[
|
|
3022
|
-
/ unconstrained_best_fit[
|
|
3073
|
+
unconstrained_best_fit[0]
|
|
3074
|
+
/ unconstrained_best_fit[0].max())
|
|
3023
3075
|
else:
|
|
3024
|
-
fit_normalized = unconstrained_best_fit[
|
|
3076
|
+
fit_normalized = unconstrained_best_fit[0]
|
|
3025
3077
|
best_fit, = ax.plot(
|
|
3026
3078
|
energies, fit_normalized, 'k-', label='fit')
|
|
3027
3079
|
# residual, = ax.plot(
|
|
3028
|
-
# energies, unconstrained_residuals[
|
|
3080
|
+
# energies, unconstrained_residuals[0], 'r-',
|
|
3029
3081
|
# label='residual')
|
|
3030
3082
|
ax.set(
|
|
3031
3083
|
title='Unconstrained fits',
|
|
@@ -3038,7 +3090,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3038
3090
|
num_frame = int(det_nxdata.intensity.nxdata.size
|
|
3039
3091
|
/ det_nxdata.intensity.nxdata.shape[-1])
|
|
3040
3092
|
num_digit = len(str(num_frame))
|
|
3041
|
-
if not
|
|
3093
|
+
if not self._save_figures:
|
|
3042
3094
|
ani = animation.FuncAnimation(
|
|
3043
3095
|
fig, animate,
|
|
3044
3096
|
frames=int(det_nxdata.intensity.nxdata.size
|
|
@@ -3066,18 +3118,18 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3066
3118
|
plt.gcf(), frames, interval=1000, blit=True,
|
|
3067
3119
|
repeat=False)
|
|
3068
3120
|
|
|
3069
|
-
if
|
|
3121
|
+
if self._interactive:
|
|
3070
3122
|
plt.show()
|
|
3071
3123
|
|
|
3072
|
-
if
|
|
3124
|
+
if self._save_figures:
|
|
3073
3125
|
path = os.path.join(
|
|
3074
|
-
|
|
3126
|
+
self._outputdir,
|
|
3075
3127
|
f'{detector.detector_name}_strainanalysis_'
|
|
3076
3128
|
'unconstrained_fits.gif')
|
|
3077
3129
|
ani.save(path)
|
|
3078
3130
|
plt.close()
|
|
3079
3131
|
|
|
3080
|
-
tth_map = detector.get_tth_map(
|
|
3132
|
+
tth_map = detector.get_tth_map((mca_data.shape[0],))
|
|
3081
3133
|
det_nxdata.tth.nxdata = tth_map
|
|
3082
3134
|
nominal_centers = np.asarray(
|
|
3083
3135
|
[get_peak_locations(d0, tth_map)
|
|
@@ -3101,8 +3153,8 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3101
3153
|
fit_nxgroup.results = NXdata()
|
|
3102
3154
|
fit_nxdata = fit_nxgroup.results
|
|
3103
3155
|
linkdims(
|
|
3104
|
-
fit_nxdata,
|
|
3105
|
-
{'
|
|
3156
|
+
fit_nxdata, self._nxdata,
|
|
3157
|
+
[{'axis': 'energy', 'index': mca_data.ndim-1}],
|
|
3106
3158
|
oversampling_axis=oversampling_axis)
|
|
3107
3159
|
fit_nxdata.makelink(det_nxdata.energy)
|
|
3108
3160
|
fit_nxdata.best_fit= unconstrained_best_fit
|
|
@@ -3113,7 +3165,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3113
3165
|
# Peak-by-peak results
|
|
3114
3166
|
fit_nxgroup.fit_hkl_centers = NXdata()
|
|
3115
3167
|
fit_nxdata = fit_nxgroup.fit_hkl_centers
|
|
3116
|
-
linkdims(fit_nxdata)
|
|
3168
|
+
linkdims(fit_nxdata, self._nxdata)
|
|
3117
3169
|
for (hkl, center_guesses, centers_fit, centers_error,
|
|
3118
3170
|
amplitudes_fit, amplitudes_error, sigmas_fit,
|
|
3119
3171
|
sigmas_error) in zip(
|
|
@@ -3127,7 +3179,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3127
3179
|
fit_nxgroup[hkl_name] = NXparameters()
|
|
3128
3180
|
# Report initial guesses HKL peak centers
|
|
3129
3181
|
fit_nxgroup[hkl_name].center_initial_guess = NXdata()
|
|
3130
|
-
linkdims(fit_nxgroup[hkl_name].center_initial_guess)
|
|
3182
|
+
linkdims(fit_nxgroup[hkl_name].center_initial_guess, self._nxdata)
|
|
3131
3183
|
fit_nxgroup[hkl_name].center_initial_guess.makelink(
|
|
3132
3184
|
nxdetector.uniform_fit[f'{hkl_name}/centers/values'],
|
|
3133
3185
|
name='values')
|
|
@@ -3135,7 +3187,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3135
3187
|
'values'
|
|
3136
3188
|
# Report HKL peak centers
|
|
3137
3189
|
fit_nxgroup[hkl_name].centers = NXdata()
|
|
3138
|
-
linkdims(fit_nxgroup[hkl_name].centers)
|
|
3190
|
+
linkdims(fit_nxgroup[hkl_name].centers, self._nxdata)
|
|
3139
3191
|
fit_nxgroup[hkl_name].centers.values = NXfield(
|
|
3140
3192
|
value=centers_fit, attrs={'units': 'keV'})
|
|
3141
3193
|
fit_nxgroup[hkl_name].centers.errors = NXfield(
|
|
@@ -3145,7 +3197,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3145
3197
|
fit_nxgroup[hkl_name].centers.attrs['signal'] = 'values'
|
|
3146
3198
|
# Report HKL peak amplitudes
|
|
3147
3199
|
fit_nxgroup[hkl_name].amplitudes = NXdata()
|
|
3148
|
-
linkdims(fit_nxgroup[hkl_name].amplitudes)
|
|
3200
|
+
linkdims(fit_nxgroup[hkl_name].amplitudes, self._nxdata)
|
|
3149
3201
|
fit_nxgroup[hkl_name].amplitudes.values = NXfield(
|
|
3150
3202
|
value=amplitudes_fit, attrs={'units': 'counts'})
|
|
3151
3203
|
fit_nxgroup[hkl_name].amplitudes.errors = NXfield(
|
|
@@ -3153,7 +3205,7 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3153
3205
|
fit_nxgroup[hkl_name].amplitudes.attrs['signal'] = 'values'
|
|
3154
3206
|
# Report HKL peak sigmas
|
|
3155
3207
|
fit_nxgroup[hkl_name].sigmas = NXdata()
|
|
3156
|
-
linkdims(fit_nxgroup[hkl_name].sigmas)
|
|
3208
|
+
linkdims(fit_nxgroup[hkl_name].sigmas, self._nxdata)
|
|
3157
3209
|
fit_nxgroup[hkl_name].sigmas.values = NXfield(
|
|
3158
3210
|
value=sigmas_fit, attrs={'units': 'keV'})
|
|
3159
3211
|
fit_nxgroup[hkl_name].sigmas.errors = NXfield(
|
|
@@ -3162,25 +3214,164 @@ class StrainAnalysisProcessor(Processor):
|
|
|
3162
3214
|
|
|
3163
3215
|
return nxroot
|
|
3164
3216
|
|
|
3217
|
+
def _get_sum_axes_data(self, nxdata, sum_axes):
|
|
3218
|
+
"""Get the raw MCA data collected by the scan averaged over the
|
|
3219
|
+
sum_axes.
|
|
3220
|
+
"""
|
|
3221
|
+
# Third party modules
|
|
3222
|
+
from nexusformat.nexus import (
|
|
3223
|
+
NXdata,
|
|
3224
|
+
NXfield,
|
|
3225
|
+
)
|
|
3165
3226
|
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3227
|
+
mca_data = np.asarray(
|
|
3228
|
+
nxdata.nxsignal[:,self._detector_indices,:])
|
|
3229
|
+
axes = [nxdata[axis] for axis in nxdata.axes if axis not in sum_axes]
|
|
3230
|
+
unique_points = []
|
|
3231
|
+
sum_indices = []
|
|
3232
|
+
for i in range(mca_data.shape[0]):
|
|
3233
|
+
point = [float(nxdata[axis.nxname][i]) for axis in axes]
|
|
3234
|
+
try:
|
|
3235
|
+
sum_indices[unique_points.index(point)].append(i)
|
|
3236
|
+
except:
|
|
3237
|
+
unique_points.append(point)
|
|
3238
|
+
sum_indices.append([i])
|
|
3239
|
+
mean_mca_data = np.empty((len(unique_points), *mca_data.shape[1:]))
|
|
3240
|
+
for i in range(len(unique_points)):
|
|
3241
|
+
mean_mca_data[i] = np.mean(mca_data[sum_indices[i],:,:], axis=0)
|
|
3242
|
+
self._nxdata = NXdata(
|
|
3243
|
+
NXfield(mean_mca_data, 'detector_data'),
|
|
3244
|
+
tuple([
|
|
3245
|
+
NXfield(
|
|
3246
|
+
[p[i] for p in unique_points], axis.nxname,
|
|
3247
|
+
attrs=axis.attrs)
|
|
3248
|
+
for i, axis in enumerate(axes)]))
|
|
3249
|
+
return mean_mca_data
|
|
3250
|
+
|
|
3251
|
+
def _get_energy_and_masks(self):
|
|
3252
|
+
"""Get the energy mask by blanking out data below 25 keV as
|
|
3253
|
+
well as that in the last bin.
|
|
3254
|
+
"""
|
|
3255
|
+
energy_masks = []
|
|
3256
|
+
for detector in self._detectors:
|
|
3257
|
+
energy_mask = np.where(detector.energies >= 25.0, 1, 0)
|
|
3258
|
+
energy_mask[-1] = 0
|
|
3259
|
+
energy_masks.append(energy_mask)
|
|
3260
|
+
return energy_masks
|
|
3261
|
+
|
|
3262
|
+
def _get_baselines(self, mca_data_mean, energy_masks):
|
|
3263
|
+
"""Get the detector baselines."""
|
|
3173
3264
|
# Local modules
|
|
3174
|
-
from CHAP.
|
|
3265
|
+
from CHAP.edd.models import BaselineConfig
|
|
3266
|
+
from CHAP.common.processor import ConstructBaseline
|
|
3175
3267
|
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3268
|
+
baselines = []
|
|
3269
|
+
for index, detector in enumerate(self._detectors):
|
|
3270
|
+
if detector.baseline:
|
|
3271
|
+
if isinstance(detector.baseline, bool):
|
|
3272
|
+
detector.baseline = BaselineConfig()
|
|
3273
|
+
if self._save_figures:
|
|
3274
|
+
filename = os.path.join(
|
|
3275
|
+
self._outputdir,
|
|
3276
|
+
f'{detector.detector_name}_strainanalysis_'
|
|
3277
|
+
'baseline.png')
|
|
3278
|
+
else:
|
|
3279
|
+
filename = None
|
|
3280
|
+
baseline, baseline_config = \
|
|
3281
|
+
ConstructBaseline.construct_baseline(
|
|
3282
|
+
mca_data_mean[index], mask=energy_masks[index],
|
|
3283
|
+
tol=detector.baseline.tol, lam=detector.baseline.lam,
|
|
3284
|
+
max_iter=detector.baseline.max_iter,
|
|
3285
|
+
title=
|
|
3286
|
+
f'Baseline for detector {detector.detector_name}',
|
|
3287
|
+
xlabel='Energy (keV)', ylabel='Intensity (counts)',
|
|
3288
|
+
interactive=self._interactive, filename=filename)
|
|
3289
|
+
|
|
3290
|
+
baselines.append(baseline)
|
|
3291
|
+
detector.baseline.lam = baseline_config['lambda']
|
|
3292
|
+
detector.baseline.attrs['num_iter'] = \
|
|
3293
|
+
baseline_config['num_iter']
|
|
3294
|
+
detector.baseline.attrs['error'] = baseline_config['error']
|
|
3295
|
+
return baselines
|
|
3296
|
+
|
|
3297
|
+
def _adjust_material_props(self, mca_data_mean, materials, energy_masks):
|
|
3298
|
+
"""Adjust the material properties."""
|
|
3299
|
+
# Local modules
|
|
3300
|
+
from CHAP.edd.utils import select_material_params
|
|
3301
|
+
|
|
3302
|
+
# ASK: extend to multiple detectors?
|
|
3303
|
+
detector = self._detectors[0]
|
|
3304
|
+
tth = detector.tth_calibrated
|
|
3305
|
+
if self._save_figures:
|
|
3306
|
+
filename = os.path.join(
|
|
3307
|
+
self._outputdir,
|
|
3308
|
+
f'{detector.detector_name}_strainanalysis_'
|
|
3309
|
+
'material_config.png')
|
|
3310
|
+
else:
|
|
3311
|
+
filename = None
|
|
3312
|
+
materials = select_material_params(
|
|
3313
|
+
detector.energies, mca_data_mean[0]*energy_masks[0],
|
|
3314
|
+
tth, label='Sum of all spectra in the map',
|
|
3315
|
+
preselected_materials=materials, interactive=self._interactive,
|
|
3316
|
+
filename=filename)
|
|
3317
|
+
self.logger.debug(f'materials: {materials}')
|
|
3318
|
+
|
|
3319
|
+
def _get_mask_hkls(self, mca_data, mca_data_mean, materials, energy_masks):
|
|
3320
|
+
"""Get the mask and HKLs used in the strain analysis."""
|
|
3321
|
+
# Local modules
|
|
3322
|
+
from CHAP.edd.utils import (
|
|
3323
|
+
get_unique_hkls_ds,
|
|
3324
|
+
select_mask_and_hkls,
|
|
3325
|
+
)
|
|
3326
|
+
|
|
3327
|
+
for index, detector in enumerate(self._detectors):
|
|
3181
3328
|
|
|
3182
|
-
|
|
3329
|
+
# Get the unique HKLs and lattice spacings for the strain
|
|
3330
|
+
# analysis materials
|
|
3331
|
+
hkls, ds = get_unique_hkls_ds(
|
|
3332
|
+
materials, tth_tol=detector.hkl_tth_tol,
|
|
3333
|
+
tth_max=detector.tth_max)
|
|
3183
3334
|
|
|
3335
|
+
# Interactively adjust the mask and HKLs used in the
|
|
3336
|
+
# strain analysis
|
|
3337
|
+
if self._save_figures:
|
|
3338
|
+
filename = os.path.join(
|
|
3339
|
+
self._outputdir,
|
|
3340
|
+
f'{detector.detector_name}_strainanalysis_'
|
|
3341
|
+
'fit_mask_hkls.png')
|
|
3342
|
+
else:
|
|
3343
|
+
filename = None
|
|
3344
|
+
include_bin_ranges, hkl_indices = \
|
|
3345
|
+
select_mask_and_hkls(
|
|
3346
|
+
detector.energies,
|
|
3347
|
+
mca_data_mean[index]*energy_masks[index],
|
|
3348
|
+
hkls, ds, detector.tth_calibrated,
|
|
3349
|
+
preselected_bin_ranges=detector.include_bin_ranges,
|
|
3350
|
+
preselected_hkl_indices=detector.hkl_indices,
|
|
3351
|
+
detector_name=detector.detector_name,
|
|
3352
|
+
ref_map=mca_data[:,index,:]*energy_masks[index],
|
|
3353
|
+
calibration_bin_ranges=detector.calibration_bin_ranges,
|
|
3354
|
+
label='Sum of all spectra in the map',
|
|
3355
|
+
interactive=self._interactive, filename=filename)
|
|
3356
|
+
detector.include_energy_ranges = \
|
|
3357
|
+
detector.get_include_energy_ranges(include_bin_ranges)
|
|
3358
|
+
detector.hkl_indices = hkl_indices
|
|
3359
|
+
self.logger.debug(
|
|
3360
|
+
f'include_energy_ranges for detector {detector.detector_name}:'
|
|
3361
|
+
f' {detector.include_energy_ranges}')
|
|
3362
|
+
self.logger.debug(
|
|
3363
|
+
f'hkl_indices for detector {detector.detector_name}:'
|
|
3364
|
+
f' {detector.hkl_indices}')
|
|
3365
|
+
if not detector.include_energy_ranges:
|
|
3366
|
+
raise ValueError(
|
|
3367
|
+
'No value provided for include_energy_ranges. '
|
|
3368
|
+
'Provide them in the MCA Tth Calibration Configuration, '
|
|
3369
|
+
'or re-run the pipeline with the --interactive flag.')
|
|
3370
|
+
if not detector.hkl_indices:
|
|
3371
|
+
raise ValueError(
|
|
3372
|
+
'No value provided for hkl_indices. Provide them in '
|
|
3373
|
+
'the detector\'s MCA Tth Calibration Configuration, or'
|
|
3374
|
+
' re-run the pipeline with the --interactive flag.')
|
|
3184
3375
|
|
|
3185
3376
|
if __name__ == '__main__':
|
|
3186
3377
|
# Local modules
|