ChessAnalysisPipeline 0.0.11__py3-none-any.whl → 0.0.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ChessAnalysisPipeline might be problematic. Click here for more details.
- CHAP/__init__.py +2 -0
- CHAP/common/__init__.py +6 -2
- CHAP/common/models/map.py +217 -70
- CHAP/common/processor.py +249 -155
- CHAP/common/reader.py +175 -130
- CHAP/common/writer.py +150 -94
- CHAP/edd/models.py +458 -262
- CHAP/edd/processor.py +614 -354
- CHAP/edd/utils.py +746 -235
- CHAP/tomo/models.py +22 -18
- CHAP/tomo/processor.py +1215 -892
- CHAP/utils/fit.py +211 -127
- CHAP/utils/general.py +789 -610
- CHAP/utils/parfile.py +1 -9
- CHAP/utils/scanparsers.py +101 -52
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/METADATA +1 -1
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/RECORD +21 -21
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/WHEEL +1 -1
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/LICENSE +0 -0
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/entry_points.txt +0 -0
- {ChessAnalysisPipeline-0.0.11.dist-info → ChessAnalysisPipeline-0.0.13.dist-info}/top_level.txt +0 -0
CHAP/edd/utils.py
CHANGED
|
@@ -1,55 +1,84 @@
|
|
|
1
1
|
"""Utility functions for EDD workflows"""
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* physical_constants['speed of light in vacuum'][0]
|
|
3
|
+
# System modules
|
|
4
|
+
from copy import deepcopy
|
|
6
5
|
|
|
6
|
+
# Third party modules
|
|
7
|
+
import numpy as np
|
|
7
8
|
|
|
8
|
-
def
|
|
9
|
-
"""Return
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
def get_peak_locations(ds, tth):
|
|
10
|
+
"""Return the peak locations for a given set of lattice spacings
|
|
11
|
+
and 2&theta value.
|
|
12
|
+
|
|
13
|
+
:param ds: A set of lattice spacings in angstroms.
|
|
14
|
+
:type ds: list[float]
|
|
15
|
+
:param tth: Diffraction angle 2&theta.
|
|
16
|
+
:type tth: float
|
|
17
|
+
:return: The peak locations in keV.
|
|
18
|
+
:rtype: numpy.ndarray
|
|
19
|
+
"""
|
|
20
|
+
# Third party modules
|
|
21
|
+
from scipy.constants import physical_constants
|
|
22
|
+
|
|
23
|
+
hc = 1e7 * physical_constants['Planck constant in eV/Hz'][0] \
|
|
24
|
+
* physical_constants['speed of light in vacuum'][0]
|
|
12
25
|
|
|
26
|
+
return hc / (2. * ds * np.sin(0.5 * np.radians(tth)))
|
|
13
27
|
|
|
14
|
-
|
|
28
|
+
def make_material(name, sgnum, lattice_parameters, dmin=0.6):
|
|
29
|
+
"""Return a hexrd.material.Material with the given properties.
|
|
30
|
+
|
|
31
|
+
:param name: Material name.
|
|
15
32
|
:type name: str
|
|
16
|
-
:param sgnum:
|
|
33
|
+
:param sgnum: Space group of the material.
|
|
17
34
|
:type sgnum: int
|
|
18
|
-
:param
|
|
19
|
-
γ], or fewer as the symmetry of
|
|
20
|
-
for instance, a cubic lattice with
|
|
21
|
-
just provide [a, ])
|
|
22
|
-
:type
|
|
23
|
-
:param dmin: dmin
|
|
35
|
+
:param lattice_parameters: The material's lattice parameters
|
|
36
|
+
([a, b, c, α, β, γ], or fewer as the symmetry of
|
|
37
|
+
the space group allows --- for instance, a cubic lattice with
|
|
38
|
+
space group number 225 can just provide [a, ]).
|
|
39
|
+
:type lattice_parameters: list[float]
|
|
40
|
+
:param dmin: Materials's dmin value in angstroms (Å),
|
|
41
|
+
defaults to `0.6`.
|
|
24
42
|
:type dmin: float, optional
|
|
25
|
-
:return:
|
|
43
|
+
:return: A hexrd material.
|
|
26
44
|
:rtype: heard.material.Material
|
|
27
45
|
"""
|
|
46
|
+
# Third party modules
|
|
28
47
|
from hexrd.material import Material
|
|
29
48
|
from hexrd.valunits import valWUnit
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
|
|
50
|
+
material = Material()
|
|
51
|
+
material.name = name
|
|
52
|
+
material.sgnum = sgnum
|
|
53
|
+
if isinstance(lattice_parameters, float):
|
|
54
|
+
lattice_parameters = [lattice_parameters]
|
|
55
|
+
material.latticeParameters = lattice_parameters
|
|
56
|
+
material.dmin = valWUnit('lp', 'length', dmin, 'angstrom')
|
|
57
|
+
nhkls = len(material.planeData.exclusions)
|
|
58
|
+
material.planeData.set_exclusions(np.zeros(nhkls, dtype=bool))
|
|
59
|
+
|
|
60
|
+
return material
|
|
40
61
|
|
|
41
62
|
def get_unique_hkls_ds(materials, tth_tol=None, tth_max=None, round_sig=8):
|
|
42
|
-
"""Return the unique HKLs and
|
|
43
|
-
materials.
|
|
63
|
+
"""Return the unique HKLs and lattice spacings for the given list
|
|
64
|
+
of materials.
|
|
44
65
|
|
|
45
|
-
:param materials:
|
|
66
|
+
:param materials: Materials to get HKLs and lattice spacings for.
|
|
46
67
|
:type materials: list[hexrd.material.Material]
|
|
47
|
-
:
|
|
68
|
+
:param tth_tol: Minimum resolvable difference in 2&theta between
|
|
69
|
+
two unique HKL peaks.
|
|
70
|
+
:type tth_tol: float, optional
|
|
71
|
+
:param tth_max: Detector rotation about hutch x axis.
|
|
72
|
+
:type tth_max: float, optional
|
|
73
|
+
:param round_sig: The number of significant figures in the unique
|
|
74
|
+
lattice spacings, defaults to `8`.
|
|
75
|
+
:type round_sig: int, optional
|
|
76
|
+
:return: Unique HKLs, unique lattice spacings.
|
|
48
77
|
:rtype: tuple[np.ndarray, np.ndarray]
|
|
49
78
|
"""
|
|
50
|
-
|
|
51
|
-
import numpy as np
|
|
79
|
+
# Local modules
|
|
52
80
|
from CHAP.edd.models import MaterialConfig
|
|
81
|
+
|
|
53
82
|
_materials = deepcopy(materials)
|
|
54
83
|
for i, m in enumerate(materials):
|
|
55
84
|
if isinstance(m, MaterialConfig):
|
|
@@ -75,234 +104,228 @@ def get_unique_hkls_ds(materials, tth_tol=None, tth_max=None, round_sig=8):
|
|
|
75
104
|
# Limit the list to unique lattice spacings
|
|
76
105
|
hkls_unique = hkls[ds_index_unique,:].astype(int)
|
|
77
106
|
ds_unique = ds[ds_index_unique]
|
|
78
|
-
return hkls_unique, ds_unique
|
|
79
107
|
|
|
108
|
+
return hkls_unique, ds_unique
|
|
80
109
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
figure. Optionally modify `detector.fit_hkls` by interacting with
|
|
84
|
-
a matplotlib figure.
|
|
85
|
-
|
|
86
|
-
:param detector: the detector to set `fit_hkls` on
|
|
87
|
-
:type detector: MCAElementConfig
|
|
88
|
-
:param material: the material to pick HKLs for
|
|
89
|
-
:type material: MaterialConfig
|
|
90
|
-
:param tth: diffraction angle two-theta
|
|
91
|
-
:type tth: float
|
|
92
|
-
:param y: reference y data to plot
|
|
93
|
-
:type y: np.ndarray
|
|
94
|
-
:param x: reference x data to plot
|
|
95
|
-
:type x: np.ndarray
|
|
96
|
-
:param interactive: show the plot and allow user interactions with
|
|
97
|
-
the matplotlib figure
|
|
98
|
-
:type interactive: bool
|
|
99
|
-
:return: plot showing the user-selected HKLs
|
|
100
|
-
:rtype: matplotlib.figure.Figure
|
|
101
|
-
"""
|
|
102
|
-
import numpy as np
|
|
103
|
-
hkls, ds = get_unique_hkls_ds(materials)
|
|
104
|
-
peak_locations = hc / (2. * ds * np.sin(0.5 * np.radians(tth)))
|
|
105
|
-
pre_selected_peak_indices = detector.fit_hkls \
|
|
106
|
-
if detector.fit_hkls else []
|
|
107
|
-
from CHAP.utils.general import select_peaks
|
|
108
|
-
selected_peaks, figure = select_peaks(
|
|
109
|
-
y, x, peak_locations,
|
|
110
|
-
peak_labels=[str(hkl)[1:-1] for hkl in hkls],
|
|
111
|
-
pre_selected_peak_indices=pre_selected_peak_indices,
|
|
112
|
-
mask=detector.mca_mask(),
|
|
113
|
-
interactive=interactive,
|
|
114
|
-
xlabel='MCA channel energy (keV)',
|
|
115
|
-
ylabel='MCA intensity (counts)',
|
|
116
|
-
title='Mask and HKLs for fitting')
|
|
117
|
-
|
|
118
|
-
selected_hkl_indices = [int(np.where(peak_locations == peak)[0][0]) \
|
|
119
|
-
for peak in selected_peaks]
|
|
120
|
-
detector.fit_hkls = selected_hkl_indices
|
|
121
|
-
|
|
122
|
-
return figure
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def select_tth_initial_guess(detector, material, y, x):
|
|
110
|
+
def select_tth_initial_guess(x, y, hkls, ds, tth_initial_guess=5.0,
|
|
111
|
+
interactive=False):
|
|
126
112
|
"""Show a matplotlib figure of a reference MCA spectrum on top of
|
|
127
113
|
HKL locations. The figure includes an input field to adjust the
|
|
128
|
-
initial
|
|
129
|
-
on the adjusted value of the initial
|
|
130
|
-
|
|
131
|
-
:param
|
|
132
|
-
:type detector: MCAElementConfig
|
|
133
|
-
:param material: the material to show HKLs for
|
|
134
|
-
:type material: MaterialConfig
|
|
135
|
-
:param y: reference y data to plot
|
|
136
|
-
:type y: np.ndarray
|
|
137
|
-
:param x: reference x data to plot
|
|
114
|
+
initial 2&theta guess and responds by updating the HKL locations
|
|
115
|
+
based on the adjusted value of the initial 2&theta guess.
|
|
116
|
+
|
|
117
|
+
:param x: MCA channel energies.
|
|
138
118
|
:type x: np.ndarray
|
|
139
|
-
:
|
|
119
|
+
:param y: MCA intensities.
|
|
120
|
+
:type y: np.ndarray
|
|
121
|
+
:param hkls: List of unique HKL indices to fit peaks for in the
|
|
122
|
+
calibration routine.
|
|
123
|
+
:type fit_hkls: Union(numpy.ndarray, list[list[int, int,int]])
|
|
124
|
+
:param ds: Lattice spacings in angstroms associated with the
|
|
125
|
+
unique HKL indices.
|
|
126
|
+
:type ds: Union(numpy.ndarray, list[float])
|
|
127
|
+
:ivar tth_initial_guess: Initial guess for 2&theta,
|
|
128
|
+
defaults to `5.0`.
|
|
129
|
+
:type tth_initial_guess: float, optional
|
|
130
|
+
:param interactive: Allows for user interactions, defaults to
|
|
131
|
+
`False`.
|
|
132
|
+
:type interactive: bool, optional
|
|
133
|
+
:return: A saveable matplotlib figure and the selected initial
|
|
134
|
+
guess for 2&theta.
|
|
135
|
+
:type: matplotlib.figure.Figure, float
|
|
140
136
|
"""
|
|
137
|
+
# Third party modules
|
|
141
138
|
import matplotlib.pyplot as plt
|
|
142
139
|
from matplotlib.widgets import Button, TextBox
|
|
143
|
-
import numpy as np
|
|
144
140
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
141
|
+
def change_fig_title(title):
|
|
142
|
+
if fig_title:
|
|
143
|
+
fig_title[0].remove()
|
|
144
|
+
fig_title.pop()
|
|
145
|
+
fig_title.append(plt.figtext(*title_pos, title, **title_props))
|
|
146
|
+
|
|
147
|
+
def change_error_text(error):
|
|
148
|
+
if error_texts:
|
|
149
|
+
error_texts[0].remove()
|
|
150
|
+
error_texts.pop()
|
|
151
|
+
error_texts.append(plt.figtext(*error_pos, error, **error_props))
|
|
152
|
+
|
|
153
|
+
def new_guess(tth):
|
|
154
|
+
"""Callback function for the tth input."""
|
|
155
|
+
try:
|
|
156
|
+
tth_new_guess = float(tth)
|
|
157
|
+
except:
|
|
158
|
+
change_error_text(
|
|
159
|
+
r'Invalid 2$\theta$ 'f'cannot convert {tth} to float, '
|
|
160
|
+
r'enter a valid 2$\theta$')
|
|
161
|
+
return
|
|
162
|
+
for i, (loc, hkl) in enumerate(zip(
|
|
163
|
+
get_peak_locations(ds, tth_new_guess), hkls)):
|
|
164
|
+
if i in hkl_peaks:
|
|
165
|
+
j = hkl_peaks.index(i)
|
|
166
|
+
hkl_lines[j].remove()
|
|
167
|
+
hkl_lbls[j].remove()
|
|
168
|
+
if x[0] <= loc <= x[-1]:
|
|
169
|
+
hkl_lines[j] = ax.axvline(loc, c='k', ls='--', lw=1)
|
|
170
|
+
hkl_lbls[j] = ax.text(loc, 1, str(hkls[i])[1:-1],
|
|
171
|
+
ha='right', va='top', rotation=90,
|
|
172
|
+
transform=ax.get_xaxis_transform())
|
|
173
|
+
else:
|
|
174
|
+
hkl_peaks.pop(j)
|
|
175
|
+
hkl_lines.pop(j)
|
|
176
|
+
hkl_lbls.pop(j)
|
|
177
|
+
elif x[0] <= loc <= x[-1]:
|
|
178
|
+
hkl_peaks.append(i)
|
|
179
|
+
hkl_lines.append(ax.axvline(loc, c='k', ls='--', lw=1))
|
|
180
|
+
hkl_lbls.append(
|
|
181
|
+
ax.text(
|
|
182
|
+
loc, 1, str(hkl)[1:-1], ha='right', va='top',
|
|
183
|
+
rotation=90, transform=ax.get_xaxis_transform()))
|
|
184
|
+
ax.get_figure().canvas.draw()
|
|
185
|
+
|
|
186
|
+
def confirm(event):
|
|
187
|
+
"""Callback function for the "Confirm" button."""
|
|
188
|
+
change_fig_title(r'Initial guess for 2$\theta$='f'{tth_input.text}')
|
|
189
|
+
plt.close()
|
|
190
|
+
|
|
191
|
+
fig_title = []
|
|
192
|
+
error_texts = []
|
|
193
|
+
|
|
194
|
+
title_pos = (0.5, 0.95)
|
|
195
|
+
title_props = {'fontsize': 'xx-large', 'ha': 'center', 'va': 'bottom'}
|
|
196
|
+
error_pos = (0.5, 0.90)
|
|
197
|
+
error_props = {'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
198
|
+
|
|
199
|
+
assert np.asarray(hkls).shape[1] == 3
|
|
200
|
+
assert np.asarray(ds).size == np.asarray(hkls).shape[0]
|
|
152
201
|
|
|
153
202
|
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
154
203
|
ax.plot(x, y)
|
|
155
204
|
ax.set_xlabel('MCA channel energy (keV)')
|
|
156
205
|
ax.set_ylabel('MCA intensity (counts)')
|
|
157
|
-
ax.
|
|
206
|
+
ax.set_xlim(x[0], x[-1])
|
|
207
|
+
peak_locations = get_peak_locations(ds, tth_initial_guess)
|
|
208
|
+
hkl_peaks = [i for i, loc in enumerate(peak_locations)
|
|
209
|
+
if x[0] <= loc <= x[-1]]
|
|
158
210
|
hkl_lines = [ax.axvline(loc, c='k', ls='--', lw=1) \
|
|
159
|
-
for loc in
|
|
211
|
+
for loc in peak_locations[hkl_peaks]]
|
|
160
212
|
hkl_lbls = [ax.text(loc, 1, str(hkl)[1:-1],
|
|
161
213
|
ha='right', va='top', rotation=90,
|
|
162
|
-
transform=ax.get_xaxis_transform())
|
|
163
|
-
for loc, hkl in zip(
|
|
164
|
-
hkls)]
|
|
214
|
+
transform=ax.get_xaxis_transform())
|
|
215
|
+
for loc, hkl in zip(peak_locations[hkl_peaks], hkls)]
|
|
165
216
|
|
|
166
|
-
|
|
167
|
-
def new_guess(tth):
|
|
168
|
-
try:
|
|
169
|
-
tth = float(tth)
|
|
170
|
-
except:
|
|
171
|
-
raise ValueError(f'Cannot convert {new_tth} to float')
|
|
172
|
-
for i, (line, loc) in enumerate(zip(hkl_lines,
|
|
173
|
-
get_peak_locations(tth))):
|
|
174
|
-
line.remove()
|
|
175
|
-
hkl_lines[i] = ax.axvline(loc, c='k', ls='--', lw=1)
|
|
176
|
-
hkl_lbls[i].remove()
|
|
177
|
-
hkl_lbls[i] = ax.text(loc, 1, str(hkls[i])[1:-1],
|
|
178
|
-
ha='right', va='top', rotation=90,
|
|
179
|
-
transform=ax.get_xaxis_transform())
|
|
180
|
-
ax.get_figure().canvas.draw()
|
|
181
|
-
detector.tth_initial_guess = tth
|
|
217
|
+
if not interactive:
|
|
182
218
|
|
|
183
|
-
|
|
184
|
-
plt.subplots_adjust(bottom=0.25)
|
|
185
|
-
tth_input = TextBox(plt.axes([0.125, 0.05, 0.15, 0.075]),
|
|
186
|
-
'$2\\theta$: ',
|
|
187
|
-
initial=tth_initial_guess)
|
|
188
|
-
cid_update_tth = tth_input.on_submit(new_guess)
|
|
219
|
+
change_fig_title(r'Initial guess for 2$\theta$='f'{tth_initial_guess}')
|
|
189
220
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
221
|
+
else:
|
|
222
|
+
|
|
223
|
+
change_fig_title(r'Adjust initial guess for 2$\theta$')
|
|
224
|
+
fig.subplots_adjust(bottom=0.2)
|
|
225
|
+
|
|
226
|
+
# Setup tth input
|
|
227
|
+
tth_input = TextBox(plt.axes([0.125, 0.05, 0.15, 0.075]),
|
|
228
|
+
'$2\\theta$: ',
|
|
229
|
+
initial=tth_initial_guess)
|
|
230
|
+
cid_update_tth = tth_input.on_submit(new_guess)
|
|
231
|
+
|
|
232
|
+
# Setup "Confirm" button
|
|
233
|
+
confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm')
|
|
234
|
+
confirm_cid = confirm_btn.on_clicked(confirm)
|
|
195
235
|
|
|
196
|
-
|
|
197
|
-
|
|
236
|
+
# Show figure for user interaction
|
|
237
|
+
plt.show()
|
|
198
238
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
239
|
+
# Disconnect all widget callbacks when figure is closed
|
|
240
|
+
tth_input.disconnect(cid_update_tth)
|
|
241
|
+
confirm_btn.disconnect(confirm_cid)
|
|
202
242
|
|
|
243
|
+
# ...and remove the buttons before returning the figure
|
|
244
|
+
tth_input.ax.remove()
|
|
245
|
+
confirm_btn.ax.remove()
|
|
203
246
|
|
|
247
|
+
fig_title[0].set_in_layout(True)
|
|
248
|
+
fig.tight_layout(rect=(0, 0, 1, 0.95))
|
|
204
249
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
250
|
+
if not interactive:
|
|
251
|
+
tth_new_guess = tth_initial_guess
|
|
252
|
+
else:
|
|
253
|
+
try:
|
|
254
|
+
tth_new_guess = float(tth_input.text)
|
|
255
|
+
except:
|
|
256
|
+
fig, tth_new_guess = select_tth_initial_guess(
|
|
257
|
+
x, y, hkls, ds, tth_initial_guess, interactive)
|
|
258
|
+
|
|
259
|
+
return fig, tth_new_guess
|
|
260
|
+
|
|
261
|
+
def select_material_params(x, y, tth, materials=[], interactive=False):
|
|
262
|
+
"""Interactively select the lattice parameters and space group for
|
|
263
|
+
a list of materials. A matplotlib figure will be shown with a plot
|
|
212
264
|
of the reference data (`x` and `y`). The figure will contain
|
|
213
265
|
widgets to add / remove materials and update selections for space
|
|
214
266
|
group number and lattice parameters for each one. The HKLs for the
|
|
215
267
|
materials defined by the widgets' values will be shown over the
|
|
216
268
|
reference data and updated when the widgets' values are
|
|
217
|
-
updated.
|
|
218
|
-
|
|
269
|
+
updated.
|
|
270
|
+
|
|
271
|
+
:param x: MCA channel energies.
|
|
272
|
+
:type x: np.ndarray
|
|
273
|
+
:param y: MCA intensities.
|
|
274
|
+
:type y: np.ndarray
|
|
275
|
+
:param tth: The (calibrated) 2&theta angle.
|
|
276
|
+
:type tth: float
|
|
277
|
+
:param materials: Materials to get HKLs and lattice spacings for.
|
|
278
|
+
:type materials: list[hexrd.material.Material]
|
|
279
|
+
:param interactive: Allows for user interactions, defaults to
|
|
280
|
+
`False`.
|
|
281
|
+
:type interactive: bool, optional
|
|
282
|
+
:return: A saveable matplotlib figure and the selected materials
|
|
283
|
+
for the strain analyses.
|
|
284
|
+
:rtype: matplotlib.figure.Figure,
|
|
285
|
+
list[CHAP.edd.models.MaterialConfig]
|
|
219
286
|
"""
|
|
220
|
-
|
|
287
|
+
# Third party modules
|
|
221
288
|
import matplotlib.pyplot as plt
|
|
222
289
|
from matplotlib.widgets import Button, TextBox
|
|
223
|
-
import numpy as np
|
|
224
|
-
from CHAP.edd.models import MaterialConfig
|
|
225
290
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if isinstance(m, MaterialConfig):
|
|
229
|
-
_materials[i] = m._material
|
|
291
|
+
# Local modules
|
|
292
|
+
from CHAP.edd.models import MaterialConfig
|
|
230
293
|
|
|
231
|
-
|
|
232
|
-
|
|
294
|
+
def change_error_text(error):
|
|
295
|
+
if error_texts:
|
|
296
|
+
error_texts[0].remove()
|
|
297
|
+
error_texts.pop()
|
|
298
|
+
error_texts.append(plt.figtext(*error_pos, error, **error_props))
|
|
233
299
|
|
|
234
300
|
def draw_plot():
|
|
301
|
+
"""Redraw plot of reference data and HKL locations based on
|
|
302
|
+
the `_materials` list on the Matplotlib axes `ax`.
|
|
303
|
+
"""
|
|
235
304
|
ax.clear()
|
|
236
305
|
ax.set_title('Reference Data')
|
|
237
306
|
ax.set_xlabel('MCA channel energy (keV)')
|
|
238
307
|
ax.set_ylabel('MCA intensity (counts)')
|
|
308
|
+
ax.set_xlim(x[0], x[-1])
|
|
239
309
|
ax.plot(x, y)
|
|
240
310
|
for i, material in enumerate(_materials):
|
|
241
311
|
hkls, ds = get_unique_hkls_ds([material])
|
|
242
|
-
E0s =
|
|
312
|
+
E0s = get_peak_locations(ds, tth)
|
|
243
313
|
for hkl, E0 in zip(hkls, E0s):
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
314
|
+
if x[0] <= E0 <= x[-1]:
|
|
315
|
+
ax.axvline(E0, c=f'C{i}', ls='--', lw=1)
|
|
316
|
+
ax.text(E0, 1, str(hkl)[1:-1], c=f'C{i}',
|
|
317
|
+
ha='right', va='top', rotation=90,
|
|
318
|
+
transform=ax.get_xaxis_transform())
|
|
248
319
|
ax.get_figure().canvas.draw()
|
|
249
320
|
|
|
250
|
-
# Confirm & close button
|
|
251
|
-
widget_callbacks = []
|
|
252
|
-
plt.subplots_adjust(bottom=0.1)
|
|
253
|
-
def confirm_selection(event):
|
|
254
|
-
plt.close()
|
|
255
|
-
confirm_button = Button(plt.axes([0.75, 0.015, 0.1, 0.05]), 'Confirm')
|
|
256
|
-
cid_confirm = confirm_button.on_clicked(confirm_selection)
|
|
257
|
-
widget_callbacks.append((confirm_button, cid_confirm))
|
|
258
|
-
|
|
259
|
-
# Widgets to edit materials
|
|
260
|
-
widgets = []
|
|
261
|
-
def update_materials(*args, **kwargs):
|
|
262
|
-
"""Validate input material properties and redraw the plot"""
|
|
263
|
-
materials_ok = True
|
|
264
|
-
for i, (material,
|
|
265
|
-
(name_input, sgnum_input, \
|
|
266
|
-
a_input, b_input, c_input, \
|
|
267
|
-
alpha_input, beta_input, gamma_input)) \
|
|
268
|
-
in enumerate(zip(materials, widgets)):
|
|
269
|
-
name = name_input.text
|
|
270
|
-
print(f'looking at parms for material {name}')
|
|
271
|
-
try:
|
|
272
|
-
sgnum = int(sgnum_input.text)
|
|
273
|
-
sgnum_input.color = '.95'
|
|
274
|
-
_materials[i].sgnum = sgnum
|
|
275
|
-
except:
|
|
276
|
-
sgnum_input.color = 'red'
|
|
277
|
-
materials_ok = False
|
|
278
|
-
lparms = []
|
|
279
|
-
lparms_ok = True
|
|
280
|
-
for lparm_input in (a_input, b_input, c_input,
|
|
281
|
-
alpha_input, beta_input, gamma_input):
|
|
282
|
-
try:
|
|
283
|
-
lparm = float(lparm_input.text)
|
|
284
|
-
lparm_input.color = '.95'
|
|
285
|
-
except:
|
|
286
|
-
lparm_input.color = 'red'
|
|
287
|
-
materials_ok = False
|
|
288
|
-
lparms_ok = False
|
|
289
|
-
else:
|
|
290
|
-
lparms.append(lparm)
|
|
291
|
-
if lparms_ok:
|
|
292
|
-
_materials[i].latticeParameters = lparms
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if materials_ok:
|
|
296
|
-
confirm_button.active = True
|
|
297
|
-
else:
|
|
298
|
-
confirm_button.active = False
|
|
299
|
-
# update items in the `materials` list, then:
|
|
300
|
-
draw_plot()
|
|
301
|
-
|
|
302
321
|
def add_material(*args, material=None, new=True):
|
|
303
|
-
"""
|
|
304
|
-
|
|
322
|
+
"""Callback function for the "Add material" button to add
|
|
323
|
+
a new row of material-property-editing widgets to the figure
|
|
324
|
+
and update the plot with new HKLs.
|
|
305
325
|
"""
|
|
326
|
+
if error_texts:
|
|
327
|
+
error_texts[0].remove()
|
|
328
|
+
error_texts.pop()
|
|
306
329
|
if material is None:
|
|
307
330
|
material = make_material('new_material', 225, 3.0)
|
|
308
331
|
_materials.append(material)
|
|
@@ -310,7 +333,7 @@ def select_material_params(x, y, tth, materials=[]):
|
|
|
310
333
|
material = material._material
|
|
311
334
|
bottom = len(_materials) * 0.075
|
|
312
335
|
plt.subplots_adjust(bottom=bottom + 0.125)
|
|
313
|
-
name_input = TextBox(plt.axes([0.
|
|
336
|
+
name_input = TextBox(plt.axes([0.1, bottom, 0.09, 0.05]),
|
|
314
337
|
'Material: ',
|
|
315
338
|
initial=material.name)
|
|
316
339
|
sgnum_input = TextBox(plt.axes([0.3, bottom, 0.06, 0.05]),
|
|
@@ -337,28 +360,516 @@ def select_material_params(x, y, tth, materials=[]):
|
|
|
337
360
|
widgets.append(
|
|
338
361
|
(name_input, sgnum_input, a_input, b_input, c_input,
|
|
339
362
|
alpha_input, beta_input, gamma_input))
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
363
|
+
widget_callbacks.append(
|
|
364
|
+
[(widget, widget.on_submit(update_materials)) \
|
|
365
|
+
for widget in widgets[-1]])
|
|
366
|
+
draw_plot()
|
|
367
|
+
|
|
368
|
+
def update_materials(*args, **kwargs):
|
|
369
|
+
"""Callback function for the material-property-editing widgets
|
|
370
|
+
button to validate input material properties from widgets,
|
|
371
|
+
update the `_materials` list, and redraw the plot.
|
|
372
|
+
"""
|
|
373
|
+
def set_vals(material_i):
|
|
374
|
+
"""Set all widget values from the `_materials` list for a
|
|
375
|
+
particular material.
|
|
376
|
+
"""
|
|
377
|
+
material = _materials[material_i]
|
|
378
|
+
# Temporarily disconnect widget callbacks
|
|
379
|
+
callbacks = widget_callbacks[material_i+2]
|
|
380
|
+
for widget, callback in callbacks:
|
|
381
|
+
widget.disconnect(callback)
|
|
382
|
+
# Set widget values
|
|
383
|
+
name_input, sgnum_input, \
|
|
384
|
+
a_input, b_input, c_input, \
|
|
385
|
+
alpha_input, beta_input, gamma_input = widgets[material_i]
|
|
386
|
+
name_input.set_val(material.name)
|
|
387
|
+
sgnum_input.set_val(material.sgnum)
|
|
388
|
+
a_input.set_val(material.latticeParameters[0].value)
|
|
389
|
+
b_input.set_val(material.latticeParameters[1].value)
|
|
390
|
+
c_input.set_val(material.latticeParameters[2].value)
|
|
391
|
+
alpha_input.set_val(material.latticeParameters[3].value)
|
|
392
|
+
beta_input.set_val(material.latticeParameters[4].value)
|
|
393
|
+
gamma_input.set_val(material.latticeParameters[5].value)
|
|
394
|
+
# Reconnect widget callbacks
|
|
395
|
+
for i, (w, cb) in enumerate(widget_callbacks[material_i+2]):
|
|
396
|
+
widget_callbacks[material_i+2][i] = (
|
|
397
|
+
w, w.on_submit(update_materials))
|
|
398
|
+
|
|
399
|
+
# Update the _materials list
|
|
400
|
+
for i, (material,
|
|
401
|
+
(name_input, sgnum_input,
|
|
402
|
+
a_input, b_input, c_input,
|
|
403
|
+
alpha_input, beta_input, gamma_input)) \
|
|
404
|
+
in enumerate(zip(_materials, widgets)):
|
|
405
|
+
# Skip if no parameters were changes on this material
|
|
406
|
+
old_material_params = (
|
|
407
|
+
material.name, material.sgnum,
|
|
408
|
+
[material.latticeParameters[i].value for i in range(6)]
|
|
409
|
+
)
|
|
410
|
+
new_material_params = (
|
|
411
|
+
name_input.text, int(sgnum_input.text),
|
|
412
|
+
[float(a_input.text), float(b_input.text), float(c_input.text),
|
|
413
|
+
float(alpha_input.text), float(beta_input.text),
|
|
414
|
+
float(gamma_input.text)]
|
|
415
|
+
)
|
|
416
|
+
if old_material_params == new_material_params:
|
|
417
|
+
continue
|
|
418
|
+
try:
|
|
419
|
+
new_material = make_material(*new_material_params)
|
|
420
|
+
except:
|
|
421
|
+
change_error_text(f'Bad input for {material.name}')
|
|
422
|
+
else:
|
|
423
|
+
_materials[i] = new_material
|
|
424
|
+
finally:
|
|
425
|
+
set_vals(i)
|
|
426
|
+
|
|
427
|
+
# Redraw reference data plot
|
|
343
428
|
draw_plot()
|
|
344
429
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
430
|
+
def confirm(event):
|
|
431
|
+
"""Callback function for the "Confirm" button."""
|
|
432
|
+
if error_texts:
|
|
433
|
+
error_texts[0].remove()
|
|
434
|
+
error_texts.pop()
|
|
435
|
+
plt.close()
|
|
436
|
+
|
|
437
|
+
widgets = []
|
|
438
|
+
widget_callbacks = []
|
|
439
|
+
error_texts = []
|
|
440
|
+
|
|
441
|
+
error_pos = (0.5, 0.95)
|
|
442
|
+
error_props = {'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'}
|
|
443
|
+
|
|
444
|
+
_materials = deepcopy(materials)
|
|
445
|
+
for i, m in enumerate(_materials):
|
|
446
|
+
if isinstance(m, MaterialConfig):
|
|
447
|
+
_materials[i] = m._material
|
|
448
|
+
|
|
449
|
+
# Set up plot of reference data
|
|
450
|
+
fig, ax = plt.subplots(figsize=(11, 8.5))
|
|
451
|
+
|
|
452
|
+
if interactive:
|
|
453
|
+
|
|
454
|
+
plt.subplots_adjust(bottom=0.1)
|
|
455
|
+
|
|
456
|
+
# Setup "Add material" button
|
|
457
|
+
add_material_btn = Button(
|
|
458
|
+
plt.axes([0.125, 0.015, 0.1, 0.05]), 'Add material')
|
|
459
|
+
add_material_cid = add_material_btn.on_clicked(add_material)
|
|
460
|
+
widget_callbacks.append([(add_material_btn, add_material_cid)])
|
|
461
|
+
|
|
462
|
+
# Setup "Confirm" button
|
|
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)])
|
|
466
|
+
|
|
467
|
+
# Setup material-property-editing buttons for each material
|
|
468
|
+
for material in _materials:
|
|
469
|
+
add_material(material=material)
|
|
470
|
+
|
|
471
|
+
# Show figure for user interaction
|
|
472
|
+
plt.show()
|
|
473
|
+
|
|
474
|
+
# Disconnect all widget callbacks when figure is closed
|
|
475
|
+
# and remove the buttons before returning the figure
|
|
476
|
+
for group in widget_callbacks:
|
|
477
|
+
for widget, callback in group:
|
|
478
|
+
widget.disconnect(callback)
|
|
479
|
+
widget.ax.remove()
|
|
480
|
+
|
|
481
|
+
fig.tight_layout()
|
|
482
|
+
|
|
483
|
+
new_materials = [
|
|
484
|
+
MaterialConfig(
|
|
485
|
+
material_name=m.name, sgnum=m.sgnum,
|
|
486
|
+
lattice_parameters=[
|
|
487
|
+
m.latticeParameters[i].value for i in range(6)])
|
|
488
|
+
for m in _materials]
|
|
489
|
+
|
|
490
|
+
return fig, new_materials
|
|
491
|
+
|
|
492
|
+
def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=[],
|
|
493
|
+
preselected_hkl_indices=[], detector_name=None, ref_map=None,
|
|
494
|
+
flux_energy_range=None, calibration_bin_ranges=None,
|
|
495
|
+
interactive=False):
|
|
496
|
+
"""Return a matplotlib figure to indicate data ranges and HKLs to
|
|
497
|
+
include for fitting in EDD Ceria calibration and/or strain
|
|
498
|
+
analysis.
|
|
499
|
+
|
|
500
|
+
:param x: MCA channel energies.
|
|
501
|
+
:type x: np.ndarray
|
|
502
|
+
:param y: MCA intensities.
|
|
503
|
+
:type y: np.ndarray
|
|
504
|
+
:param hkls: Avaliable Unique HKL values to fit peaks for in the
|
|
505
|
+
calibration routine.
|
|
506
|
+
:type hkls: list[list[int]]
|
|
507
|
+
:param ds: Lattice spacings associated with the unique HKL indices
|
|
508
|
+
in angstroms.
|
|
509
|
+
:type ds: list[float]
|
|
510
|
+
:param tth: The (calibrated) 2&theta angle.
|
|
511
|
+
:type tth: float
|
|
512
|
+
:param preselected_bin_ranges: Preselected MCA channel index ranges
|
|
513
|
+
whose data should be included after applying a mask,
|
|
514
|
+
defaults to `[]`
|
|
515
|
+
:type preselected_bin_ranges: list[list[int]], optional
|
|
516
|
+
:param preselected_hkl_indices: Preselected unique HKL indices to
|
|
517
|
+
fit peaks for in the calibration routine, defaults to `[]`.
|
|
518
|
+
:type preselected_hkl_indices: list[int], optional
|
|
519
|
+
:param detector_name: Name of the MCA detector element.
|
|
520
|
+
:type detector_name: str, optional
|
|
521
|
+
:param ref_map: Reference map of MCA intensities to show underneath
|
|
522
|
+
the interactive plot.
|
|
523
|
+
:type ref_map: np.ndarray, optional
|
|
524
|
+
:param flux_energy_range: Energy range in eV in the flux file
|
|
525
|
+
containing station beam energy in eV versus flux
|
|
526
|
+
:type flux_energy_range: tuple(float, float), optional
|
|
527
|
+
:param calibration_bin_ranges: MCA channel index ranges included
|
|
528
|
+
in the detector calibration.
|
|
529
|
+
:type calibration_bin_ranges: list[[int, int]], optional
|
|
530
|
+
:param interactive: Allows for user interactions, defaults to
|
|
531
|
+
`False`.
|
|
532
|
+
:type interactive: bool, optional
|
|
533
|
+
:return: A saveable matplotlib figure, the list of selected data
|
|
534
|
+
index ranges to include, and the list of HKL indices to
|
|
535
|
+
include
|
|
536
|
+
:rtype: matplotlib.figure.Figure, list[list[int]], list[int]
|
|
537
|
+
"""
|
|
538
|
+
# Third party modules
|
|
539
|
+
import matplotlib.lines as mlines
|
|
540
|
+
from matplotlib.patches import Patch
|
|
541
|
+
import matplotlib.pyplot as plt
|
|
542
|
+
from matplotlib.widgets import Button, SpanSelector
|
|
543
|
+
|
|
544
|
+
# Local modules
|
|
545
|
+
from CHAP.utils.general import (
|
|
546
|
+
get_consecutive_int_range,
|
|
547
|
+
index_nearest_down,
|
|
548
|
+
index_nearest_upp,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
def change_fig_title(title):
|
|
552
|
+
if fig_title:
|
|
553
|
+
fig_title[0].remove()
|
|
554
|
+
fig_title.pop()
|
|
555
|
+
fig_title.append(plt.figtext(*title_pos, title, **title_props))
|
|
556
|
+
|
|
557
|
+
def change_error_text(error):
|
|
558
|
+
if error_texts:
|
|
559
|
+
error_texts[0].remove()
|
|
560
|
+
error_texts.pop()
|
|
561
|
+
error_texts.append(plt.figtext(*error_pos, error, **error_props))
|
|
562
|
+
|
|
563
|
+
def hkl_locations_in_any_span(hkl_index):
|
|
564
|
+
"""Return the index of the span where the location of a specific
|
|
565
|
+
HKL resides. Return(-1 if outside any span."""
|
|
566
|
+
if hkl_index < 0 or hkl_index>= len(hkl_locations):
|
|
567
|
+
return -1
|
|
568
|
+
for i, span in enumerate(spans):
|
|
569
|
+
if (span.extents[0] <= hkl_locations[hkl_index] and
|
|
570
|
+
span.extents[1] >= hkl_locations[hkl_index]):
|
|
571
|
+
return i
|
|
572
|
+
return -1
|
|
573
|
+
|
|
574
|
+
def on_span_select(xmin, xmax):
|
|
575
|
+
"""Callback function for the SpanSelector widget."""
|
|
576
|
+
removed_hkls = False
|
|
577
|
+
if not init_flag[0]:
|
|
578
|
+
for hkl_index in deepcopy(selected_hkl_indices):
|
|
579
|
+
if hkl_locations_in_any_span(hkl_index) < 0:
|
|
580
|
+
hkl_vlines[hkl_index].set(**excluded_hkl_props)
|
|
581
|
+
selected_hkl_indices.remove(hkl_index)
|
|
582
|
+
removed_hkls = True
|
|
583
|
+
combined_spans = False
|
|
584
|
+
combined_spans_test = True
|
|
585
|
+
while combined_spans_test:
|
|
586
|
+
combined_spans_test = False
|
|
587
|
+
for i, span1 in enumerate(spans):
|
|
588
|
+
for span2 in reversed(spans[i+1:]):
|
|
589
|
+
if (span1.extents[1] >= span2.extents[0]
|
|
590
|
+
and span1.extents[0] <= span2.extents[1]):
|
|
591
|
+
span1.extents = (
|
|
592
|
+
min(span1.extents[0], span2.extents[0]),
|
|
593
|
+
max(span1.extents[1], span2.extents[1]))
|
|
594
|
+
span2.set_visible(False)
|
|
595
|
+
spans.remove(span2)
|
|
596
|
+
combined_spans = True
|
|
597
|
+
combined_spans_test = True
|
|
598
|
+
break
|
|
599
|
+
if combined_spans_test:
|
|
600
|
+
break
|
|
601
|
+
if flux_energy_range is not None:
|
|
602
|
+
for span in spans:
|
|
603
|
+
min_ = max(span.extents[0], min_x)
|
|
604
|
+
max_ = min(span.extents[1], max_x)
|
|
605
|
+
span.extents = (min_, max_)
|
|
606
|
+
added_hkls = False
|
|
607
|
+
for hkl_index in range(len(hkl_locations)):
|
|
608
|
+
if (hkl_index not in selected_hkl_indices
|
|
609
|
+
and hkl_locations_in_any_span(hkl_index) >= 0):
|
|
610
|
+
hkl_vlines[hkl_index].set(**included_hkl_props)
|
|
611
|
+
selected_hkl_indices.append(hkl_index)
|
|
612
|
+
added_hkls = True
|
|
613
|
+
if combined_spans:
|
|
614
|
+
if added_hkls or removed_hkls:
|
|
615
|
+
change_error_text(
|
|
616
|
+
'Combined overlapping spans and selected only HKL(s) '
|
|
617
|
+
'inside the selected energy mask')
|
|
618
|
+
else:
|
|
619
|
+
change_error_text(
|
|
620
|
+
'Combined overlapping spans in the selected energy mask')
|
|
621
|
+
elif added_hkls and removed_hkls:
|
|
622
|
+
change_error_text(
|
|
623
|
+
'Adjusted the selected HKL(s) to match the selected '
|
|
624
|
+
'energy mask')
|
|
625
|
+
elif added_hkls:
|
|
626
|
+
change_error_text(
|
|
627
|
+
'Added HKL(s) to match the selected energy mask')
|
|
628
|
+
elif removed_hkls:
|
|
629
|
+
change_error_text(
|
|
630
|
+
'Removed HKL(s) outside the selected energy mask')
|
|
631
|
+
plt.draw()
|
|
632
|
+
|
|
633
|
+
def add_span(event, xrange_init=None):
|
|
634
|
+
"""Callback function for the "Add span" button."""
|
|
635
|
+
spans.append(
|
|
636
|
+
SpanSelector(
|
|
637
|
+
ax, on_span_select, 'horizontal', props=included_data_props,
|
|
638
|
+
useblit=True, interactive=interactive, drag_from_anywhere=True,
|
|
639
|
+
ignore_event_outside=True, grab_range=5))
|
|
640
|
+
if xrange_init is None:
|
|
641
|
+
xmin_init = min_x
|
|
642
|
+
xmax_init = 0.5*(min_x + hkl_locations[0])
|
|
643
|
+
else:
|
|
644
|
+
xmin_init = max(min_x, xrange_init[0])
|
|
645
|
+
xmax_init = min(max_x, xrange_init[1])
|
|
646
|
+
spans[-1]._selection_completed = True
|
|
647
|
+
spans[-1].extents = (xmin_init, xmax_init)
|
|
648
|
+
spans[-1].onselect(xmin_init, xmax_init)
|
|
649
|
+
|
|
650
|
+
def pick_hkl(event):
|
|
651
|
+
"""The "onpick" callback function."""
|
|
652
|
+
try:
|
|
653
|
+
hkl_index = hkl_vlines.index(event.artist)
|
|
654
|
+
except:
|
|
655
|
+
pass
|
|
656
|
+
else:
|
|
657
|
+
hkl_vline = event.artist
|
|
658
|
+
if hkl_index in deepcopy(selected_hkl_indices):
|
|
659
|
+
hkl_vline.set(**excluded_hkl_props)
|
|
660
|
+
selected_hkl_indices.remove(hkl_index)
|
|
661
|
+
span = spans[hkl_locations_in_any_span(hkl_index)]
|
|
662
|
+
span_next_hkl_index = hkl_locations_in_any_span(hkl_index+1)
|
|
663
|
+
span_prev_hkl_index = hkl_locations_in_any_span(hkl_index-1)
|
|
664
|
+
if span_next_hkl_index < 0 and span_prev_hkl_index < 0:
|
|
665
|
+
span.set_visible(False)
|
|
666
|
+
spans.remove(span)
|
|
667
|
+
elif span_next_hkl_index < 0:
|
|
668
|
+
span.extents = (
|
|
669
|
+
span.extents[0],
|
|
670
|
+
0.5*(hkl_locations[hkl_index-1]
|
|
671
|
+
+ hkl_locations[hkl_index]))
|
|
672
|
+
elif span_prev_hkl_index < 0:
|
|
673
|
+
span.extents = (
|
|
674
|
+
0.5*(hkl_locations[hkl_index]
|
|
675
|
+
+ hkl_locations[hkl_index+1]),
|
|
676
|
+
span.extents[1])
|
|
677
|
+
else:
|
|
678
|
+
xrange_init = [
|
|
679
|
+
0.5*(hkl_locations[hkl_index]
|
|
680
|
+
+ hkl_locations[hkl_index+1]),
|
|
681
|
+
span.extents[1]]
|
|
682
|
+
span.extents = (
|
|
683
|
+
span.extents[0],
|
|
684
|
+
0.5*(hkl_locations[hkl_index-1]
|
|
685
|
+
+ hkl_locations[hkl_index]))
|
|
686
|
+
add_span(None, xrange_init=xrange_init)
|
|
687
|
+
change_error_text(
|
|
688
|
+
f'Adjusted the selected energy mask to reflect the '
|
|
689
|
+
'removed HKL')
|
|
690
|
+
else:
|
|
691
|
+
change_error_text(
|
|
692
|
+
f'Selected HKL is outside any current span, '
|
|
693
|
+
'extend or add spans before adding this value')
|
|
694
|
+
plt.draw()
|
|
695
|
+
|
|
696
|
+
def reset(event):
|
|
697
|
+
"""Callback function for the "Confirm" button."""
|
|
698
|
+
for hkl_index in deepcopy(selected_hkl_indices):
|
|
699
|
+
hkl_vlines[hkl_index].set(**excluded_hkl_props)
|
|
700
|
+
selected_hkl_indices.remove(hkl_index)
|
|
701
|
+
for span in reversed(spans):
|
|
702
|
+
span.set_visible(False)
|
|
703
|
+
spans.remove(span)
|
|
704
|
+
plt.draw()
|
|
705
|
+
|
|
706
|
+
def confirm(event):
|
|
707
|
+
"""Callback function for the "Confirm" button."""
|
|
708
|
+
if not len(spans) or len(selected_hkl_indices) < 2:
|
|
709
|
+
change_error_text('Select at least one span and two HKLs')
|
|
710
|
+
plt.draw()
|
|
711
|
+
else:
|
|
712
|
+
if error_texts:
|
|
713
|
+
error_texts[0].remove()
|
|
714
|
+
error_texts.pop()
|
|
715
|
+
if detector_name is None:
|
|
716
|
+
change_fig_title('Selected data and HKLs used in fitting')
|
|
717
|
+
else:
|
|
718
|
+
change_fig_title(
|
|
719
|
+
f'Selected data and HKLs used in fitting {detector_name}')
|
|
720
|
+
plt.close()
|
|
721
|
+
|
|
722
|
+
selected_hkl_indices = preselected_hkl_indices
|
|
723
|
+
spans = []
|
|
724
|
+
hkl_vlines = []
|
|
725
|
+
fig_title = []
|
|
726
|
+
error_texts = []
|
|
727
|
+
|
|
728
|
+
hkl_locations = [loc for loc in get_peak_locations(ds, tth)
|
|
729
|
+
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
|
+
if selected_hkl_indices and not preselected_bin_ranges:
|
|
785
|
+
index_ranges = get_consecutive_int_range(selected_hkl_indices)
|
|
786
|
+
for index_range in index_ranges:
|
|
787
|
+
i = index_range[0]
|
|
788
|
+
if i:
|
|
789
|
+
min_ = 0.5*(hkl_locations[i-1] + hkl_locations[i])
|
|
790
|
+
else:
|
|
791
|
+
min_ = 0.5*(min_x + hkl_locations[i])
|
|
792
|
+
j = index_range[1]
|
|
793
|
+
if j < len(hkl_locations)-1:
|
|
794
|
+
max_ = 0.5*(hkl_locations[j] + hkl_locations[j+1])
|
|
795
|
+
else:
|
|
796
|
+
max_ = 0.5*(hkl_locations[j] + max_x)
|
|
797
|
+
preselected_bin_ranges.append(
|
|
798
|
+
[index_nearest_upp(x, min_), index_nearest_down(x, max_)])
|
|
799
|
+
|
|
800
|
+
for i, (loc, lbl) in enumerate(zip(hkl_locations, hkl_labels)):
|
|
801
|
+
nearest_index = np.searchsorted(x, loc)
|
|
802
|
+
if i in selected_hkl_indices:
|
|
803
|
+
hkl_vline = ax.axvline(loc, **included_hkl_props)
|
|
804
|
+
else:
|
|
805
|
+
hkl_vline = ax.axvline(loc, **excluded_hkl_props)
|
|
806
|
+
ax.text(loc, 1, lbl, ha='right', va='top', rotation=90,
|
|
807
|
+
transform=ax.get_xaxis_transform())
|
|
808
|
+
hkl_vlines.append(hkl_vline)
|
|
809
|
+
|
|
810
|
+
init_flag = [True]
|
|
811
|
+
for bin_range in preselected_bin_ranges:
|
|
812
|
+
add_span(None, xrange_init=x[bin_range])
|
|
813
|
+
init_flag = [False]
|
|
814
|
+
|
|
815
|
+
if not interactive:
|
|
816
|
+
|
|
817
|
+
if detector_name is None:
|
|
818
|
+
change_fig_title('Selected data and HKLs used in fitting')
|
|
819
|
+
else:
|
|
820
|
+
change_fig_title(
|
|
821
|
+
f'Selected data and HKLs used in fitting {detector_name}')
|
|
822
|
+
|
|
823
|
+
else:
|
|
824
|
+
|
|
825
|
+
if detector_name is None:
|
|
826
|
+
change_fig_title('Select data and HKLs to use in fitting')
|
|
827
|
+
else:
|
|
828
|
+
change_fig_title(
|
|
829
|
+
f'Select data and HKLs to use in fitting {detector_name}')
|
|
830
|
+
fig.subplots_adjust(bottom=0.2)
|
|
831
|
+
|
|
832
|
+
# Setup "Add span" button
|
|
833
|
+
add_span_btn = Button(plt.axes([0.125, 0.05, 0.15, 0.075]), 'Add span')
|
|
834
|
+
add_span_cid = add_span_btn.on_clicked(add_span)
|
|
835
|
+
|
|
836
|
+
for vline in hkl_vlines:
|
|
837
|
+
vline.set_picker(5)
|
|
838
|
+
pick_hkl_cid = fig.canvas.mpl_connect('pick_event', pick_hkl)
|
|
839
|
+
|
|
840
|
+
# Setup "Reset" button
|
|
841
|
+
reset_btn = Button(plt.axes([0.4375, 0.05, 0.15, 0.075]), 'Reset')
|
|
842
|
+
reset_cid = reset_btn.on_clicked(reset)
|
|
843
|
+
|
|
844
|
+
# Setup "Confirm" button
|
|
845
|
+
confirm_btn = Button(plt.axes([0.75, 0.05, 0.15, 0.075]), 'Confirm')
|
|
846
|
+
confirm_cid = confirm_btn.on_clicked(confirm)
|
|
847
|
+
|
|
848
|
+
# Show figure for user interaction
|
|
849
|
+
plt.show()
|
|
850
|
+
|
|
851
|
+
# Disconnect all widget callbacks when figure is closed
|
|
852
|
+
add_span_btn.disconnect(add_span_cid)
|
|
853
|
+
fig.canvas.mpl_disconnect(pick_hkl_cid)
|
|
854
|
+
reset_btn.disconnect(reset_cid)
|
|
855
|
+
confirm_btn.disconnect(confirm_cid)
|
|
856
|
+
|
|
857
|
+
# ...and remove the buttons before returning the figure
|
|
858
|
+
add_span_btn.ax.remove()
|
|
859
|
+
confirm_btn.ax.remove()
|
|
860
|
+
reset_btn.ax.remove()
|
|
861
|
+
plt.subplots_adjust(bottom=0.0)
|
|
862
|
+
|
|
863
|
+
selected_bin_ranges = [np.searchsorted(x, span.extents).tolist()
|
|
864
|
+
for span in spans]
|
|
865
|
+
if not selected_bin_ranges:
|
|
866
|
+
selected_bin_ranges = None
|
|
867
|
+
if selected_hkl_indices:
|
|
868
|
+
selected_hkl_indices = sorted(selected_hkl_indices)
|
|
869
|
+
else:
|
|
870
|
+
selected_hkl_indices = None
|
|
871
|
+
|
|
872
|
+
fig_title[0].set_in_layout(True)
|
|
873
|
+
fig.tight_layout(rect=(0, 0, 1, 0.95))
|
|
874
|
+
|
|
875
|
+
return fig, selected_bin_ranges, selected_hkl_indices
|