ChessAnalysisPipeline 0.0.14__py3-none-any.whl → 0.0.15__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 +9 -0
- CHAP/common/models/map.py +295 -55
- CHAP/common/processor.py +846 -10
- CHAP/common/reader.py +171 -0
- CHAP/common/writer.py +181 -18
- CHAP/edd/__init__.py +10 -3
- CHAP/edd/models.py +822 -451
- CHAP/edd/processor.py +2221 -756
- CHAP/edd/reader.py +672 -0
- CHAP/edd/utils.py +846 -292
- CHAP/foxden/__init__.py +6 -0
- CHAP/foxden/processor.py +42 -0
- CHAP/foxden/writer.py +65 -0
- CHAP/pipeline.py +1 -1
- CHAP/runner.py +4 -4
- CHAP/tomo/models.py +7 -5
- CHAP/tomo/processor.py +118 -39
- CHAP/utils/__init__.py +1 -0
- CHAP/utils/fit.py +1292 -1315
- CHAP/utils/general.py +393 -53
- CHAP/utils/models.py +567 -0
- CHAP/utils/scanparsers.py +141 -28
- ChessAnalysisPipeline-0.0.15.dist-info/LICENSE +60 -0
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/RECORD +29 -25
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/WHEEL +1 -1
- ChessAnalysisPipeline-0.0.14.dist-info/LICENSE +0 -21
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.14.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/top_level.txt +0 -0
CHAP/edd/utils.py
CHANGED
|
@@ -25,6 +25,7 @@ def get_peak_locations(ds, tth):
|
|
|
25
25
|
|
|
26
26
|
return hc / (2. * ds * np.sin(0.5 * np.radians(tth)))
|
|
27
27
|
|
|
28
|
+
|
|
28
29
|
def make_material(name, sgnum, lattice_parameters, dmin=0.6):
|
|
29
30
|
"""Return a hexrd.material.Material with the given properties.
|
|
30
31
|
|
|
@@ -59,6 +60,7 @@ def make_material(name, sgnum, lattice_parameters, dmin=0.6):
|
|
|
59
60
|
|
|
60
61
|
return material
|
|
61
62
|
|
|
63
|
+
|
|
62
64
|
def get_unique_hkls_ds(materials, tth_tol=None, tth_max=None, round_sig=8):
|
|
63
65
|
"""Return the unique HKLs and lattice spacings for the given list
|
|
64
66
|
of materials.
|
|
@@ -107,8 +109,9 @@ def get_unique_hkls_ds(materials, tth_tol=None, tth_max=None, round_sig=8):
|
|
|
107
109
|
|
|
108
110
|
return hkls_unique, ds_unique
|
|
109
111
|
|
|
112
|
+
|
|
110
113
|
def select_tth_initial_guess(x, y, hkls, ds, tth_initial_guess=5.0,
|
|
111
|
-
interactive=False):
|
|
114
|
+
interactive=False, filename=None):
|
|
112
115
|
"""Show a matplotlib figure of a reference MCA spectrum on top of
|
|
113
116
|
HKL locations. The figure includes an input field to adjust the
|
|
114
117
|
initial 2&theta guess and responds by updating the HKL locations
|
|
@@ -127,13 +130,19 @@ def select_tth_initial_guess(x, y, hkls, ds, tth_initial_guess=5.0,
|
|
|
127
130
|
:ivar tth_initial_guess: Initial guess for 2&theta,
|
|
128
131
|
defaults to `5.0`.
|
|
129
132
|
:type tth_initial_guess: float, optional
|
|
130
|
-
:param interactive:
|
|
131
|
-
`
|
|
133
|
+
:param interactive: Show the plot and allow user interactions with
|
|
134
|
+
the matplotlib figure, defaults to `True`.
|
|
135
|
+
:type interactive: bool, optional
|
|
136
|
+
:param filename: Save a .png of the plot to filename, defaults to
|
|
137
|
+
`None`, in which case the plot is not saved.
|
|
138
|
+
:type filename: str, optional
|
|
132
139
|
:type interactive: bool, optional
|
|
133
|
-
:return:
|
|
134
|
-
|
|
135
|
-
:type: matplotlib.figure.Figure, float
|
|
140
|
+
:return: The selected initial guess for 2&theta.
|
|
141
|
+
:type: float
|
|
136
142
|
"""
|
|
143
|
+
if not interactive and filename is None:
|
|
144
|
+
return tth_initial_guess
|
|
145
|
+
|
|
137
146
|
# Third party modules
|
|
138
147
|
import matplotlib.pyplot as plt
|
|
139
148
|
from matplotlib.widgets import Button, TextBox
|
|
@@ -191,14 +200,15 @@ def select_tth_initial_guess(x, y, hkls, ds, tth_initial_guess=5.0,
|
|
|
191
200
|
fig_title = []
|
|
192
201
|
error_texts = []
|
|
193
202
|
|
|
203
|
+
assert np.asarray(hkls).shape[1] == 3
|
|
204
|
+
assert np.asarray(ds).size == np.asarray(hkls).shape[0]
|
|
205
|
+
|
|
206
|
+
# Setup the Matplotlib figure
|
|
194
207
|
title_pos = (0.5, 0.95)
|
|
195
208
|
title_props = {'fontsize': 'xx-large', 'ha': 'center', 'va': 'bottom'}
|
|
196
209
|
error_pos = (0.5, 0.90)
|
|
197
210
|
error_props = {'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
198
211
|
|
|
199
|
-
assert np.asarray(hkls).shape[1] == 3
|
|
200
|
-
assert np.asarray(ds).size == np.asarray(hkls).shape[0]
|
|
201
|
-
|
|
202
212
|
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
203
213
|
ax.plot(x, y)
|
|
204
214
|
ax.set_xlabel('MCA channel energy (keV)')
|
|
@@ -244,8 +254,12 @@ def select_tth_initial_guess(x, y, hkls, ds, tth_initial_guess=5.0,
|
|
|
244
254
|
tth_input.ax.remove()
|
|
245
255
|
confirm_btn.ax.remove()
|
|
246
256
|
|
|
247
|
-
|
|
248
|
-
|
|
257
|
+
# Save the figures if requested and close
|
|
258
|
+
if filename is not None:
|
|
259
|
+
fig_title[0].set_in_layout(True)
|
|
260
|
+
fig.tight_layout(rect=(0, 0, 1, 0.95))
|
|
261
|
+
fig.savefig(filename)
|
|
262
|
+
plt.close()
|
|
249
263
|
|
|
250
264
|
if not interactive:
|
|
251
265
|
tth_new_guess = tth_initial_guess
|
|
@@ -253,13 +267,14 @@ def select_tth_initial_guess(x, y, hkls, ds, tth_initial_guess=5.0,
|
|
|
253
267
|
try:
|
|
254
268
|
tth_new_guess = float(tth_input.text)
|
|
255
269
|
except:
|
|
256
|
-
|
|
257
|
-
x, y, hkls, ds, tth_initial_guess, interactive)
|
|
270
|
+
tth_new_guess = select_tth_initial_guess(
|
|
271
|
+
x, y, hkls, ds, tth_initial_guess, interactive, filename)
|
|
272
|
+
|
|
273
|
+
return tth_new_guess
|
|
258
274
|
|
|
259
|
-
return fig, tth_new_guess
|
|
260
275
|
|
|
261
|
-
def
|
|
262
|
-
interactive=False):
|
|
276
|
+
def select_material_params_old(x, y, tth, materials=[], label='Reference Data',
|
|
277
|
+
interactive=False, filename=None):
|
|
263
278
|
"""Interactively select the lattice parameters and space group for
|
|
264
279
|
a list of materials. A matplotlib figure will be shown with a plot
|
|
265
280
|
of the reference data (`x` and `y`). The figure will contain
|
|
@@ -280,17 +295,19 @@ def select_material_params(x, y, tth, materials=[], label='Reference Data',
|
|
|
280
295
|
:param label: Legend label for the 1D plot of reference MCA data
|
|
281
296
|
from the parameters `x`, `y`, defaults to `"Reference Data"`
|
|
282
297
|
:type label: str, optional
|
|
283
|
-
:param interactive:
|
|
284
|
-
`False`.
|
|
298
|
+
:param interactive: Show the plot and allow user interactions with
|
|
299
|
+
the matplotlib figure, defaults to `False`.
|
|
285
300
|
:type interactive: bool, optional
|
|
286
|
-
:
|
|
287
|
-
|
|
288
|
-
:
|
|
289
|
-
|
|
301
|
+
:param filename: Save a .png of the plot to filename, defaults to
|
|
302
|
+
`None`, in which case the plot is not saved.
|
|
303
|
+
:type filename: str, optional
|
|
304
|
+
:return: The selected materials for the strain analyses.
|
|
305
|
+
:rtype: list[CHAP.edd.models.MaterialConfig]
|
|
290
306
|
"""
|
|
291
307
|
# Third party modules
|
|
292
|
-
|
|
293
|
-
|
|
308
|
+
if interactive or filename is not None:
|
|
309
|
+
import matplotlib.pyplot as plt
|
|
310
|
+
from matplotlib.widgets import Button, TextBox
|
|
294
311
|
|
|
295
312
|
# Local modules
|
|
296
313
|
from CHAP.edd.models import MaterialConfig
|
|
@@ -443,65 +460,323 @@ def select_material_params(x, y, tth, materials=[], label='Reference Data',
|
|
|
443
460
|
widget_callbacks = []
|
|
444
461
|
error_texts = []
|
|
445
462
|
|
|
446
|
-
error_pos = (0.5, 0.95)
|
|
447
|
-
error_props = {'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
448
|
-
|
|
449
463
|
_materials = deepcopy(materials)
|
|
450
464
|
for i, m in enumerate(_materials):
|
|
451
465
|
if isinstance(m, MaterialConfig):
|
|
452
466
|
_materials[i] = m._material
|
|
453
467
|
|
|
454
|
-
#
|
|
468
|
+
# Create the Matplotlib figure
|
|
469
|
+
if interactive or filename is not None:
|
|
470
|
+
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
471
|
+
|
|
472
|
+
if not interactive:
|
|
473
|
+
|
|
474
|
+
draw_plot()
|
|
475
|
+
|
|
476
|
+
else:
|
|
477
|
+
|
|
478
|
+
error_pos = (0.5, 0.95)
|
|
479
|
+
error_props = {
|
|
480
|
+
'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
481
|
+
|
|
482
|
+
plt.subplots_adjust(bottom=0.1)
|
|
483
|
+
|
|
484
|
+
# Setup "Add material" button
|
|
485
|
+
add_material_btn = Button(
|
|
486
|
+
plt.axes([0.125, 0.015, 0.1, 0.05]), 'Add material')
|
|
487
|
+
add_material_cid = add_material_btn.on_clicked(add_material)
|
|
488
|
+
widget_callbacks.append([(add_material_btn, add_material_cid)])
|
|
489
|
+
|
|
490
|
+
# Setup "Confirm" button
|
|
491
|
+
confirm_btn = Button(plt.axes([0.75, 0.015, 0.1, 0.05]), 'Confirm')
|
|
492
|
+
confirm_cid = confirm_btn.on_clicked(confirm)
|
|
493
|
+
widget_callbacks.append([(confirm_btn, confirm_cid)])
|
|
494
|
+
|
|
495
|
+
# Setup material-property-editing buttons for each material
|
|
496
|
+
for material in _materials:
|
|
497
|
+
add_material(material=material)
|
|
498
|
+
|
|
499
|
+
# Show figure for user interaction
|
|
500
|
+
plt.show()
|
|
501
|
+
|
|
502
|
+
# Disconnect all widget callbacks when figure is closed
|
|
503
|
+
# and remove the buttons before returning the figure
|
|
504
|
+
for group in widget_callbacks:
|
|
505
|
+
for widget, callback in group:
|
|
506
|
+
widget.disconnect(callback)
|
|
507
|
+
widget.ax.remove()
|
|
508
|
+
|
|
509
|
+
# Save the figures if requested and close
|
|
510
|
+
fig.tight_layout()
|
|
511
|
+
if filename is not None:
|
|
512
|
+
fig.savefig(filename)
|
|
513
|
+
plt.close()
|
|
514
|
+
|
|
515
|
+
new_materials = [
|
|
516
|
+
MaterialConfig(
|
|
517
|
+
material_name=m.name, sgnum=m.sgnum,
|
|
518
|
+
lattice_parameters=[
|
|
519
|
+
m.latticeParameters[i].value for i in range(6)])
|
|
520
|
+
for m in _materials]
|
|
521
|
+
|
|
522
|
+
return new_materials
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def select_material_params(
|
|
526
|
+
x, y, tth, preselected_materials=[], label='Reference Data',
|
|
527
|
+
interactive=False, filename=None):
|
|
528
|
+
"""Interactively select the lattice parameters and space group for
|
|
529
|
+
a list of materials. A matplotlib figure will be shown with a plot
|
|
530
|
+
of the reference data (`x` and `y`). The figure will contain
|
|
531
|
+
widgets to add / remove materials and update selections for space
|
|
532
|
+
group number and lattice parameters for each one. The HKLs for the
|
|
533
|
+
materials defined by the widgets' values will be shown over the
|
|
534
|
+
reference data and updated when the widgets' values are
|
|
535
|
+
updated.
|
|
536
|
+
|
|
537
|
+
:param x: MCA channel energies.
|
|
538
|
+
:type x: np.ndarray
|
|
539
|
+
:param y: MCA intensities.
|
|
540
|
+
:type y: np.ndarray
|
|
541
|
+
:param tth: The (calibrated) 2&theta angle.
|
|
542
|
+
:type tth: float
|
|
543
|
+
:param materials: Materials to get HKLs and lattice spacings for,
|
|
544
|
+
default to `[]`.
|
|
545
|
+
:type materials: list[hexrd.material.Material], optional
|
|
546
|
+
:param label: Legend label for the 1D plot of reference MCA data
|
|
547
|
+
from the parameters `x`, `y`, defaults to `"Reference Data"`
|
|
548
|
+
:type label: str, optional
|
|
549
|
+
:param interactive: Show the plot and allow user interactions with
|
|
550
|
+
the matplotlib figure, defaults to `False`.
|
|
551
|
+
:type interactive: bool, optional
|
|
552
|
+
:param filename: Save a .png of the plot to filename, defaults to
|
|
553
|
+
`None`, in which case the plot is not saved.
|
|
554
|
+
:type filename: str, optional
|
|
555
|
+
:return: The selected materials for the strain analyses.
|
|
556
|
+
:rtype: list[CHAP.edd.models.MaterialConfig]
|
|
557
|
+
"""
|
|
558
|
+
# Third party modules
|
|
559
|
+
if interactive or filename is not None:
|
|
560
|
+
from hexrd.material import Material
|
|
561
|
+
import matplotlib.pyplot as plt
|
|
562
|
+
from matplotlib.widgets import Button, TextBox, RadioButtons
|
|
563
|
+
|
|
564
|
+
# Local modules
|
|
565
|
+
from CHAP.edd.models import MaterialConfig
|
|
566
|
+
from CHAP.utils.general import round_to_n
|
|
567
|
+
|
|
568
|
+
def add_material(new_material, add_buttons):
|
|
569
|
+
if isinstance(new_material, Material):
|
|
570
|
+
m = new_material
|
|
571
|
+
else:
|
|
572
|
+
if not isinstance(new_material, MaterialConfig):
|
|
573
|
+
new_material = MaterialConfig(**new_material)
|
|
574
|
+
m = new_material._material
|
|
575
|
+
materials.append(m)
|
|
576
|
+
lat_params = [round_to_n(m.latticeParameters[i].value, 6)
|
|
577
|
+
for i in range(6)]
|
|
578
|
+
bottom = 0.05*len(materials)
|
|
579
|
+
if interactive:
|
|
580
|
+
bottom += 0.075
|
|
581
|
+
mat_texts.append(
|
|
582
|
+
plt.figtext(
|
|
583
|
+
0.15, bottom,
|
|
584
|
+
f'- {m.name}: sgnum = {m.sgnum}, lat params = {lat_params}',
|
|
585
|
+
fontsize='large', ha='left', va='center'))
|
|
586
|
+
def add(event):
|
|
587
|
+
"""Callback function for the "Add" button."""
|
|
588
|
+
added_material.append(True)
|
|
589
|
+
plt.close()
|
|
590
|
+
|
|
591
|
+
def remove(event):
|
|
592
|
+
"""Callback function for the "Remove" button."""
|
|
593
|
+
for mat_text in mat_texts:
|
|
594
|
+
mat_text.remove()
|
|
595
|
+
mat_texts.clear()
|
|
596
|
+
for button in buttons:
|
|
597
|
+
button[0].disconnect(button[1])
|
|
598
|
+
button[0].ax.remove()
|
|
599
|
+
buttons.clear()
|
|
600
|
+
if len(materials) == 1:
|
|
601
|
+
removed_material.clear()
|
|
602
|
+
removed_material.append(materials[0].name)
|
|
603
|
+
plt.close()
|
|
604
|
+
else:
|
|
605
|
+
def remove_material(label):
|
|
606
|
+
removed_material.clear()
|
|
607
|
+
removed_material.append(label)
|
|
608
|
+
radio_btn.disconnect(radio_cid)
|
|
609
|
+
radio_btn.ax.remove()
|
|
610
|
+
plt.close()
|
|
611
|
+
|
|
612
|
+
mat_texts.append(
|
|
613
|
+
plt.figtext(
|
|
614
|
+
0.1, 0.1 + 0.05*len(materials),
|
|
615
|
+
'Select a material to remove:',
|
|
616
|
+
fontsize='x-large', ha='left', va='center'))
|
|
617
|
+
radio_btn = RadioButtons(
|
|
618
|
+
plt.axes([0.1, 0.05, 0.3, 0.05*len(materials)]),
|
|
619
|
+
labels = list(reversed([m.name for m in materials])),
|
|
620
|
+
activecolor='k')
|
|
621
|
+
removed_material.append(radio_btn.value_selected)
|
|
622
|
+
radio_cid = radio_btn.on_clicked(remove_material)
|
|
623
|
+
plt.draw()
|
|
624
|
+
|
|
625
|
+
def accept(event):
|
|
626
|
+
"""Callback function for the "Accept" button."""
|
|
627
|
+
plt.close()
|
|
628
|
+
|
|
629
|
+
materials = []
|
|
630
|
+
added_material = []
|
|
631
|
+
removed_material = []
|
|
632
|
+
mat_texts = []
|
|
633
|
+
buttons = []
|
|
634
|
+
|
|
635
|
+
# Create figure
|
|
455
636
|
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
637
|
+
ax.set_title(label, fontsize='x-large')
|
|
638
|
+
ax.set_xlabel('MCA channel energy (keV)', fontsize='large')
|
|
639
|
+
ax.set_ylabel('MCA intensity (counts)', fontsize='large')
|
|
640
|
+
ax.set_xlim(x[0], x[-1])
|
|
641
|
+
ax.plot(x, y)
|
|
456
642
|
|
|
457
|
-
|
|
643
|
+
# Add materials
|
|
644
|
+
for m in reversed(preselected_materials):
|
|
645
|
+
add_material(m, interactive)
|
|
646
|
+
|
|
647
|
+
# Add materials to figure
|
|
648
|
+
for i, material in enumerate(materials):
|
|
649
|
+
hkls, ds = get_unique_hkls_ds([material])
|
|
650
|
+
E0s = get_peak_locations(ds, tth)
|
|
651
|
+
for hkl, E0 in zip(hkls, E0s):
|
|
652
|
+
if x[0] <= E0 <= x[-1]:
|
|
653
|
+
ax.axvline(E0, c=f'C{i}', ls='--', lw=1)
|
|
654
|
+
ax.text(E0, 1, str(hkl)[1:-1], c=f'C{i}',
|
|
655
|
+
ha='right', va='top', rotation=90,
|
|
656
|
+
transform=ax.get_xaxis_transform())
|
|
458
657
|
|
|
459
|
-
|
|
658
|
+
if not interactive:
|
|
460
659
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
660
|
+
if materials:
|
|
661
|
+
mat_texts.append(
|
|
662
|
+
plt.figtext(
|
|
663
|
+
0.1, 0.05 + 0.05*len(materials),
|
|
664
|
+
'Currently selected materials:',
|
|
665
|
+
fontsize='x-large', ha='left', va='center'))
|
|
666
|
+
plt.subplots_adjust(bottom=0.125 + 0.05*len(materials))
|
|
466
667
|
|
|
467
|
-
|
|
468
|
-
confirm_btn = Button(plt.axes([0.75, 0.015, 0.1, 0.05]), 'Confirm')
|
|
469
|
-
confirm_cid = confirm_btn.on_clicked(confirm)
|
|
470
|
-
widget_callbacks.append([(confirm_btn, confirm_cid)])
|
|
668
|
+
else:
|
|
471
669
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
670
|
+
if materials:
|
|
671
|
+
mat_texts.append(
|
|
672
|
+
plt.figtext(
|
|
673
|
+
0.1, 0.125 + 0.05*len(materials),
|
|
674
|
+
'Currently selected materials:',
|
|
675
|
+
fontsize='x-large', ha='left', va='center'))
|
|
676
|
+
else:
|
|
677
|
+
mat_texts.append(
|
|
678
|
+
plt.figtext(
|
|
679
|
+
0.1, 0.125, 'Add at least one material',
|
|
680
|
+
fontsize='x-large', ha='left', va='center'))
|
|
681
|
+
plt.subplots_adjust(bottom=0.2 + 0.05*len(materials))
|
|
682
|
+
|
|
683
|
+
# Setup "Add" button
|
|
684
|
+
add_btn = Button(plt.axes([0.1, 0.025, 0.15, 0.05]), 'Add material')
|
|
685
|
+
add_cid = add_btn.on_clicked(add)
|
|
686
|
+
buttons.append((add_btn, add_cid))
|
|
687
|
+
|
|
688
|
+
# Setup "Remove" button
|
|
689
|
+
if materials:
|
|
690
|
+
remove_btn = Button(
|
|
691
|
+
plt.axes([0.425, 0.025, 0.15, 0.05]), f'Remove material')
|
|
692
|
+
remove_cid = remove_btn.on_clicked(remove)
|
|
693
|
+
buttons.append((remove_btn, remove_cid))
|
|
694
|
+
|
|
695
|
+
# Setup "Accept" button
|
|
696
|
+
accept_btn = Button(
|
|
697
|
+
plt.axes([0.75, 0.025, 0.15, 0.05]), 'Accept materials')
|
|
698
|
+
accept_cid = accept_btn.on_clicked(accept)
|
|
699
|
+
buttons.append((accept_btn, accept_cid))
|
|
475
700
|
|
|
476
|
-
# Show figure for user interaction
|
|
477
701
|
plt.show()
|
|
478
702
|
|
|
479
703
|
# Disconnect all widget callbacks when figure is closed
|
|
480
704
|
# and remove the buttons before returning the figure
|
|
481
|
-
for
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
705
|
+
for button in buttons:
|
|
706
|
+
button[0].disconnect(button[1])
|
|
707
|
+
button[0].ax.remove()
|
|
708
|
+
buttons.clear()
|
|
709
|
+
|
|
710
|
+
if filename is not None:
|
|
711
|
+
for mat_text in mat_texts:
|
|
712
|
+
pos = mat_text.get_position()
|
|
713
|
+
if interactive:
|
|
714
|
+
mat_text.set_position((pos[0], pos[1]-0.075))
|
|
715
|
+
else:
|
|
716
|
+
mat_text.set_position(pos)
|
|
717
|
+
if mat_text.get_text() == 'Currently selected materials:':
|
|
718
|
+
mat_text.set_text('Selected materials:')
|
|
719
|
+
mat_text.set_in_layout(True)
|
|
720
|
+
fig.tight_layout(rect=(0, 0.05 + 0.05*len(materials), 1, 1))
|
|
721
|
+
fig.savefig(filename)
|
|
722
|
+
plt.close()
|
|
723
|
+
|
|
724
|
+
if added_material:
|
|
725
|
+
# Local modules
|
|
726
|
+
from CHAP.utils.general import (
|
|
727
|
+
input_int,
|
|
728
|
+
input_num_list,
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
error = True
|
|
732
|
+
while error:
|
|
733
|
+
try:
|
|
734
|
+
print('\nEnter the name of the material to be added:')
|
|
735
|
+
name = input()
|
|
736
|
+
sgnum = input_int(
|
|
737
|
+
'Enter the space group for this material',
|
|
738
|
+
raise_error=True, log=False)
|
|
739
|
+
lat_params = input_num_list(
|
|
740
|
+
'Enter the lattice properties for this material',
|
|
741
|
+
raise_error=True, log=False)
|
|
742
|
+
print()
|
|
743
|
+
new_material = MaterialConfig(
|
|
744
|
+
material_name=name, sgnum=sgnum,
|
|
745
|
+
lattice_parameters=lat_params)
|
|
746
|
+
error = False
|
|
747
|
+
except (
|
|
748
|
+
ValueError, TypeError, SyntaxError, MemoryError,
|
|
749
|
+
RecursionError, IndexError) as e:
|
|
750
|
+
print(f'{e}: try again')
|
|
751
|
+
except:
|
|
752
|
+
raise
|
|
753
|
+
materials.append(new_material)
|
|
754
|
+
return select_material_params(
|
|
755
|
+
x, y, tth, preselected_materials= materials, label=label,
|
|
756
|
+
interactive=interactive, filename=filename)
|
|
757
|
+
if removed_material:
|
|
758
|
+
return select_material_params(
|
|
759
|
+
x, y, tth,
|
|
760
|
+
preselected_materials=[
|
|
761
|
+
m for m in materials if m.name not in removed_material],
|
|
762
|
+
label=label, interactive=interactive, filename=filename)
|
|
763
|
+
if not materials:
|
|
764
|
+
return select_material_params(
|
|
765
|
+
x, y, tth, label=label, interactive=interactive, filename=filename)
|
|
766
|
+
return [
|
|
491
767
|
MaterialConfig(
|
|
492
768
|
material_name=m.name, sgnum=m.sgnum,
|
|
493
769
|
lattice_parameters=[
|
|
494
770
|
m.latticeParameters[i].value for i in range(6)])
|
|
495
|
-
for m in
|
|
771
|
+
for m in materials]
|
|
496
772
|
|
|
497
|
-
return fig, new_materials
|
|
498
773
|
|
|
499
774
|
def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
500
|
-
preselected_hkl_indices=[],
|
|
501
|
-
flux_energy_range=None, calibration_bin_ranges=None,
|
|
502
|
-
label='Reference Data', interactive=False):
|
|
775
|
+
preselected_hkl_indices=[], num_hkl_min=1, detector_name=None,
|
|
776
|
+
ref_map=None, flux_energy_range=None, calibration_bin_ranges=None,
|
|
777
|
+
label='Reference Data', interactive=False, filename=None):
|
|
503
778
|
"""Return a matplotlib figure to indicate data ranges and HKLs to
|
|
504
|
-
include for fitting in EDD
|
|
779
|
+
include for fitting in EDD energy/tth calibration and/or strain
|
|
505
780
|
analysis.
|
|
506
781
|
|
|
507
782
|
:param x: MCA channel energies.
|
|
@@ -523,6 +798,9 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
523
798
|
:param preselected_hkl_indices: Preselected unique HKL indices to
|
|
524
799
|
fit peaks for in the calibration routine, defaults to `[]`.
|
|
525
800
|
:type preselected_hkl_indices: list[int], optional
|
|
801
|
+
:param num_hkl_min: Minimum number of HKLs to select,
|
|
802
|
+
defaults to `1`.
|
|
803
|
+
:type num_hkl_min: int, optional
|
|
526
804
|
:param detector_name: Name of the MCA detector element.
|
|
527
805
|
:type detector_name: str, optional
|
|
528
806
|
:param ref_map: Reference map of MCA intensities to show underneath
|
|
@@ -534,28 +812,32 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
534
812
|
:param calibration_bin_ranges: MCA channel index ranges included
|
|
535
813
|
in the detector calibration.
|
|
536
814
|
:type calibration_bin_ranges: list[[int, int]], optional
|
|
537
|
-
:param interactive: Allows for user interactions, defaults to
|
|
538
|
-
`False`.
|
|
539
|
-
:type interactive: bool, optional
|
|
540
815
|
:param label: Legend label for the 1D plot of reference MCA data
|
|
541
816
|
from the parameters `x`, `y`, defaults to `"Reference Data"`
|
|
542
817
|
:type label: str, optional
|
|
543
|
-
:
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
:
|
|
818
|
+
:param interactive: Show the plot and allow user interactions with
|
|
819
|
+
the matplotlib figure, defaults to `True`.
|
|
820
|
+
:type interactive: bool, optional
|
|
821
|
+
:param filename: Save a .png of the plot to filename, defaults to
|
|
822
|
+
`None`, in which case the plot is not saved.
|
|
823
|
+
:type filename: str, optional
|
|
824
|
+
:return: The list of selected data index ranges to include, and the
|
|
825
|
+
list of HKL indices to include
|
|
826
|
+
:rtype: list[list[int]], list[int]
|
|
547
827
|
"""
|
|
548
828
|
# Third party modules
|
|
549
|
-
|
|
550
|
-
|
|
829
|
+
if interactive or filename is not None:
|
|
830
|
+
import matplotlib.lines as mlines
|
|
831
|
+
from matplotlib.patches import Patch
|
|
832
|
+
from matplotlib.widgets import Button
|
|
551
833
|
import matplotlib.pyplot as plt
|
|
552
|
-
from matplotlib.widgets import
|
|
834
|
+
from matplotlib.widgets import SpanSelector
|
|
553
835
|
|
|
554
836
|
# Local modules
|
|
555
837
|
from CHAP.utils.general import (
|
|
556
838
|
get_consecutive_int_range,
|
|
557
839
|
index_nearest_down,
|
|
558
|
-
|
|
840
|
+
index_nearest_up,
|
|
559
841
|
)
|
|
560
842
|
|
|
561
843
|
def change_fig_title(title):
|
|
@@ -570,10 +852,20 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
570
852
|
error_texts.pop()
|
|
571
853
|
error_texts.append(plt.figtext(*error_pos, error, **error_props))
|
|
572
854
|
|
|
855
|
+
def get_mask():
|
|
856
|
+
"""Return a boolean array that acts as the mask corresponding
|
|
857
|
+
to the currently-selected index ranges"""
|
|
858
|
+
mask = np.full(x.shape[0], False)
|
|
859
|
+
for span in spans:
|
|
860
|
+
_min, _max = span.extents
|
|
861
|
+
mask = np.logical_or(
|
|
862
|
+
mask, np.logical_and(x >= _min, x <= _max))
|
|
863
|
+
return mask
|
|
864
|
+
|
|
573
865
|
def hkl_locations_in_any_span(hkl_index):
|
|
574
866
|
"""Return the index of the span where the location of a specific
|
|
575
867
|
HKL resides. Return(-1 if outside any span."""
|
|
576
|
-
if hkl_index < 0 or hkl_index>= len(hkl_locations):
|
|
868
|
+
if hkl_index < 0 or hkl_index >= len(hkl_locations):
|
|
577
869
|
return -1
|
|
578
870
|
for i, span in enumerate(spans):
|
|
579
871
|
if (span.extents[0] <= hkl_locations[hkl_index] and
|
|
@@ -581,15 +873,21 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
581
873
|
return i
|
|
582
874
|
return -1
|
|
583
875
|
|
|
876
|
+
def position_cax():
|
|
877
|
+
"""Reposition the colorbar axes according to the axes of the
|
|
878
|
+
reference map"""
|
|
879
|
+
((left, bottom), (right, top)) = ax_map.get_position().get_points()
|
|
880
|
+
cax.set_position([right + 0.01, bottom, 0.01, top - bottom])
|
|
881
|
+
|
|
584
882
|
def on_span_select(xmin, xmax):
|
|
585
883
|
"""Callback function for the SpanSelector widget."""
|
|
586
884
|
removed_hkls = False
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
if
|
|
885
|
+
for hkl_index in deepcopy(selected_hkl_indices):
|
|
886
|
+
if hkl_locations_in_any_span(hkl_index) < 0:
|
|
887
|
+
if interactive or filename is not None:
|
|
590
888
|
hkl_vlines[hkl_index].set(**excluded_hkl_props)
|
|
591
|
-
|
|
592
|
-
|
|
889
|
+
selected_hkl_indices.remove(hkl_index)
|
|
890
|
+
removed_hkls = True
|
|
593
891
|
combined_spans = False
|
|
594
892
|
combined_spans_test = True
|
|
595
893
|
while combined_spans_test:
|
|
@@ -617,48 +915,39 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
617
915
|
for hkl_index in range(len(hkl_locations)):
|
|
618
916
|
if (hkl_index not in selected_hkl_indices
|
|
619
917
|
and hkl_locations_in_any_span(hkl_index) >= 0):
|
|
620
|
-
|
|
918
|
+
if interactive or filename is not None:
|
|
919
|
+
hkl_vlines[hkl_index].set(**included_hkl_props)
|
|
621
920
|
selected_hkl_indices.append(hkl_index)
|
|
622
921
|
added_hkls = True
|
|
623
|
-
if
|
|
624
|
-
if
|
|
922
|
+
if interactive or filename is not None:
|
|
923
|
+
if combined_spans:
|
|
924
|
+
if added_hkls or removed_hkls:
|
|
925
|
+
change_error_text(
|
|
926
|
+
'Combined overlapping spans and selected only HKL(s) '
|
|
927
|
+
'inside the selected energy mask')
|
|
928
|
+
else:
|
|
929
|
+
change_error_text('Combined overlapping spans in the '
|
|
930
|
+
'selected energy mask')
|
|
931
|
+
elif added_hkls and removed_hkls:
|
|
625
932
|
change_error_text(
|
|
626
|
-
'
|
|
627
|
-
'
|
|
628
|
-
|
|
933
|
+
'Adjusted the selected HKL(s) to match the selected '
|
|
934
|
+
'energy mask')
|
|
935
|
+
elif added_hkls:
|
|
629
936
|
change_error_text(
|
|
630
|
-
'
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
'energy mask')
|
|
635
|
-
elif added_hkls:
|
|
636
|
-
change_error_text(
|
|
637
|
-
'Added HKL(s) to match the selected energy mask')
|
|
638
|
-
elif removed_hkls:
|
|
639
|
-
change_error_text(
|
|
640
|
-
'Removed HKL(s) outside the selected energy mask')
|
|
937
|
+
'Added HKL(s) to match the selected energy mask')
|
|
938
|
+
elif removed_hkls:
|
|
939
|
+
change_error_text(
|
|
940
|
+
'Removed HKL(s) outside the selected energy mask')
|
|
641
941
|
# If using ref_map, update the colorbar range to min / max of
|
|
642
942
|
# the selected data only
|
|
643
943
|
if ref_map is not None:
|
|
644
944
|
selected_data = ref_map[:,get_mask()]
|
|
645
|
-
_min, _max = np.argmin(selected_data), np.argmax(selected_data)
|
|
646
945
|
ref_map_mappable = ax_map.pcolormesh(
|
|
647
|
-
x, np.arange(ref_map.shape[0]), ref_map,
|
|
946
|
+
x, np.arange(ref_map.shape[0]), ref_map,
|
|
947
|
+
vmin=selected_data.min(), vmax=selected_data.max())
|
|
648
948
|
fig.colorbar(ref_map_mappable, cax=cax)
|
|
649
949
|
plt.draw()
|
|
650
950
|
|
|
651
|
-
def get_mask():
|
|
652
|
-
"""Return a boolean array that acts as the mask corresponding
|
|
653
|
-
to the currently-selected index ranges"""
|
|
654
|
-
mask = np.full(x.shape[0], False)
|
|
655
|
-
bin_indices = np.arange(x.shape[0])
|
|
656
|
-
for span in spans:
|
|
657
|
-
_min, _max = span.extents
|
|
658
|
-
mask = np.logical_or(
|
|
659
|
-
mask, np.logical_and(bin_indices >= _min, bin_indices <= _max))
|
|
660
|
-
return mask
|
|
661
|
-
|
|
662
951
|
def add_span(event, xrange_init=None):
|
|
663
952
|
"""Callback function for the "Add span" button."""
|
|
664
953
|
spans.append(
|
|
@@ -688,17 +977,19 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
688
977
|
hkl_vline.set(**excluded_hkl_props)
|
|
689
978
|
selected_hkl_indices.remove(hkl_index)
|
|
690
979
|
span = spans[hkl_locations_in_any_span(hkl_index)]
|
|
691
|
-
span_next_hkl_index = hkl_locations_in_any_span(hkl_index+1)
|
|
692
980
|
span_prev_hkl_index = hkl_locations_in_any_span(hkl_index-1)
|
|
693
|
-
|
|
981
|
+
span_curr_hkl_index = hkl_locations_in_any_span(hkl_index)
|
|
982
|
+
span_next_hkl_index = hkl_locations_in_any_span(hkl_index+1)
|
|
983
|
+
if (span_curr_hkl_index != span_prev_hkl_index
|
|
984
|
+
and span_curr_hkl_index != span_next_hkl_index):
|
|
694
985
|
span.set_visible(False)
|
|
695
986
|
spans.remove(span)
|
|
696
|
-
elif
|
|
987
|
+
elif span_curr_hkl_index != span_next_hkl_index:
|
|
697
988
|
span.extents = (
|
|
698
989
|
span.extents[0],
|
|
699
990
|
0.5*(hkl_locations[hkl_index-1]
|
|
700
991
|
+ hkl_locations[hkl_index]))
|
|
701
|
-
elif
|
|
992
|
+
elif span_curr_hkl_index != span_prev_hkl_index:
|
|
702
993
|
span.extents = (
|
|
703
994
|
0.5*(hkl_locations[hkl_index]
|
|
704
995
|
+ hkl_locations[hkl_index+1]),
|
|
@@ -717,19 +1008,54 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
717
1008
|
f'Adjusted the selected energy mask to reflect the '
|
|
718
1009
|
'removed HKL')
|
|
719
1010
|
else:
|
|
1011
|
+
hkl_vline.set(**included_hkl_props)
|
|
1012
|
+
prev_hkl = hkl_index-1 in selected_hkl_indices
|
|
1013
|
+
next_hkl = hkl_index+1 in selected_hkl_indices
|
|
1014
|
+
if prev_hkl and next_hkl:
|
|
1015
|
+
span_prev = spans[hkl_locations_in_any_span(hkl_index-1)]
|
|
1016
|
+
span_next = spans[hkl_locations_in_any_span(hkl_index+1)]
|
|
1017
|
+
span_prev.extents = (
|
|
1018
|
+
span_prev.extents[0], span_next.extents[1])
|
|
1019
|
+
span_next.set_visible(False)
|
|
1020
|
+
elif prev_hkl:
|
|
1021
|
+
span_prev = spans[hkl_locations_in_any_span(hkl_index-1)]
|
|
1022
|
+
if hkl_index < len(hkl_locations)-1:
|
|
1023
|
+
max_ = 0.5*(
|
|
1024
|
+
hkl_locations[hkl_index]
|
|
1025
|
+
+ hkl_locations[hkl_index+1])
|
|
1026
|
+
else:
|
|
1027
|
+
max_ = 0.5*(hkl_locations[hkl_index] + max_x)
|
|
1028
|
+
span_prev.extents = (span_prev.extents[0], max_)
|
|
1029
|
+
elif next_hkl:
|
|
1030
|
+
span_next = spans[hkl_locations_in_any_span(hkl_index+1)]
|
|
1031
|
+
if hkl_index > 0:
|
|
1032
|
+
min_ = 0.5*(
|
|
1033
|
+
hkl_locations[hkl_index-1]
|
|
1034
|
+
+ hkl_locations[hkl_index])
|
|
1035
|
+
else:
|
|
1036
|
+
min_ = 0.5*(min_x + hkl_locations[hkl_index])
|
|
1037
|
+
span_next.extents = (min_, span_next.extents[1])
|
|
1038
|
+
else:
|
|
1039
|
+
if hkl_index > 0:
|
|
1040
|
+
min_ = 0.5*(
|
|
1041
|
+
hkl_locations[hkl_index-1]
|
|
1042
|
+
+ hkl_locations[hkl_index])
|
|
1043
|
+
else:
|
|
1044
|
+
min_ = 0.5*(min_x + hkl_locations[hkl_index])
|
|
1045
|
+
if hkl_index < len(hkl_locations)-1:
|
|
1046
|
+
max_ = 0.5*(
|
|
1047
|
+
hkl_locations[hkl_index]
|
|
1048
|
+
+ hkl_locations[hkl_index+1])
|
|
1049
|
+
else:
|
|
1050
|
+
max_ = 0.5*(hkl_locations[hkl_index] + max_x)
|
|
1051
|
+
add_span(None, xrange_init=(min_, max_))
|
|
720
1052
|
change_error_text(
|
|
721
|
-
f'
|
|
722
|
-
'
|
|
1053
|
+
f'Adjusted the selected energy mask to reflect the '
|
|
1054
|
+
'added HKL')
|
|
723
1055
|
plt.draw()
|
|
724
1056
|
|
|
725
|
-
def position_cax():
|
|
726
|
-
"""Reposition the colorbar axes according to the axes of the
|
|
727
|
-
reference map"""
|
|
728
|
-
((left, bottom), (right, top)) = ax_map.get_position().get_points()
|
|
729
|
-
cax.set_position([right + 0.01, bottom, 0.01, top - bottom])
|
|
730
|
-
|
|
731
1057
|
def reset(event):
|
|
732
|
-
"""Callback function for the "
|
|
1058
|
+
"""Callback function for the "Reset" button."""
|
|
733
1059
|
for hkl_index in deepcopy(selected_hkl_indices):
|
|
734
1060
|
hkl_vlines[hkl_index].set(**excluded_hkl_props)
|
|
735
1061
|
selected_hkl_indices.remove(hkl_index)
|
|
@@ -740,8 +1066,9 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
740
1066
|
|
|
741
1067
|
def confirm(event):
|
|
742
1068
|
"""Callback function for the "Confirm" button."""
|
|
743
|
-
if not len(spans) or len(selected_hkl_indices) <
|
|
744
|
-
change_error_text(
|
|
1069
|
+
if not len(spans) or len(selected_hkl_indices) < num_hkl_min:
|
|
1070
|
+
change_error_text(
|
|
1071
|
+
f'Select at least one span and {num_hkl_min} HKLs')
|
|
745
1072
|
plt.draw()
|
|
746
1073
|
else:
|
|
747
1074
|
if error_texts:
|
|
@@ -760,78 +1087,12 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
760
1087
|
fig_title = []
|
|
761
1088
|
error_texts = []
|
|
762
1089
|
|
|
1090
|
+
if ref_map is not None and ref_map.ndim == 1:
|
|
1091
|
+
ref_map = None
|
|
1092
|
+
|
|
1093
|
+
# Make preselected_bin_ranges consistent with selected_hkl_indices
|
|
763
1094
|
hkl_locations = [loc for loc in get_peak_locations(ds, tth)
|
|
764
1095
|
if x[0] <= loc <= x[-1]]
|
|
765
|
-
hkl_labels = [str(hkl)[1:-1] for hkl, loc in zip(hkls, hkl_locations)]
|
|
766
|
-
|
|
767
|
-
title_pos = (0.5, 0.95)
|
|
768
|
-
title_props = {'fontsize': 'xx-large', 'ha': 'center', 'va': 'bottom'}
|
|
769
|
-
error_pos = (0.5, 0.90)
|
|
770
|
-
error_props = {'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
771
|
-
excluded_hkl_props = {
|
|
772
|
-
'color': 'black', 'linestyle': '--','linewidth': 1,
|
|
773
|
-
'marker': 10, 'markersize': 5, 'fillstyle': 'none'}
|
|
774
|
-
included_hkl_props = {
|
|
775
|
-
'color': 'green', 'linestyle': '-', 'linewidth': 2,
|
|
776
|
-
'marker': 10, 'markersize': 10, 'fillstyle': 'full'}
|
|
777
|
-
excluded_data_props = {
|
|
778
|
-
'facecolor': 'white', 'edgecolor': 'gray', 'linestyle': ':'}
|
|
779
|
-
included_data_props = {
|
|
780
|
-
'alpha': 0.5, 'facecolor': 'tab:blue', 'edgecolor': 'blue'}
|
|
781
|
-
|
|
782
|
-
if flux_energy_range is None:
|
|
783
|
-
min_x = x.min()
|
|
784
|
-
max_x = x.max()
|
|
785
|
-
else:
|
|
786
|
-
min_x = x[index_nearest_upp(x, max(x.min(), flux_energy_range[0]))]
|
|
787
|
-
max_x = x[index_nearest_down(x, min(x.max(), flux_energy_range[1]))]
|
|
788
|
-
|
|
789
|
-
if ref_map is None:
|
|
790
|
-
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
791
|
-
ax.set(xlabel='Energy (keV)', ylabel='Intensity (counts)')
|
|
792
|
-
else:
|
|
793
|
-
# Ensure ref_map is 2D
|
|
794
|
-
if ref_map.ndim > 2:
|
|
795
|
-
ref_map = np.reshape(
|
|
796
|
-
ref_map, (np.prod(ref_map.shape[:-1]), ref_map.shape[-1]))
|
|
797
|
-
# If needed, abbreviate ref_map to <= 50 spectra to keep
|
|
798
|
-
# response time of mouse interactions quick.
|
|
799
|
-
max_ref_spectra = 50
|
|
800
|
-
if ref_map.shape[0] > max_ref_spectra:
|
|
801
|
-
choose_i = np.sort(
|
|
802
|
-
np.random.choice(
|
|
803
|
-
ref_map.shape[0], max_ref_spectra, replace=False))
|
|
804
|
-
ref_map = ref_map[choose_i]
|
|
805
|
-
fig, (ax, ax_map) = plt.subplots(
|
|
806
|
-
2, sharex=True, figsize=(11, 8.5), height_ratios=[2, 1])
|
|
807
|
-
ax.set(ylabel='Intensity (counts)')
|
|
808
|
-
ref_map_mappable = ax_map.pcolormesh(
|
|
809
|
-
x, np.arange(ref_map.shape[0]), ref_map)
|
|
810
|
-
ax_map.set_yticks([])
|
|
811
|
-
ax_map.set_xlabel('Energy (keV)')
|
|
812
|
-
ax_map.set_xlim(x[0], x[-1])
|
|
813
|
-
((left, bottom), (right, top)) = ax_map.get_position().get_points()
|
|
814
|
-
cax = plt.axes([right + 0.01, bottom, 0.01, top - bottom])
|
|
815
|
-
fig.colorbar(ref_map_mappable, cax=cax)
|
|
816
|
-
handles = ax.plot(x, y, color='k', label=label)
|
|
817
|
-
if calibration_bin_ranges is not None:
|
|
818
|
-
ylow = ax.get_ylim()[0]
|
|
819
|
-
for low, upp in calibration_bin_ranges:
|
|
820
|
-
ax.plot([x[low], x[upp]], [ylow, ylow], color='r', linewidth=2)
|
|
821
|
-
handles.append(mlines.Line2D(
|
|
822
|
-
[], [], label='Energies included in calibration', color='r',
|
|
823
|
-
linewidth=2))
|
|
824
|
-
handles.append(mlines.Line2D(
|
|
825
|
-
[], [], label='Excluded / unselected HKL', **excluded_hkl_props))
|
|
826
|
-
handles.append(mlines.Line2D(
|
|
827
|
-
[], [], label='Included / selected HKL', **included_hkl_props))
|
|
828
|
-
handles.append(Patch(
|
|
829
|
-
label='Excluded / unselected data', **excluded_data_props))
|
|
830
|
-
handles.append(Patch(
|
|
831
|
-
label='Included / selected data', **included_data_props))
|
|
832
|
-
ax.legend(handles=handles)
|
|
833
|
-
ax.set_xlim(x[0], x[-1])
|
|
834
|
-
|
|
835
1096
|
if selected_hkl_indices and not preselected_bin_ranges:
|
|
836
1097
|
index_ranges = get_consecutive_int_range(selected_hkl_indices)
|
|
837
1098
|
for index_range in index_ranges:
|
|
@@ -846,30 +1107,109 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
846
1107
|
else:
|
|
847
1108
|
max_ = 0.5*(hkl_locations[j] + max_x)
|
|
848
1109
|
preselected_bin_ranges.append(
|
|
849
|
-
[
|
|
1110
|
+
[index_nearest_up(x, min_), index_nearest_down(x, max_)])
|
|
850
1111
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1112
|
+
if flux_energy_range is None:
|
|
1113
|
+
min_x = x.min()
|
|
1114
|
+
max_x = x.max()
|
|
1115
|
+
else:
|
|
1116
|
+
min_x = x[index_nearest_up(x, max(x.min(), flux_energy_range[0]))]
|
|
1117
|
+
max_x = x[index_nearest_down(x, min(x.max(), flux_energy_range[1]))]
|
|
1118
|
+
|
|
1119
|
+
# Setup the Matplotlib figure
|
|
1120
|
+
if not interactive and filename is None:
|
|
1121
|
+
|
|
1122
|
+
# It is too convenient to not use the Matplotlib SpanSelector
|
|
1123
|
+
# so define a (fig, ax) tuple, despite not creating a figure
|
|
1124
|
+
included_data_props = {}
|
|
1125
|
+
fig, ax = plt.subplots()
|
|
1126
|
+
|
|
1127
|
+
else:
|
|
1128
|
+
|
|
1129
|
+
title_pos = (0.5, 0.95)
|
|
1130
|
+
title_props = {'fontsize': 'xx-large', 'ha': 'center', 'va': 'bottom'}
|
|
1131
|
+
error_pos = (0.5, 0.90)
|
|
1132
|
+
error_props = {'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
1133
|
+
excluded_hkl_props = {
|
|
1134
|
+
'color': 'black', 'linestyle': '--','linewidth': 1,
|
|
1135
|
+
'marker': 10, 'markersize': 5, 'fillstyle': 'none'}
|
|
1136
|
+
included_hkl_props = {
|
|
1137
|
+
'color': 'green', 'linestyle': '-', 'linewidth': 2,
|
|
1138
|
+
'marker': 10, 'markersize': 10, 'fillstyle': 'full'}
|
|
1139
|
+
included_data_props = {
|
|
1140
|
+
'alpha': 0.5, 'facecolor': 'tab:blue', 'edgecolor': 'blue'}
|
|
1141
|
+
excluded_data_props = {
|
|
1142
|
+
'facecolor': 'white', 'edgecolor': 'gray', 'linestyle': ':'}
|
|
1143
|
+
|
|
1144
|
+
if ref_map is None:
|
|
1145
|
+
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
1146
|
+
ax.set(xlabel='Energy (keV)', ylabel='Intensity (counts)')
|
|
855
1147
|
else:
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1148
|
+
if ref_map.ndim > 2:
|
|
1149
|
+
ref_map = np.reshape(
|
|
1150
|
+
ref_map, (np.prod(ref_map.shape[:-1]), ref_map.shape[-1]))
|
|
1151
|
+
# If needed, abbreviate ref_map to <= 50 spectra to keep
|
|
1152
|
+
# response time of mouse interactions quick.
|
|
1153
|
+
max_ref_spectra = 50
|
|
1154
|
+
if ref_map.shape[0] > max_ref_spectra:
|
|
1155
|
+
choose_i = np.sort(
|
|
1156
|
+
np.random.choice(
|
|
1157
|
+
ref_map.shape[0], max_ref_spectra, replace=False))
|
|
1158
|
+
ref_map = ref_map[choose_i]
|
|
1159
|
+
fig, (ax, ax_map) = plt.subplots(
|
|
1160
|
+
2, sharex=True, figsize=(11, 8.5), height_ratios=[2, 1])
|
|
1161
|
+
ax.set(ylabel='Intensity (counts)')
|
|
1162
|
+
ref_map_mappable = ax_map.pcolormesh(
|
|
1163
|
+
x, np.arange(ref_map.shape[0]), ref_map)
|
|
1164
|
+
ax_map.set_yticks([])
|
|
1165
|
+
ax_map.set_xlabel('Energy (keV)')
|
|
1166
|
+
ax_map.set_xlim(x[0], x[-1])
|
|
1167
|
+
((left, bottom), (right, top)) = ax_map.get_position().get_points()
|
|
1168
|
+
cax = plt.axes([right + 0.01, bottom, 0.01, top - bottom])
|
|
1169
|
+
fig.colorbar(ref_map_mappable, cax=cax)
|
|
1170
|
+
handles = ax.plot(x, y, color='k', label=label)
|
|
1171
|
+
if calibration_bin_ranges is not None:
|
|
1172
|
+
ylow = ax.get_ylim()[0]
|
|
1173
|
+
for low, upp in calibration_bin_ranges:
|
|
1174
|
+
ax.plot([x[low], x[upp]], [ylow, ylow], color='r', linewidth=2)
|
|
1175
|
+
handles.append(mlines.Line2D(
|
|
1176
|
+
[], [], label='Energies included in calibration', color='r',
|
|
1177
|
+
linewidth=2))
|
|
1178
|
+
handles.append(mlines.Line2D(
|
|
1179
|
+
[], [], label='Excluded / unselected HKL', **excluded_hkl_props))
|
|
1180
|
+
handles.append(mlines.Line2D(
|
|
1181
|
+
[], [], label='Included / selected HKL', **included_hkl_props))
|
|
1182
|
+
handles.append(Patch(
|
|
1183
|
+
label='Excluded / unselected data', **excluded_data_props))
|
|
1184
|
+
handles.append(Patch(
|
|
1185
|
+
label='Included / selected data', **included_data_props))
|
|
1186
|
+
ax.legend(handles=handles)
|
|
1187
|
+
ax.set_xlim(x[0], x[-1])
|
|
860
1188
|
|
|
861
|
-
|
|
1189
|
+
# Add HKL lines
|
|
1190
|
+
hkl_labels = [str(hkl)[1:-1] for hkl, loc in zip(hkls, hkl_locations)]
|
|
1191
|
+
for i, (loc, lbl) in enumerate(zip(hkl_locations, hkl_labels)):
|
|
1192
|
+
nearest_index = np.searchsorted(x, loc)
|
|
1193
|
+
if i in selected_hkl_indices:
|
|
1194
|
+
hkl_vline = ax.axvline(loc, **included_hkl_props)
|
|
1195
|
+
else:
|
|
1196
|
+
hkl_vline = ax.axvline(loc, **excluded_hkl_props)
|
|
1197
|
+
ax.text(loc, 1, lbl, ha='right', va='top', rotation=90,
|
|
1198
|
+
transform=ax.get_xaxis_transform())
|
|
1199
|
+
hkl_vlines.append(hkl_vline)
|
|
1200
|
+
|
|
1201
|
+
# Add initial spans
|
|
862
1202
|
for bin_range in preselected_bin_ranges:
|
|
863
1203
|
add_span(None, xrange_init=x[bin_range])
|
|
864
|
-
init_flag = [False]
|
|
865
1204
|
|
|
866
1205
|
if not interactive:
|
|
867
1206
|
|
|
868
|
-
if
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1207
|
+
if filename is not None:
|
|
1208
|
+
if detector_name is None:
|
|
1209
|
+
change_fig_title('Selected data and HKLs used in fitting')
|
|
1210
|
+
else:
|
|
1211
|
+
change_fig_title(
|
|
1212
|
+
f'Selected data and HKLs used in fitting {detector_name}')
|
|
873
1213
|
|
|
874
1214
|
else:
|
|
875
1215
|
|
|
@@ -913,6 +1253,14 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
913
1253
|
reset_btn.ax.remove()
|
|
914
1254
|
plt.subplots_adjust(bottom=0.0)
|
|
915
1255
|
|
|
1256
|
+
if filename is not None:
|
|
1257
|
+
fig_title[0].set_in_layout(True)
|
|
1258
|
+
fig.tight_layout(rect=(0, 0, 0.9, 0.9))
|
|
1259
|
+
if ref_map is not None:
|
|
1260
|
+
position_cax()
|
|
1261
|
+
fig.savefig(filename)
|
|
1262
|
+
plt.close()
|
|
1263
|
+
|
|
916
1264
|
selected_bin_ranges = [np.searchsorted(x, span.extents).tolist()
|
|
917
1265
|
for span in spans]
|
|
918
1266
|
if not selected_bin_ranges:
|
|
@@ -922,15 +1270,79 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
922
1270
|
else:
|
|
923
1271
|
selected_hkl_indices = None
|
|
924
1272
|
|
|
925
|
-
|
|
926
|
-
fig.tight_layout(rect=(0, 0, 0.9, 0.9))
|
|
927
|
-
if ref_map is not None:
|
|
928
|
-
position_cax()
|
|
929
|
-
|
|
930
|
-
return fig, selected_bin_ranges, selected_hkl_indices
|
|
1273
|
+
return selected_bin_ranges, selected_hkl_indices
|
|
931
1274
|
|
|
932
1275
|
|
|
933
|
-
def
|
|
1276
|
+
def get_rolling_sum_spectra(
|
|
1277
|
+
y, bin_axis, start=0, end=None, width=None, stride=None, num=None,
|
|
1278
|
+
mode='valid'):
|
|
1279
|
+
"""
|
|
1280
|
+
Return the rolling sum of the spectra over a specified axis.
|
|
1281
|
+
"""
|
|
1282
|
+
y = np.asarray(y)
|
|
1283
|
+
if not 0 <= bin_axis < y.ndim-1:
|
|
1284
|
+
raise ValueError(f'Invalid "bin_axis" parameter ({bin_axis})')
|
|
1285
|
+
size = y.shape[bin_axis]
|
|
1286
|
+
if not 0 <= start < size:
|
|
1287
|
+
raise ValueError(f'Invalid "start" parameter ({start})')
|
|
1288
|
+
if end is None:
|
|
1289
|
+
end = size
|
|
1290
|
+
elif not start < end <= size:
|
|
1291
|
+
raise ValueError('Invalid "start" and "end" combination '
|
|
1292
|
+
f'({start} and {end})')
|
|
1293
|
+
|
|
1294
|
+
size = end-start
|
|
1295
|
+
if stride is None:
|
|
1296
|
+
if width is None:
|
|
1297
|
+
width = max(1, int(size/num))
|
|
1298
|
+
stride = width
|
|
1299
|
+
else:
|
|
1300
|
+
width = max(1, min(width, size))
|
|
1301
|
+
if num is None:
|
|
1302
|
+
stride = width
|
|
1303
|
+
else:
|
|
1304
|
+
stride = max(1, int((size-width) / (num-1)))
|
|
1305
|
+
else:
|
|
1306
|
+
stride = max(1, min(stride, size-stride))
|
|
1307
|
+
if width is None:
|
|
1308
|
+
width = stride
|
|
1309
|
+
if mode == 'valid':
|
|
1310
|
+
num = 1 + max(0, int((size-width) / stride))
|
|
1311
|
+
else:
|
|
1312
|
+
num = int(size/stride)
|
|
1313
|
+
if num*stride < size:
|
|
1314
|
+
num += 1
|
|
1315
|
+
bin_ranges = [(start+n*stride, min(start+size, start+n*stride+width))
|
|
1316
|
+
for n in range(num)]
|
|
1317
|
+
|
|
1318
|
+
y_shape = y.shape
|
|
1319
|
+
y_ndim = y.ndim
|
|
1320
|
+
swap_axis = False
|
|
1321
|
+
if y_ndim > 2 and bin_axis != y_ndim-2:
|
|
1322
|
+
y = np.swapaxes(y, bin_axis, y_ndim-2)
|
|
1323
|
+
swap_axis = True
|
|
1324
|
+
if y_ndim > 3:
|
|
1325
|
+
map_shape = y.shape[0:y_ndim-2]
|
|
1326
|
+
y = y.reshape((np.prod(map_shape), *y.shape[y_ndim-2:]))
|
|
1327
|
+
if y_ndim == 2:
|
|
1328
|
+
y = np.expand_dims(y, 0)
|
|
1329
|
+
|
|
1330
|
+
ry = np.zeros((y.shape[0], num, y.shape[-1]), dtype=y.dtype)
|
|
1331
|
+
for dim in range(y.shape[0]):
|
|
1332
|
+
for n in range(num):
|
|
1333
|
+
ry[dim, n] = np.sum(y[dim,bin_ranges[n][0]:bin_ranges[n][1]], 0)
|
|
1334
|
+
|
|
1335
|
+
if y_ndim > 3:
|
|
1336
|
+
ry = np.reshape(ry, (*map_shape, num, y_shape[-1]))
|
|
1337
|
+
if y_ndim == 2:
|
|
1338
|
+
ry = np.squeeze(ry)
|
|
1339
|
+
if swap_axis:
|
|
1340
|
+
ry = np.swapaxes(ry, bin_axis, y_ndim-2)
|
|
1341
|
+
|
|
1342
|
+
return ry
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
def get_spectra_fits(spectra, energies, peak_locations, detector):
|
|
934
1346
|
"""Return twenty arrays of fit results for the map of spectra
|
|
935
1347
|
provided: uniform centers, uniform center errors, uniform
|
|
936
1348
|
amplitudes, uniform amplitude errors, uniform sigmas, uniform
|
|
@@ -948,8 +1360,8 @@ def get_spectra_fits(spectra, energies, peak_locations, fit_params):
|
|
|
948
1360
|
:param peak_locations: Initial guesses for peak ceneters to use
|
|
949
1361
|
for the uniform fit.
|
|
950
1362
|
:type peak_locations: list[float]
|
|
951
|
-
|
|
952
|
-
|
|
1363
|
+
:param detector: A single MCA detector element configuration.
|
|
1364
|
+
:type detector: CHAP.edd.models.MCAElementStrainAnalysisConfig
|
|
953
1365
|
:returns: Uniform and unconstrained centers, amplitdues, sigmas
|
|
954
1366
|
(and errors for all three), best fits, residuals between the
|
|
955
1367
|
best fits and the input spectra, reduced chi, and fit success
|
|
@@ -961,86 +1373,228 @@ def get_spectra_fits(spectra, energies, peak_locations, fit_params):
|
|
|
961
1373
|
numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
962
1374
|
numpy.ndarray]
|
|
963
1375
|
"""
|
|
964
|
-
|
|
1376
|
+
# Third party modules
|
|
1377
|
+
from nexusformat.nexus import NXdata, NXfield
|
|
965
1378
|
|
|
966
|
-
#
|
|
967
|
-
fit
|
|
1379
|
+
# Local modules
|
|
1380
|
+
from CHAP.utils.fit import FitProcessor
|
|
1381
|
+
|
|
1382
|
+
num_proc = detector.num_proc
|
|
1383
|
+
rel_height_cutoff = detector.rel_height_cutoff
|
|
968
1384
|
num_peak = len(peak_locations)
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1385
|
+
nxdata = NXdata(NXfield(spectra, 'y'), NXfield(energies, 'x'))
|
|
1386
|
+
|
|
1387
|
+
# Construct the fit model
|
|
1388
|
+
models = []
|
|
1389
|
+
if detector.background is not None:
|
|
1390
|
+
if isinstance(detector.background, str):
|
|
1391
|
+
models.append(
|
|
1392
|
+
{'model': detector.background, 'prefix': 'bkgd_'})
|
|
1393
|
+
else:
|
|
1394
|
+
for model in detector.background:
|
|
1395
|
+
models.append({'model': model, 'prefix': f'{model}_'})
|
|
1396
|
+
models.append(
|
|
1397
|
+
{'model': 'multipeak', 'centers': list(peak_locations),
|
|
1398
|
+
'fit_type': 'uniform', 'peak_models': detector.peak_models,
|
|
1399
|
+
'centers_range': detector.centers_range,
|
|
1400
|
+
'fwhm_min': detector.fwhm_min, 'fwhm_max': detector.fwhm_max})
|
|
1401
|
+
config = {
|
|
1402
|
+
# 'code': 'lmfit',
|
|
1403
|
+
'models': models,
|
|
1404
|
+
# 'plot': True,
|
|
1405
|
+
'num_proc': num_proc,
|
|
1406
|
+
'rel_height_cutoff': rel_height_cutoff,
|
|
1407
|
+
# 'method': 'trf',
|
|
1408
|
+
'method': 'leastsq',
|
|
1409
|
+
# 'method': 'least_squares',
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
# Perform uniform fit
|
|
1413
|
+
fit = FitProcessor()
|
|
1414
|
+
uniform_fit = fit.process(nxdata, config)
|
|
1415
|
+
uniform_success = uniform_fit.success
|
|
1416
|
+
if spectra.ndim == 1:
|
|
1417
|
+
if uniform_success:
|
|
1418
|
+
if num_peak == 1:
|
|
1419
|
+
uniform_fit_centers = [uniform_fit.best_values['center']]
|
|
1420
|
+
uniform_fit_centers_errors = [
|
|
1421
|
+
uniform_fit.best_errors['center']]
|
|
1422
|
+
uniform_fit_amplitudes = [
|
|
1423
|
+
uniform_fit.best_values['amplitude']]
|
|
1424
|
+
uniform_fit_amplitudes_errors = [
|
|
1425
|
+
uniform_fit.best_errors['amplitude']]
|
|
1426
|
+
uniform_fit_sigmas = [uniform_fit.best_values['sigma']]
|
|
1427
|
+
uniform_fit_centers_errors = [uniform_fit.best_errors['sigma']]
|
|
1428
|
+
else:
|
|
1429
|
+
uniform_fit_centers = [
|
|
1430
|
+
uniform_fit.best_values[
|
|
1431
|
+
f'peak{i+1}_center'] for i in range(num_peak)]
|
|
1432
|
+
uniform_fit_centers_errors = [
|
|
1433
|
+
uniform_fit.best_errors[
|
|
1434
|
+
f'peak{i+1}_center'] for i in range(num_peak)]
|
|
1435
|
+
uniform_fit_amplitudes = [
|
|
1436
|
+
uniform_fit.best_values[
|
|
1437
|
+
f'peak{i+1}_amplitude'] for i in range(num_peak)]
|
|
1438
|
+
uniform_fit_amplitudes_errors = [
|
|
1439
|
+
uniform_fit.best_errors[
|
|
1440
|
+
f'peak{i+1}_amplitude'] for i in range(num_peak)]
|
|
1441
|
+
uniform_fit_sigmas = [
|
|
1442
|
+
uniform_fit.best_values[
|
|
1443
|
+
f'peak{i+1}_sigma'] for i in range(num_peak)]
|
|
1444
|
+
uniform_fit_sigmas_errors = [
|
|
1445
|
+
uniform_fit.best_errors[
|
|
1446
|
+
f'peak{i+1}_sigma'] for i in range(num_peak)]
|
|
1447
|
+
else:
|
|
1448
|
+
uniform_fit_centers = list(peak_locations)
|
|
1449
|
+
uniform_fit_centers_errors = [0]
|
|
1450
|
+
uniform_fit_amplitudes = [0]
|
|
1451
|
+
uniform_fit_amplitudes_errors = [0]
|
|
1452
|
+
uniform_fit_sigmas = [0]
|
|
1453
|
+
uniform_fit_sigmas_errors = [0]
|
|
1454
|
+
else:
|
|
1455
|
+
if num_peak == 1:
|
|
1456
|
+
uniform_fit_centers = [
|
|
1457
|
+
uniform_fit.best_values[
|
|
1458
|
+
uniform_fit.best_parameters().index('center')]]
|
|
1459
|
+
uniform_fit_centers_errors = [
|
|
1460
|
+
uniform_fit.best_errors[
|
|
1461
|
+
uniform_fit.best_parameters().index('center')]]
|
|
1462
|
+
uniform_fit_amplitudes = [
|
|
1463
|
+
uniform_fit.best_values[
|
|
1464
|
+
uniform_fit.best_parameters().index('amplitude')]]
|
|
1465
|
+
uniform_fit_amplitudes_errors = [
|
|
1466
|
+
uniform_fit.best_errors[
|
|
1467
|
+
uniform_fit.best_parameters().index('amplitude')]]
|
|
1468
|
+
uniform_fit_sigmas = [
|
|
1469
|
+
uniform_fit.best_values[
|
|
1470
|
+
uniform_fit.best_parameters().index('sigma')]]
|
|
1471
|
+
uniform_fit_sigmas_errors = [
|
|
1472
|
+
uniform_fit.best_errors[
|
|
1473
|
+
uniform_fit.best_parameters().index('sigma')]]
|
|
1474
|
+
else:
|
|
1475
|
+
uniform_fit_centers = [
|
|
1476
|
+
uniform_fit.best_values[
|
|
1477
|
+
uniform_fit.best_parameters().index(f'peak{i+1}_center')]
|
|
1478
|
+
for i in range(num_peak)]
|
|
1479
|
+
uniform_fit_centers_errors = [
|
|
1480
|
+
uniform_fit.best_errors[
|
|
1481
|
+
uniform_fit.best_parameters().index(f'peak{i+1}_center')]
|
|
1482
|
+
for i in range(num_peak)]
|
|
1483
|
+
uniform_fit_amplitudes = [
|
|
1484
|
+
uniform_fit.best_values[
|
|
1485
|
+
uniform_fit.best_parameters().index(
|
|
1486
|
+
f'peak{i+1}_amplitude')]
|
|
1487
|
+
for i in range(num_peak)]
|
|
1488
|
+
uniform_fit_amplitudes_errors = [
|
|
1489
|
+
uniform_fit.best_errors[
|
|
1490
|
+
uniform_fit.best_parameters().index(
|
|
1491
|
+
f'peak{i+1}_amplitude')]
|
|
1492
|
+
for i in range(num_peak)]
|
|
1493
|
+
uniform_fit_sigmas = [
|
|
1494
|
+
uniform_fit.best_values[
|
|
1495
|
+
uniform_fit.best_parameters().index(f'peak{i+1}_sigma')]
|
|
1496
|
+
for i in range(num_peak)]
|
|
1497
|
+
uniform_fit_sigmas_errors = [
|
|
1498
|
+
uniform_fit.best_errors[
|
|
1499
|
+
uniform_fit.best_parameters().index(f'peak{i+1}_sigma')]
|
|
1500
|
+
for i in range(num_peak)]
|
|
1501
|
+
if not np.asarray(uniform_success).all():
|
|
1502
|
+
for n in range(num_peak):
|
|
1503
|
+
uniform_fit_centers[n] = np.where(
|
|
1504
|
+
uniform_success, uniform_fit_centers[n], peak_locations[n])
|
|
1505
|
+
uniform_fit_centers_errors[n] *= uniform_success
|
|
1506
|
+
uniform_fit_amplitudes[n] *= uniform_success
|
|
1507
|
+
uniform_fit_amplitudes_errors[n] *= uniform_success
|
|
1508
|
+
uniform_fit_sigmas[n] *= uniform_success
|
|
1509
|
+
uniform_fit_sigmas_errors[n] *= uniform_success
|
|
1510
|
+
uniform_best_fit = uniform_fit.best_fit
|
|
1511
|
+
uniform_residuals = uniform_fit.residual
|
|
1512
|
+
uniform_redchi = uniform_fit.redchi
|
|
1513
|
+
|
|
1514
|
+
if num_peak == 1:
|
|
1515
|
+
return (
|
|
1516
|
+
uniform_fit_centers, uniform_fit_centers_errors,
|
|
1517
|
+
uniform_fit_amplitudes, uniform_fit_amplitudes_errors,
|
|
1518
|
+
uniform_fit_sigmas, uniform_fit_sigmas_errors,
|
|
1519
|
+
uniform_best_fit, uniform_residuals, uniform_redchi, uniform_success,
|
|
1520
|
+
uniform_fit_centers, uniform_fit_centers_errors,
|
|
1521
|
+
uniform_fit_amplitudes, uniform_fit_amplitudes_errors,
|
|
1522
|
+
uniform_fit_sigmas, uniform_fit_sigmas_errors,
|
|
1523
|
+
uniform_best_fit, uniform_residuals,
|
|
1524
|
+
uniform_redchi, uniform_success
|
|
1525
|
+
)
|
|
1009
1526
|
|
|
1010
1527
|
# Perform unconstrained fit
|
|
1011
|
-
|
|
1012
|
-
fit.
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1528
|
+
config['models'][-1]['fit_type'] = 'unconstrained'
|
|
1529
|
+
unconstrained_fit = fit.process(uniform_fit, config)
|
|
1530
|
+
unconstrained_success = unconstrained_fit.success
|
|
1531
|
+
if spectra.ndim == 1:
|
|
1532
|
+
if unconstrained_success:
|
|
1533
|
+
unconstrained_fit_centers = [
|
|
1534
|
+
unconstrained_fit.best_values[
|
|
1535
|
+
f'peak{i+1}_center'] for i in range(num_peak)]
|
|
1536
|
+
unconstrained_fit_centers_errors = [
|
|
1537
|
+
unconstrained_fit.best_errors[
|
|
1538
|
+
f'peak{i+1}_center'] for i in range(num_peak)]
|
|
1539
|
+
unconstrained_fit_amplitudes = [
|
|
1540
|
+
unconstrained_fit.best_values[
|
|
1541
|
+
f'peak{i+1}_amplitude'] for i in range(num_peak)]
|
|
1542
|
+
unconstrained_fit_amplitudes_errors = [
|
|
1543
|
+
unconstrained_fit.best_errors[
|
|
1544
|
+
f'peak{i+1}_amplitude'] for i in range(num_peak)]
|
|
1545
|
+
unconstrained_fit_sigmas = [
|
|
1546
|
+
unconstrained_fit.best_values[
|
|
1547
|
+
f'peak{i+1}_sigma'] for i in range(num_peak)]
|
|
1548
|
+
unconstrained_fit_sigmas_errors = [
|
|
1549
|
+
unconstrained_fit.best_errors[
|
|
1550
|
+
f'peak{i+1}_sigma'] for i in range(num_peak)]
|
|
1551
|
+
else:
|
|
1552
|
+
unconstrained_fit_centers = list(peak_locations)
|
|
1553
|
+
unconstrained_fit_centers_errors = [0]
|
|
1554
|
+
unconstrained_fit_amplitudes = [0]
|
|
1555
|
+
unconstrained_fit_amplitudes_errors = [0]
|
|
1556
|
+
unconstrained_fit_sigmas = [0]
|
|
1557
|
+
unconstrained_fit_sigmas_errors = [0]
|
|
1558
|
+
else:
|
|
1559
|
+
unconstrained_fit_centers = np.array(
|
|
1560
|
+
[unconstrained_fit.best_values[
|
|
1561
|
+
unconstrained_fit.best_parameters().index(f'peak{i+1}_center')]
|
|
1562
|
+
for i in range(num_peak)])
|
|
1563
|
+
unconstrained_fit_centers_errors = np.array(
|
|
1564
|
+
[unconstrained_fit.best_errors[
|
|
1565
|
+
unconstrained_fit.best_parameters().index(f'peak{i+1}_center')]
|
|
1566
|
+
for i in range(num_peak)])
|
|
1567
|
+
unconstrained_fit_amplitudes = [
|
|
1568
|
+
unconstrained_fit.best_values[
|
|
1569
|
+
unconstrained_fit.best_parameters().index(
|
|
1570
|
+
f'peak{i+1}_amplitude')]
|
|
1571
|
+
for i in range(num_peak)]
|
|
1572
|
+
unconstrained_fit_amplitudes_errors = [
|
|
1573
|
+
unconstrained_fit.best_errors[
|
|
1574
|
+
unconstrained_fit.best_parameters().index(
|
|
1575
|
+
f'peak{i+1}_amplitude')]
|
|
1576
|
+
for i in range(num_peak)]
|
|
1577
|
+
unconstrained_fit_sigmas = [
|
|
1578
|
+
unconstrained_fit.best_values[
|
|
1579
|
+
unconstrained_fit.best_parameters().index(f'peak{i+1}_sigma')]
|
|
1580
|
+
for i in range(num_peak)]
|
|
1581
|
+
unconstrained_fit_sigmas_errors = [
|
|
1582
|
+
unconstrained_fit.best_errors[
|
|
1583
|
+
unconstrained_fit.best_parameters().index(f'peak{i+1}_sigma')]
|
|
1584
|
+
for i in range(num_peak)]
|
|
1585
|
+
if not np.asarray(unconstrained_success).all():
|
|
1586
|
+
for n in range(num_peak):
|
|
1587
|
+
unconstrained_fit_centers[n] = np.where(
|
|
1588
|
+
unconstrained_success, unconstrained_fit_centers[n],
|
|
1589
|
+
peak_locations[n])
|
|
1590
|
+
unconstrained_fit_centers_errors[n] *= unconstrained_success
|
|
1591
|
+
unconstrained_fit_amplitudes[n] *= unconstrained_success
|
|
1592
|
+
unconstrained_fit_amplitudes_errors[n] *= unconstrained_success
|
|
1593
|
+
unconstrained_fit_sigmas[n] *= unconstrained_success
|
|
1594
|
+
unconstrained_fit_sigmas_errors[n] *= unconstrained_success
|
|
1595
|
+
unconstrained_best_fit = unconstrained_fit.best_fit
|
|
1596
|
+
unconstrained_residuals = unconstrained_fit.residual
|
|
1597
|
+
unconstrained_redchi = unconstrained_fit.redchi
|
|
1044
1598
|
|
|
1045
1599
|
return (
|
|
1046
1600
|
uniform_fit_centers, uniform_fit_centers_errors,
|