batplot 1.8.4__py3-none-any.whl → 1.8.11__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.
- batplot/__init__.py +1 -1
- batplot/args.py +22 -4
- batplot/batch.py +12 -0
- batplot/batplot.py +383 -132
- batplot/converters.py +171 -122
- batplot/cpc_interactive.py +319 -161
- batplot/data/USER_MANUAL.md +49 -0
- batplot/electrochem_interactive.py +120 -80
- batplot/interactive.py +1766 -81
- batplot/modes.py +12 -11
- batplot/operando.py +22 -0
- batplot/operando_ec_interactive.py +390 -16
- batplot/session.py +85 -9
- batplot/style.py +198 -21
- {batplot-1.8.4.dist-info → batplot-1.8.11.dist-info}/METADATA +1 -1
- {batplot-1.8.4.dist-info → batplot-1.8.11.dist-info}/RECORD +20 -20
- {batplot-1.8.4.dist-info → batplot-1.8.11.dist-info}/WHEEL +1 -1
- {batplot-1.8.4.dist-info → batplot-1.8.11.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.8.4.dist-info → batplot-1.8.11.dist-info}/entry_points.txt +0 -0
- {batplot-1.8.4.dist-info → batplot-1.8.11.dist-info}/top_level.txt +0 -0
batplot/converters.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides functions to convert X-ray diffraction data between
|
|
4
4
|
different representations, primarily from angle-based (2θ) to momentum
|
|
5
|
-
transfer (Q) space.
|
|
5
|
+
transfer (Q) space, and between different wavelengths.
|
|
6
6
|
|
|
7
7
|
WHY CONVERT BETWEEN 2θ AND Q?
|
|
8
8
|
-----------------------------
|
|
@@ -24,8 +24,8 @@ Converting to Q-space is useful for:
|
|
|
24
24
|
- Direct comparison with theoretical calculations (often in Q-space)
|
|
25
25
|
- Combining datasets from different experiments
|
|
26
26
|
|
|
27
|
-
CONVERSION
|
|
28
|
-
|
|
27
|
+
CONVERSION FORMULAS:
|
|
28
|
+
-------------------
|
|
29
29
|
The conversion uses Bragg's law:
|
|
30
30
|
Q = (4π sin(θ)) / λ
|
|
31
31
|
|
|
@@ -34,6 +34,9 @@ Where:
|
|
|
34
34
|
- θ: Half of the diffraction angle (2θ/2) in radians
|
|
35
35
|
- λ: X-ray wavelength in Angstroms
|
|
36
36
|
|
|
37
|
+
To convert from Q back to 2θ:
|
|
38
|
+
2θ = 2 × arcsin(Q × λ / (4π)) × (180/π)
|
|
39
|
+
|
|
37
40
|
Physical meaning:
|
|
38
41
|
- Q represents the momentum transferred from X-ray to sample
|
|
39
42
|
- Higher Q = smaller d-spacing (shorter distances in crystal)
|
|
@@ -46,159 +49,205 @@ import os
|
|
|
46
49
|
import numpy as np
|
|
47
50
|
|
|
48
51
|
|
|
49
|
-
def
|
|
52
|
+
def convert_xrd_data(filenames, from_param: str, to_param: str):
|
|
50
53
|
"""
|
|
51
|
-
Convert
|
|
52
|
-
|
|
53
|
-
HOW IT WORKS:
|
|
54
|
-
------------
|
|
55
|
-
This function reads XRD data files that have 2θ (two-theta) angles as the
|
|
56
|
-
x-axis and converts them to Q-space (momentum transfer). The conversion
|
|
57
|
-
process:
|
|
54
|
+
Convert XRD data files between different representations.
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
5. Save output as .qye file (Q, intensity, [error])
|
|
56
|
+
This function handles three conversion modes:
|
|
57
|
+
1. Wavelength-to-wavelength: Convert 2θ values from one wavelength to another
|
|
58
|
+
2. Wavelength-to-Q: Convert 2θ (with given wavelength) to Q space
|
|
59
|
+
3. Q-to-wavelength: Convert Q space to 2θ (with given wavelength)
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
HOW IT WORKS:
|
|
66
62
|
------------
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- Column 3 (optional): Error bars (e.g., 5, 7, 10, ...)
|
|
71
|
-
|
|
72
|
-
OUTPUT FORMAT:
|
|
73
|
-
-------------
|
|
74
|
-
Saved .qye file contains:
|
|
75
|
-
- Column 1: Q values in Å⁻¹ (momentum transfer)
|
|
76
|
-
- Column 2: Intensity values (unchanged)
|
|
77
|
-
- Column 3 (if present): Error bars (unchanged)
|
|
78
|
-
- Header comment: Documents conversion parameters
|
|
63
|
+
The function reads input files, determines the conversion type based on
|
|
64
|
+
the from/to parameters, applies the appropriate conversion formula, and
|
|
65
|
+
saves the converted data to a new 'converted' subfolder.
|
|
79
66
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
67
|
+
CONVERSION MODES:
|
|
68
|
+
----------------
|
|
69
|
+
1. Wavelength-to-wavelength (e.g., --convert 1.54 0.25):
|
|
70
|
+
- Input: 2θ values measured with wavelength1
|
|
71
|
+
- Process: Convert 2θ → Q using wavelength1, then Q → 2θ using wavelength2
|
|
72
|
+
- Output: 2θ values for wavelength2
|
|
73
|
+
|
|
74
|
+
2. Wavelength-to-Q (e.g., --convert 1.54 q):
|
|
75
|
+
- Input: 2θ values measured with given wavelength
|
|
76
|
+
- Process: Convert 2θ → Q using the wavelength
|
|
77
|
+
- Output: Q values (Å⁻¹)
|
|
78
|
+
|
|
79
|
+
3. Q-to-wavelength (e.g., --convert q 1.54):
|
|
80
|
+
- Input: Q values (Å⁻¹)
|
|
81
|
+
- Process: Convert Q → 2θ using the given wavelength
|
|
82
|
+
- Output: 2θ values for the given wavelength
|
|
88
83
|
|
|
89
84
|
Args:
|
|
90
85
|
filenames: List of file paths to convert (e.g., ['data.xy', 'pattern.xye'])
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
- 1.5406 for Cu Kα (most common)
|
|
94
|
-
- 0.7093 for Mo Kα
|
|
95
|
-
- 0.6199 for synchrotron
|
|
86
|
+
from_param: Source parameter - either a wavelength (float as string) or 'q'
|
|
87
|
+
to_param: Target parameter - either a wavelength (float as string) or 'q'
|
|
96
88
|
|
|
97
89
|
Output:
|
|
98
|
-
Creates
|
|
99
|
-
|
|
90
|
+
Creates converted files in a 'converted' subfolder within the directory
|
|
91
|
+
containing the input files. Files keep their original names but may have
|
|
92
|
+
different extensions (.qye for Q-space, original extension for 2θ).
|
|
100
93
|
|
|
101
94
|
Example:
|
|
102
|
-
>>> # Convert Cu Kα
|
|
103
|
-
>>>
|
|
104
|
-
Saved pattern.
|
|
95
|
+
>>> # Convert 2θ from Cu Kα (1.54 Å) to Mo Kα (0.709 Å)
|
|
96
|
+
>>> convert_xrd_data(['pattern.xy'], '1.54', '0.709')
|
|
97
|
+
Saved converted/pattern.xy
|
|
98
|
+
|
|
99
|
+
>>> # Convert 2θ to Q space
|
|
100
|
+
>>> convert_xrd_data(['pattern.xy'], '1.54', 'q')
|
|
101
|
+
Saved converted/pattern.qye
|
|
105
102
|
|
|
106
|
-
>>> # Convert
|
|
107
|
-
>>>
|
|
108
|
-
Saved
|
|
109
|
-
Saved scan2.qye
|
|
103
|
+
>>> # Convert Q to 2θ
|
|
104
|
+
>>> convert_xrd_data(['pattern.qye'], 'q', '1.54')
|
|
105
|
+
Saved converted/pattern.xy
|
|
110
106
|
"""
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
# Parse parameters
|
|
108
|
+
try:
|
|
109
|
+
from_is_q = (from_param.lower() == 'q')
|
|
110
|
+
to_is_q = (to_param.lower() == 'q')
|
|
111
|
+
|
|
112
|
+
if from_is_q:
|
|
113
|
+
from_wl = None
|
|
114
|
+
else:
|
|
115
|
+
from_wl = float(from_param)
|
|
116
|
+
|
|
117
|
+
if to_is_q:
|
|
118
|
+
to_wl = None
|
|
119
|
+
else:
|
|
120
|
+
to_wl = float(to_param)
|
|
121
|
+
except ValueError:
|
|
122
|
+
print(f"Error: Invalid conversion parameters. Expected wavelengths (numbers) or 'q', got '{from_param}' and '{to_param}'")
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
# Determine conversion type
|
|
126
|
+
if not from_is_q and not to_is_q:
|
|
127
|
+
# Wavelength-to-wavelength conversion
|
|
128
|
+
conversion_type = "wavelength_to_wavelength"
|
|
129
|
+
elif not from_is_q and to_is_q:
|
|
130
|
+
# Wavelength-to-Q conversion
|
|
131
|
+
conversion_type = "wavelength_to_q"
|
|
132
|
+
elif from_is_q and not to_is_q:
|
|
133
|
+
# Q-to-wavelength conversion
|
|
134
|
+
conversion_type = "q_to_wavelength"
|
|
135
|
+
else:
|
|
136
|
+
print("Error: Cannot convert Q to Q (no conversion needed)")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
# Process each file
|
|
117
140
|
for fname in filenames:
|
|
118
|
-
#
|
|
141
|
+
# Validate file exists
|
|
119
142
|
if not os.path.isfile(fname):
|
|
120
143
|
print(f"File not found: {fname}")
|
|
121
|
-
continue
|
|
144
|
+
continue
|
|
122
145
|
|
|
123
|
-
#
|
|
124
|
-
# np.loadtxt() reads numeric data, skipping lines starting with '#'
|
|
125
|
-
# This handles comment lines in data files
|
|
146
|
+
# Read data from file
|
|
126
147
|
try:
|
|
127
148
|
data = np.loadtxt(fname, comments="#")
|
|
128
149
|
except Exception as e:
|
|
129
150
|
print(f"Error reading {fname}: {e}")
|
|
130
|
-
continue
|
|
151
|
+
continue
|
|
131
152
|
|
|
132
|
-
#
|
|
133
|
-
# Some files might have only one row, which numpy reads as 1D array
|
|
134
|
-
# We reshape to 2D so indexing works consistently
|
|
153
|
+
# Ensure data is 2D array
|
|
135
154
|
if data.ndim == 1:
|
|
136
|
-
data = data.reshape(1, -1)
|
|
155
|
+
data = data.reshape(1, -1)
|
|
137
156
|
|
|
138
|
-
#
|
|
139
|
-
# Need at least 2 columns: x (2θ) and y (intensity)
|
|
157
|
+
# Validate data format
|
|
140
158
|
if data.shape[1] < 2:
|
|
141
159
|
print(f"Invalid data format in {fname}: need at least 2 columns (x, y)")
|
|
142
160
|
continue
|
|
143
161
|
|
|
144
|
-
#
|
|
145
|
-
x = data[:, 0] #
|
|
146
|
-
y = data[:, 1] # Intensity values
|
|
147
|
-
e = data[:, 2] if data.shape[1] >= 3 else None # Error bars (
|
|
148
|
-
|
|
149
|
-
# ====================================================================
|
|
150
|
-
# STEP 6: CONVERT 2θ TO Q
|
|
151
|
-
# ====================================================================
|
|
152
|
-
# This is the core conversion using Bragg's law.
|
|
153
|
-
#
|
|
154
|
-
# Mathematical steps:
|
|
155
|
-
# 1. Get θ (half-angle): θ = 2θ / 2
|
|
156
|
-
# Example: 2θ = 20° → θ = 10°
|
|
157
|
-
#
|
|
158
|
-
# 2. Convert to radians: θ_rad = θ × (π/180)
|
|
159
|
-
# Example: θ = 10° → θ_rad = 0.1745 radians
|
|
160
|
-
# (NumPy's np.radians() does this conversion)
|
|
161
|
-
#
|
|
162
|
-
# 3. Apply conversion formula: Q = 4π sin(θ_rad) / λ
|
|
163
|
-
# Example: θ_rad = 0.1745, λ = 1.5406 Å
|
|
164
|
-
# Q = 4π sin(0.1745) / 1.5406 = 1.42 Å⁻¹
|
|
165
|
-
#
|
|
166
|
-
# Why sin(θ)?
|
|
167
|
-
# The momentum transfer Q is proportional to sin(θ), not θ itself.
|
|
168
|
-
# This is because diffraction follows a sine relationship (Bragg's law).
|
|
169
|
-
# ====================================================================
|
|
162
|
+
# Extract columns
|
|
163
|
+
x = data[:, 0] # X values (2θ or Q)
|
|
164
|
+
y = data[:, 1] # Intensity values
|
|
165
|
+
e = data[:, 2] if data.shape[1] >= 3 else None # Error bars (optional)
|
|
170
166
|
|
|
171
|
-
#
|
|
172
|
-
|
|
173
|
-
|
|
167
|
+
# Perform conversion based on type
|
|
168
|
+
if conversion_type == "wavelength_to_wavelength":
|
|
169
|
+
# Step 1: Convert 2θ (from_wl) → Q
|
|
170
|
+
theta_rad = np.radians(x / 2) # Convert 2θ to θ in radians
|
|
171
|
+
q = 4 * np.pi * np.sin(theta_rad) / from_wl
|
|
172
|
+
|
|
173
|
+
# Step 2: Convert Q → 2θ (to_wl)
|
|
174
|
+
# Q = 4π sin(θ) / λ, so sin(θ) = Q × λ / (4π)
|
|
175
|
+
# θ = arcsin(Q × λ / (4π))
|
|
176
|
+
# 2θ = 2 × θ × (180/π)
|
|
177
|
+
sin_theta = q * to_wl / (4 * np.pi)
|
|
178
|
+
# Clamp sin_theta to valid range [-1, 1] to avoid domain errors
|
|
179
|
+
sin_theta = np.clip(sin_theta, -1.0, 1.0)
|
|
180
|
+
theta_rad_new = np.arcsin(sin_theta)
|
|
181
|
+
x_new = 2 * np.degrees(theta_rad_new) # Convert to 2θ in degrees
|
|
182
|
+
output_ext = os.path.splitext(fname)[1] # Keep original extension
|
|
183
|
+
|
|
184
|
+
elif conversion_type == "wavelength_to_q":
|
|
185
|
+
# Convert 2θ → Q
|
|
186
|
+
theta_rad = np.radians(x / 2) # Convert 2θ to θ in radians
|
|
187
|
+
x_new = 4 * np.pi * np.sin(theta_rad) / from_wl
|
|
188
|
+
output_ext = ".qye"
|
|
189
|
+
|
|
190
|
+
elif conversion_type == "q_to_wavelength":
|
|
191
|
+
# Convert Q → 2θ
|
|
192
|
+
# Q = 4π sin(θ) / λ, so sin(θ) = Q × λ / (4π)
|
|
193
|
+
sin_theta = x * to_wl / (4 * np.pi)
|
|
194
|
+
# Clamp sin_theta to valid range [-1, 1] to avoid domain errors
|
|
195
|
+
sin_theta = np.clip(sin_theta, -1.0, 1.0)
|
|
196
|
+
theta_rad = np.arcsin(sin_theta)
|
|
197
|
+
x_new = 2 * np.degrees(theta_rad) # Convert to 2θ in degrees
|
|
198
|
+
output_ext = ".xy"
|
|
174
199
|
|
|
175
|
-
#
|
|
176
|
-
# This gives Q in units of Å⁻¹ (inverse Angstroms)
|
|
177
|
-
q = 4 * np.pi * np.sin(theta_rad) / wavelength
|
|
178
|
-
|
|
179
|
-
# STEP 7: Prepare output data
|
|
180
|
-
# Combine Q, intensity, and optional error bars into output array
|
|
200
|
+
# Prepare output data
|
|
181
201
|
if e is None:
|
|
182
|
-
|
|
183
|
-
out_data = np.column_stack((q, y))
|
|
202
|
+
out_data = np.column_stack((x_new, y))
|
|
184
203
|
else:
|
|
185
|
-
|
|
186
|
-
|
|
204
|
+
out_data = np.column_stack((x_new, y, e))
|
|
205
|
+
|
|
206
|
+
# Create output directory (converted subfolder)
|
|
207
|
+
input_dir = os.path.dirname(os.path.abspath(fname))
|
|
208
|
+
if not input_dir:
|
|
209
|
+
input_dir = os.getcwd()
|
|
210
|
+
output_dir = os.path.join(input_dir, "converted")
|
|
211
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
212
|
+
|
|
213
|
+
# Generate output filename
|
|
214
|
+
base = os.path.basename(os.path.splitext(fname)[0])
|
|
215
|
+
output_fname = os.path.join(output_dir, f"{base}{output_ext}")
|
|
187
216
|
|
|
188
|
-
#
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
217
|
+
# Create header comment
|
|
218
|
+
if conversion_type == "wavelength_to_wavelength":
|
|
219
|
+
header = f"# Converted from {os.path.basename(fname)}: 2θ (λ={from_wl} Å) → Q → 2θ (λ={to_wl} Å)"
|
|
220
|
+
elif conversion_type == "wavelength_to_q":
|
|
221
|
+
header = f"# Converted from {os.path.basename(fname)}: 2θ (λ={from_wl} Å) → Q"
|
|
222
|
+
else: # q_to_wavelength
|
|
223
|
+
header = f"# Converted from {os.path.basename(fname)}: Q → 2θ (λ={to_wl} Å)"
|
|
193
224
|
|
|
194
|
-
#
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
225
|
+
# Save converted data
|
|
226
|
+
try:
|
|
227
|
+
# Use UTF-8 encoding to support Greek letters (θ) in headers on all platforms
|
|
228
|
+
np.savetxt(output_fname, out_data, fmt="% .6f", header=header, encoding='utf-8')
|
|
229
|
+
print(f"Saved {output_fname}")
|
|
230
|
+
except Exception as e:
|
|
231
|
+
print(f"Error saving {output_fname}: {e}")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def convert_to_qye(filenames, wavelength: float):
|
|
235
|
+
"""
|
|
236
|
+
Convert 2θ-based XRD files to Q-based .qye files.
|
|
237
|
+
|
|
238
|
+
This is a legacy function maintained for backward compatibility.
|
|
239
|
+
For new code, use convert_xrd_data() instead.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
filenames: List of file paths to convert (e.g., ['data.xy', 'pattern.xye'])
|
|
243
|
+
wavelength: X-ray wavelength in Angstroms (Å)
|
|
244
|
+
|
|
245
|
+
Output:
|
|
246
|
+
Creates .qye files alongside input files with same basename.
|
|
247
|
+
Example: data.xy → data.qye
|
|
248
|
+
"""
|
|
249
|
+
# Convert to new format: wavelength to Q
|
|
250
|
+
convert_xrd_data(filenames, str(wavelength), 'q')
|
|
202
251
|
|
|
203
252
|
|
|
204
|
-
__all__ = ["convert_to_qye"]
|
|
253
|
+
__all__ = ["convert_xrd_data", "convert_to_qye"]
|