ChessAnalysisPipeline 0.0.13__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 +10 -0
- CHAP/common/models/map.py +389 -124
- CHAP/common/processor.py +1494 -59
- CHAP/common/reader.py +180 -8
- CHAP/common/writer.py +192 -15
- CHAP/edd/__init__.py +12 -3
- CHAP/edd/models.py +868 -451
- CHAP/edd/processor.py +2383 -462
- CHAP/edd/reader.py +672 -0
- CHAP/edd/utils.py +906 -172
- CHAP/foxden/__init__.py +6 -0
- CHAP/foxden/processor.py +42 -0
- CHAP/foxden/writer.py +65 -0
- CHAP/pipeline.py +35 -3
- CHAP/runner.py +43 -16
- CHAP/tomo/models.py +15 -5
- CHAP/tomo/processor.py +871 -761
- CHAP/utils/__init__.py +1 -0
- CHAP/utils/fit.py +1339 -1309
- CHAP/utils/general.py +568 -105
- CHAP/utils/models.py +567 -0
- CHAP/utils/scanparsers.py +460 -77
- ChessAnalysisPipeline-0.0.15.dist-info/LICENSE +60 -0
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/RECORD +29 -25
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/WHEEL +1 -1
- ChessAnalysisPipeline-0.0.13.dist-info/LICENSE +0 -21
- {ChessAnalysisPipeline-0.0.13.dist-info → ChessAnalysisPipeline-0.0.15.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.13.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`.
|
|
132
135
|
:type interactive: bool, optional
|
|
133
|
-
:
|
|
134
|
-
|
|
135
|
-
:type:
|
|
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
|
|
139
|
+
:type interactive: bool, optional
|
|
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,12 +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
|
|
276
|
+
def select_material_params_old(x, y, tth, materials=[], label='Reference Data',
|
|
277
|
+
interactive=False, filename=None):
|
|
262
278
|
"""Interactively select the lattice parameters and space group for
|
|
263
279
|
a list of materials. A matplotlib figure will be shown with a plot
|
|
264
280
|
of the reference data (`x` and `y`). The figure will contain
|
|
@@ -276,17 +292,22 @@ def select_material_params(x, y, tth, materials=[], interactive=False):
|
|
|
276
292
|
:type tth: float
|
|
277
293
|
:param materials: Materials to get HKLs and lattice spacings for.
|
|
278
294
|
:type materials: list[hexrd.material.Material]
|
|
279
|
-
:param
|
|
280
|
-
`
|
|
295
|
+
:param label: Legend label for the 1D plot of reference MCA data
|
|
296
|
+
from the parameters `x`, `y`, defaults to `"Reference Data"`
|
|
297
|
+
:type label: str, optional
|
|
298
|
+
:param interactive: Show the plot and allow user interactions with
|
|
299
|
+
the matplotlib figure, defaults to `False`.
|
|
281
300
|
:type interactive: bool, optional
|
|
282
|
-
:
|
|
283
|
-
|
|
284
|
-
:
|
|
285
|
-
|
|
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]
|
|
286
306
|
"""
|
|
287
307
|
# Third party modules
|
|
288
|
-
|
|
289
|
-
|
|
308
|
+
if interactive or filename is not None:
|
|
309
|
+
import matplotlib.pyplot as plt
|
|
310
|
+
from matplotlib.widgets import Button, TextBox
|
|
290
311
|
|
|
291
312
|
# Local modules
|
|
292
313
|
from CHAP.edd.models import MaterialConfig
|
|
@@ -306,7 +327,7 @@ def select_material_params(x, y, tth, materials=[], interactive=False):
|
|
|
306
327
|
ax.set_xlabel('MCA channel energy (keV)')
|
|
307
328
|
ax.set_ylabel('MCA intensity (counts)')
|
|
308
329
|
ax.set_xlim(x[0], x[-1])
|
|
309
|
-
ax.plot(x, y)
|
|
330
|
+
ax.plot(x, y, label=label)
|
|
310
331
|
for i, material in enumerate(_materials):
|
|
311
332
|
hkls, ds = get_unique_hkls_ds([material])
|
|
312
333
|
E0s = get_peak_locations(ds, tth)
|
|
@@ -316,6 +337,7 @@ def select_material_params(x, y, tth, materials=[], interactive=False):
|
|
|
316
337
|
ax.text(E0, 1, str(hkl)[1:-1], c=f'C{i}',
|
|
317
338
|
ha='right', va='top', rotation=90,
|
|
318
339
|
transform=ax.get_xaxis_transform())
|
|
340
|
+
ax.legend()
|
|
319
341
|
ax.get_figure().canvas.draw()
|
|
320
342
|
|
|
321
343
|
def add_material(*args, material=None, new=True):
|
|
@@ -438,63 +460,323 @@ def select_material_params(x, y, tth, materials=[], interactive=False):
|
|
|
438
460
|
widget_callbacks = []
|
|
439
461
|
error_texts = []
|
|
440
462
|
|
|
441
|
-
error_pos = (0.5, 0.95)
|
|
442
|
-
error_props = {'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
443
|
-
|
|
444
463
|
_materials = deepcopy(materials)
|
|
445
464
|
for i, m in enumerate(_materials):
|
|
446
465
|
if isinstance(m, MaterialConfig):
|
|
447
466
|
_materials[i] = m._material
|
|
448
467
|
|
|
449
|
-
#
|
|
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
|
|
450
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)
|
|
451
642
|
|
|
452
|
-
|
|
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())
|
|
453
657
|
|
|
454
|
-
|
|
658
|
+
if not interactive:
|
|
455
659
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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))
|
|
461
667
|
|
|
462
|
-
|
|
463
|
-
confirm_btn = Button(plt.axes([0.75, 0.015, 0.1, 0.05]), 'Confirm')
|
|
464
|
-
confirm_cid = confirm_btn.on_clicked(confirm)
|
|
465
|
-
widget_callbacks.append([(confirm_btn, confirm_cid)])
|
|
668
|
+
else:
|
|
466
669
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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))
|
|
470
700
|
|
|
471
|
-
# Show figure for user interaction
|
|
472
701
|
plt.show()
|
|
473
702
|
|
|
474
703
|
# Disconnect all widget callbacks when figure is closed
|
|
475
704
|
# and remove the buttons before returning the figure
|
|
476
|
-
for
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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 [
|
|
484
767
|
MaterialConfig(
|
|
485
768
|
material_name=m.name, sgnum=m.sgnum,
|
|
486
769
|
lattice_parameters=[
|
|
487
770
|
m.latticeParameters[i].value for i in range(6)])
|
|
488
|
-
for m in
|
|
771
|
+
for m in materials]
|
|
489
772
|
|
|
490
|
-
return fig, new_materials
|
|
491
773
|
|
|
492
774
|
def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
493
|
-
preselected_hkl_indices=[],
|
|
494
|
-
flux_energy_range=None, calibration_bin_ranges=None,
|
|
495
|
-
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):
|
|
496
778
|
"""Return a matplotlib figure to indicate data ranges and HKLs to
|
|
497
|
-
include for fitting in EDD
|
|
779
|
+
include for fitting in EDD energy/tth calibration and/or strain
|
|
498
780
|
analysis.
|
|
499
781
|
|
|
500
782
|
:param x: MCA channel energies.
|
|
@@ -516,6 +798,9 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
516
798
|
:param preselected_hkl_indices: Preselected unique HKL indices to
|
|
517
799
|
fit peaks for in the calibration routine, defaults to `[]`.
|
|
518
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
|
|
519
804
|
:param detector_name: Name of the MCA detector element.
|
|
520
805
|
:type detector_name: str, optional
|
|
521
806
|
:param ref_map: Reference map of MCA intensities to show underneath
|
|
@@ -527,25 +812,32 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
527
812
|
:param calibration_bin_ranges: MCA channel index ranges included
|
|
528
813
|
in the detector calibration.
|
|
529
814
|
:type calibration_bin_ranges: list[[int, int]], optional
|
|
530
|
-
:param
|
|
531
|
-
`
|
|
815
|
+
:param label: Legend label for the 1D plot of reference MCA data
|
|
816
|
+
from the parameters `x`, `y`, defaults to `"Reference Data"`
|
|
817
|
+
:type label: str, optional
|
|
818
|
+
:param interactive: Show the plot and allow user interactions with
|
|
819
|
+
the matplotlib figure, defaults to `True`.
|
|
532
820
|
:type interactive: bool, optional
|
|
533
|
-
:
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
:
|
|
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]
|
|
537
827
|
"""
|
|
538
828
|
# Third party modules
|
|
539
|
-
|
|
540
|
-
|
|
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
|
|
541
833
|
import matplotlib.pyplot as plt
|
|
542
|
-
from matplotlib.widgets import
|
|
834
|
+
from matplotlib.widgets import SpanSelector
|
|
543
835
|
|
|
544
836
|
# Local modules
|
|
545
837
|
from CHAP.utils.general import (
|
|
546
838
|
get_consecutive_int_range,
|
|
547
839
|
index_nearest_down,
|
|
548
|
-
|
|
840
|
+
index_nearest_up,
|
|
549
841
|
)
|
|
550
842
|
|
|
551
843
|
def change_fig_title(title):
|
|
@@ -560,10 +852,20 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
560
852
|
error_texts.pop()
|
|
561
853
|
error_texts.append(plt.figtext(*error_pos, error, **error_props))
|
|
562
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
|
+
|
|
563
865
|
def hkl_locations_in_any_span(hkl_index):
|
|
564
866
|
"""Return the index of the span where the location of a specific
|
|
565
867
|
HKL resides. Return(-1 if outside any span."""
|
|
566
|
-
if hkl_index < 0 or hkl_index>= len(hkl_locations):
|
|
868
|
+
if hkl_index < 0 or hkl_index >= len(hkl_locations):
|
|
567
869
|
return -1
|
|
568
870
|
for i, span in enumerate(spans):
|
|
569
871
|
if (span.extents[0] <= hkl_locations[hkl_index] and
|
|
@@ -571,15 +873,21 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
571
873
|
return i
|
|
572
874
|
return -1
|
|
573
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
|
+
|
|
574
882
|
def on_span_select(xmin, xmax):
|
|
575
883
|
"""Callback function for the SpanSelector widget."""
|
|
576
884
|
removed_hkls = False
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
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:
|
|
580
888
|
hkl_vlines[hkl_index].set(**excluded_hkl_props)
|
|
581
|
-
|
|
582
|
-
|
|
889
|
+
selected_hkl_indices.remove(hkl_index)
|
|
890
|
+
removed_hkls = True
|
|
583
891
|
combined_spans = False
|
|
584
892
|
combined_spans_test = True
|
|
585
893
|
while combined_spans_test:
|
|
@@ -607,27 +915,37 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
607
915
|
for hkl_index in range(len(hkl_locations)):
|
|
608
916
|
if (hkl_index not in selected_hkl_indices
|
|
609
917
|
and hkl_locations_in_any_span(hkl_index) >= 0):
|
|
610
|
-
|
|
918
|
+
if interactive or filename is not None:
|
|
919
|
+
hkl_vlines[hkl_index].set(**included_hkl_props)
|
|
611
920
|
selected_hkl_indices.append(hkl_index)
|
|
612
921
|
added_hkls = True
|
|
613
|
-
if
|
|
614
|
-
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:
|
|
615
932
|
change_error_text(
|
|
616
|
-
'
|
|
617
|
-
'
|
|
618
|
-
|
|
933
|
+
'Adjusted the selected HKL(s) to match the selected '
|
|
934
|
+
'energy mask')
|
|
935
|
+
elif added_hkls:
|
|
619
936
|
change_error_text(
|
|
620
|
-
'
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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')
|
|
941
|
+
# If using ref_map, update the colorbar range to min / max of
|
|
942
|
+
# the selected data only
|
|
943
|
+
if ref_map is not None:
|
|
944
|
+
selected_data = ref_map[:,get_mask()]
|
|
945
|
+
ref_map_mappable = ax_map.pcolormesh(
|
|
946
|
+
x, np.arange(ref_map.shape[0]), ref_map,
|
|
947
|
+
vmin=selected_data.min(), vmax=selected_data.max())
|
|
948
|
+
fig.colorbar(ref_map_mappable, cax=cax)
|
|
631
949
|
plt.draw()
|
|
632
950
|
|
|
633
951
|
def add_span(event, xrange_init=None):
|
|
@@ -659,17 +977,19 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
659
977
|
hkl_vline.set(**excluded_hkl_props)
|
|
660
978
|
selected_hkl_indices.remove(hkl_index)
|
|
661
979
|
span = spans[hkl_locations_in_any_span(hkl_index)]
|
|
662
|
-
span_next_hkl_index = hkl_locations_in_any_span(hkl_index+1)
|
|
663
980
|
span_prev_hkl_index = hkl_locations_in_any_span(hkl_index-1)
|
|
664
|
-
|
|
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):
|
|
665
985
|
span.set_visible(False)
|
|
666
986
|
spans.remove(span)
|
|
667
|
-
elif
|
|
987
|
+
elif span_curr_hkl_index != span_next_hkl_index:
|
|
668
988
|
span.extents = (
|
|
669
989
|
span.extents[0],
|
|
670
990
|
0.5*(hkl_locations[hkl_index-1]
|
|
671
991
|
+ hkl_locations[hkl_index]))
|
|
672
|
-
elif
|
|
992
|
+
elif span_curr_hkl_index != span_prev_hkl_index:
|
|
673
993
|
span.extents = (
|
|
674
994
|
0.5*(hkl_locations[hkl_index]
|
|
675
995
|
+ hkl_locations[hkl_index+1]),
|
|
@@ -688,13 +1008,54 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
688
1008
|
f'Adjusted the selected energy mask to reflect the '
|
|
689
1009
|
'removed HKL')
|
|
690
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_))
|
|
691
1052
|
change_error_text(
|
|
692
|
-
f'
|
|
693
|
-
'
|
|
1053
|
+
f'Adjusted the selected energy mask to reflect the '
|
|
1054
|
+
'added HKL')
|
|
694
1055
|
plt.draw()
|
|
695
1056
|
|
|
696
1057
|
def reset(event):
|
|
697
|
-
"""Callback function for the "
|
|
1058
|
+
"""Callback function for the "Reset" button."""
|
|
698
1059
|
for hkl_index in deepcopy(selected_hkl_indices):
|
|
699
1060
|
hkl_vlines[hkl_index].set(**excluded_hkl_props)
|
|
700
1061
|
selected_hkl_indices.remove(hkl_index)
|
|
@@ -705,8 +1066,9 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
705
1066
|
|
|
706
1067
|
def confirm(event):
|
|
707
1068
|
"""Callback function for the "Confirm" button."""
|
|
708
|
-
if not len(spans) or len(selected_hkl_indices) <
|
|
709
|
-
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')
|
|
710
1072
|
plt.draw()
|
|
711
1073
|
else:
|
|
712
1074
|
if error_texts:
|
|
@@ -725,62 +1087,12 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
725
1087
|
fig_title = []
|
|
726
1088
|
error_texts = []
|
|
727
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
|
|
728
1094
|
hkl_locations = [loc for loc in get_peak_locations(ds, tth)
|
|
729
1095
|
if x[0] <= loc <= x[-1]]
|
|
730
|
-
hkl_labels = [str(hkl)[1:-1] for hkl, loc in zip(hkls, hkl_locations)]
|
|
731
|
-
|
|
732
|
-
title_pos = (0.5, 0.95)
|
|
733
|
-
title_props = {'fontsize': 'xx-large', 'ha': 'center', 'va': 'bottom'}
|
|
734
|
-
error_pos = (0.5, 0.90)
|
|
735
|
-
error_props = {'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
736
|
-
excluded_hkl_props = {
|
|
737
|
-
'color': 'black', 'linestyle': '--','linewidth': 1,
|
|
738
|
-
'marker': 10, 'markersize': 5, 'fillstyle': 'none'}
|
|
739
|
-
included_hkl_props = {
|
|
740
|
-
'color': 'green', 'linestyle': '-', 'linewidth': 2,
|
|
741
|
-
'marker': 10, 'markersize': 10, 'fillstyle': 'full'}
|
|
742
|
-
excluded_data_props = {
|
|
743
|
-
'facecolor': 'white', 'edgecolor': 'gray', 'linestyle': ':'}
|
|
744
|
-
included_data_props = {
|
|
745
|
-
'alpha': 0.5, 'facecolor': 'tab:blue', 'edgecolor': 'blue'}
|
|
746
|
-
|
|
747
|
-
if flux_energy_range is None:
|
|
748
|
-
min_x = x.min()
|
|
749
|
-
max_x = x.max()
|
|
750
|
-
else:
|
|
751
|
-
min_x = x[index_nearest_upp(x, max(x.min(), flux_energy_range[0]))]
|
|
752
|
-
max_x = x[index_nearest_down(x, min(x.max(), flux_energy_range[1]))]
|
|
753
|
-
|
|
754
|
-
if ref_map is None:
|
|
755
|
-
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
756
|
-
ax.set(xlabel='Energy (keV)', ylabel='Intensity (counts)')
|
|
757
|
-
else:
|
|
758
|
-
fig, (ax, ax_map) = plt.subplots(
|
|
759
|
-
2, sharex=True, figsize=(11, 8.5), height_ratios=[2, 1])
|
|
760
|
-
ax.set(ylabel='Intensity (counts)')
|
|
761
|
-
ax_map.pcolormesh(x, np.arange(ref_map.shape[0]), ref_map)
|
|
762
|
-
ax_map.set_yticks([])
|
|
763
|
-
ax_map.set_xlabel('Energy (keV)')
|
|
764
|
-
ax_map.set_xlim(x[0], x[-1])
|
|
765
|
-
handles = ax.plot(x, y, color='k', label='Reference Data')
|
|
766
|
-
if calibration_bin_ranges is not None:
|
|
767
|
-
ylow = ax.get_ylim()[0]
|
|
768
|
-
for low, upp in calibration_bin_ranges:
|
|
769
|
-
ax.plot([x[low], x[upp]], [ylow, ylow], color='r', linewidth=2)
|
|
770
|
-
handles.append(mlines.Line2D(
|
|
771
|
-
[], [], label='Energies included in calibration', color='r',
|
|
772
|
-
linewidth=2))
|
|
773
|
-
handles.append(mlines.Line2D(
|
|
774
|
-
[], [], label='Excluded / unselected HKL', **excluded_hkl_props))
|
|
775
|
-
handles.append(mlines.Line2D(
|
|
776
|
-
[], [], label='Included / selected HKL', **included_hkl_props))
|
|
777
|
-
handles.append(Patch(
|
|
778
|
-
label='Excluded / unselected data', **excluded_data_props))
|
|
779
|
-
handles.append(Patch(
|
|
780
|
-
label='Included / selected data', **included_data_props))
|
|
781
|
-
ax.legend(handles=handles)
|
|
782
|
-
ax.set_xlim(x[0], x[-1])
|
|
783
|
-
|
|
784
1096
|
if selected_hkl_indices and not preselected_bin_ranges:
|
|
785
1097
|
index_ranges = get_consecutive_int_range(selected_hkl_indices)
|
|
786
1098
|
for index_range in index_ranges:
|
|
@@ -795,30 +1107,109 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
795
1107
|
else:
|
|
796
1108
|
max_ = 0.5*(hkl_locations[j] + max_x)
|
|
797
1109
|
preselected_bin_ranges.append(
|
|
798
|
-
[
|
|
1110
|
+
[index_nearest_up(x, min_), index_nearest_down(x, max_)])
|
|
1111
|
+
|
|
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()
|
|
799
1126
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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)')
|
|
804
1147
|
else:
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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])
|
|
809
1188
|
|
|
810
|
-
|
|
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
|
|
811
1202
|
for bin_range in preselected_bin_ranges:
|
|
812
1203
|
add_span(None, xrange_init=x[bin_range])
|
|
813
|
-
init_flag = [False]
|
|
814
1204
|
|
|
815
1205
|
if not interactive:
|
|
816
1206
|
|
|
817
|
-
if
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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}')
|
|
822
1213
|
|
|
823
1214
|
else:
|
|
824
1215
|
|
|
@@ -828,6 +1219,8 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
828
1219
|
change_fig_title(
|
|
829
1220
|
f'Select data and HKLs to use in fitting {detector_name}')
|
|
830
1221
|
fig.subplots_adjust(bottom=0.2)
|
|
1222
|
+
if ref_map is not None:
|
|
1223
|
+
position_cax()
|
|
831
1224
|
|
|
832
1225
|
# Setup "Add span" button
|
|
833
1226
|
add_span_btn = Button(plt.axes([0.125, 0.05, 0.15, 0.075]), 'Add span')
|
|
@@ -860,6 +1253,14 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
860
1253
|
reset_btn.ax.remove()
|
|
861
1254
|
plt.subplots_adjust(bottom=0.0)
|
|
862
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
|
+
|
|
863
1264
|
selected_bin_ranges = [np.searchsorted(x, span.extents).tolist()
|
|
864
1265
|
for span in spans]
|
|
865
1266
|
if not selected_bin_ranges:
|
|
@@ -869,7 +1270,340 @@ def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
|
869
1270
|
else:
|
|
870
1271
|
selected_hkl_indices = None
|
|
871
1272
|
|
|
872
|
-
|
|
873
|
-
|
|
1273
|
+
return selected_bin_ranges, selected_hkl_indices
|
|
1274
|
+
|
|
1275
|
+
|
|
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):
|
|
1346
|
+
"""Return twenty arrays of fit results for the map of spectra
|
|
1347
|
+
provided: uniform centers, uniform center errors, uniform
|
|
1348
|
+
amplitudes, uniform amplitude errors, uniform sigmas, uniform
|
|
1349
|
+
sigma errors, uniform best fit, uniform residuals, uniform reduced
|
|
1350
|
+
chi, uniform success codes, unconstrained centers, unconstrained
|
|
1351
|
+
center errors, unconstrained amplitudes, unconstrained amplitude
|
|
1352
|
+
errors, unconstrained sigmas, unconstrained sigma errors,
|
|
1353
|
+
unconstrained best fit, unconstrained residuals, unconstrained
|
|
1354
|
+
reduced chi, and unconstrained success codes.
|
|
1355
|
+
|
|
1356
|
+
:param spectra: Array of intensity spectra to fit.
|
|
1357
|
+
:type spectra: numpy.ndarray
|
|
1358
|
+
:param energies: Bin energies for the spectra provided.
|
|
1359
|
+
:type energies: numpy.ndarray
|
|
1360
|
+
:param peak_locations: Initial guesses for peak ceneters to use
|
|
1361
|
+
for the uniform fit.
|
|
1362
|
+
:type peak_locations: list[float]
|
|
1363
|
+
:param detector: A single MCA detector element configuration.
|
|
1364
|
+
:type detector: CHAP.edd.models.MCAElementStrainAnalysisConfig
|
|
1365
|
+
:returns: Uniform and unconstrained centers, amplitdues, sigmas
|
|
1366
|
+
(and errors for all three), best fits, residuals between the
|
|
1367
|
+
best fits and the input spectra, reduced chi, and fit success
|
|
1368
|
+
statuses.
|
|
1369
|
+
:rtype: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
1370
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
1371
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
1372
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
1373
|
+
numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray,
|
|
1374
|
+
numpy.ndarray]
|
|
1375
|
+
"""
|
|
1376
|
+
# Third party modules
|
|
1377
|
+
from nexusformat.nexus import NXdata, NXfield
|
|
874
1378
|
|
|
875
|
-
|
|
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
|
|
1384
|
+
num_peak = len(peak_locations)
|
|
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
|
+
)
|
|
1526
|
+
|
|
1527
|
+
# Perform unconstrained fit
|
|
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
|
|
1598
|
+
|
|
1599
|
+
return (
|
|
1600
|
+
uniform_fit_centers, uniform_fit_centers_errors,
|
|
1601
|
+
uniform_fit_amplitudes, uniform_fit_amplitudes_errors,
|
|
1602
|
+
uniform_fit_sigmas, uniform_fit_sigmas_errors,
|
|
1603
|
+
uniform_best_fit, uniform_residuals, uniform_redchi, uniform_success,
|
|
1604
|
+
unconstrained_fit_centers, unconstrained_fit_centers_errors,
|
|
1605
|
+
unconstrained_fit_amplitudes, unconstrained_fit_amplitudes_errors,
|
|
1606
|
+
unconstrained_fit_sigmas, unconstrained_fit_sigmas_errors,
|
|
1607
|
+
unconstrained_best_fit, unconstrained_residuals,
|
|
1608
|
+
unconstrained_redchi, unconstrained_success
|
|
1609
|
+
)
|