batplot 1.8.4__py3-none-any.whl → 1.8.5__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 batplot might be problematic. Click here for more details.
- batplot/__init__.py +1 -1
- batplot/args.py +22 -4
- batplot/batch.py +12 -0
- batplot/batplot.py +215 -73
- batplot/converters.py +170 -122
- batplot/cpc_interactive.py +33 -21
- batplot/data/USER_MANUAL.md +49 -0
- batplot/electrochem_interactive.py +63 -36
- batplot/interactive.py +1547 -73
- batplot/operando.py +22 -0
- batplot/operando_ec_interactive.py +232 -2
- batplot/session.py +24 -0
- batplot/style.py +89 -2
- {batplot-1.8.4.dist-info → batplot-1.8.5.dist-info}/METADATA +1 -1
- {batplot-1.8.4.dist-info → batplot-1.8.5.dist-info}/RECORD +19 -19
- {batplot-1.8.4.dist-info → batplot-1.8.5.dist-info}/WHEEL +1 -1
- {batplot-1.8.4.dist-info → batplot-1.8.5.dist-info}/licenses/LICENSE +0 -0
- {batplot-1.8.4.dist-info → batplot-1.8.5.dist-info}/entry_points.txt +0 -0
- {batplot-1.8.4.dist-info → batplot-1.8.5.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,204 @@ 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
|
+
np.savetxt(output_fname, out_data, fmt="% .6f", header=header)
|
|
228
|
+
print(f"Saved {output_fname}")
|
|
229
|
+
except Exception as e:
|
|
230
|
+
print(f"Error saving {output_fname}: {e}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def convert_to_qye(filenames, wavelength: float):
|
|
234
|
+
"""
|
|
235
|
+
Convert 2θ-based XRD files to Q-based .qye files.
|
|
236
|
+
|
|
237
|
+
This is a legacy function maintained for backward compatibility.
|
|
238
|
+
For new code, use convert_xrd_data() instead.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
filenames: List of file paths to convert (e.g., ['data.xy', 'pattern.xye'])
|
|
242
|
+
wavelength: X-ray wavelength in Angstroms (Å)
|
|
243
|
+
|
|
244
|
+
Output:
|
|
245
|
+
Creates .qye files alongside input files with same basename.
|
|
246
|
+
Example: data.xy → data.qye
|
|
247
|
+
"""
|
|
248
|
+
# Convert to new format: wavelength to Q
|
|
249
|
+
convert_xrd_data(filenames, str(wavelength), 'q')
|
|
202
250
|
|
|
203
251
|
|
|
204
|
-
__all__ = ["convert_to_qye"]
|
|
252
|
+
__all__ = ["convert_xrd_data", "convert_to_qye"]
|
batplot/cpc_interactive.py
CHANGED
|
@@ -2920,13 +2920,17 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
2920
2920
|
continue
|
|
2921
2921
|
push_state("legend-position")
|
|
2922
2922
|
try:
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2923
|
+
# Sanitize and store the new position
|
|
2924
|
+
new_pos = _sanitize_legend_offset((x_in, xy_in[1]))
|
|
2925
|
+
if new_pos is not None:
|
|
2926
|
+
fig._cpc_legend_xy_in = new_pos
|
|
2927
|
+
_apply_legend_position()
|
|
2928
|
+
fig.canvas.draw_idle()
|
|
2929
|
+
print(f"Legend position updated: x={new_pos[0]:.2f}, y={new_pos[1]:.2f}")
|
|
2930
|
+
else:
|
|
2931
|
+
print(f"Invalid position: x={x_in:.2f} is out of bounds. Position not updated.")
|
|
2932
|
+
except Exception as e:
|
|
2933
|
+
print(f"Error updating legend position: {e}")
|
|
2930
2934
|
elif pos_cmd == 'y':
|
|
2931
2935
|
# Y only: stay in loop
|
|
2932
2936
|
while True:
|
|
@@ -2943,13 +2947,17 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
2943
2947
|
continue
|
|
2944
2948
|
push_state("legend-position")
|
|
2945
2949
|
try:
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2950
|
+
# Sanitize and store the new position
|
|
2951
|
+
new_pos = _sanitize_legend_offset((xy_in[0], y_in))
|
|
2952
|
+
if new_pos is not None:
|
|
2953
|
+
fig._cpc_legend_xy_in = new_pos
|
|
2954
|
+
_apply_legend_position()
|
|
2955
|
+
fig.canvas.draw_idle()
|
|
2956
|
+
print(f"Legend position updated: x={new_pos[0]:.2f}, y={new_pos[1]:.2f}")
|
|
2957
|
+
else:
|
|
2958
|
+
print(f"Invalid position: y={y_in:.2f} is out of bounds. Position not updated.")
|
|
2959
|
+
except Exception as e:
|
|
2960
|
+
print(f"Error updating legend position: {e}")
|
|
2953
2961
|
else:
|
|
2954
2962
|
# Try to parse as "x y" format
|
|
2955
2963
|
parts = pos_cmd.replace(',', ' ').split()
|
|
@@ -2961,13 +2969,17 @@ def cpc_interactive_menu(fig, ax, ax2, sc_charge, sc_discharge, sc_eff, file_dat
|
|
|
2961
2969
|
print("Invalid numbers."); continue
|
|
2962
2970
|
push_state("legend-position")
|
|
2963
2971
|
try:
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2972
|
+
# Sanitize and store the new position
|
|
2973
|
+
new_pos = _sanitize_legend_offset((x_in, y_in))
|
|
2974
|
+
if new_pos is not None:
|
|
2975
|
+
fig._cpc_legend_xy_in = new_pos
|
|
2976
|
+
_apply_legend_position()
|
|
2977
|
+
fig.canvas.draw_idle()
|
|
2978
|
+
print(f"Legend position updated: x={new_pos[0]:.2f}, y={new_pos[1]:.2f}")
|
|
2979
|
+
else:
|
|
2980
|
+
print(f"Invalid position: x={x_in:.2f}, y={y_in:.2f} is out of bounds. Position not updated.")
|
|
2981
|
+
except Exception as e:
|
|
2982
|
+
print(f"Error updating legend position: {e}")
|
|
2971
2983
|
else:
|
|
2972
2984
|
print("Unknown option.")
|
|
2973
2985
|
except Exception:
|
batplot/data/USER_MANUAL.md
CHANGED
|
@@ -167,6 +167,55 @@ batplot data.xye:0.25:1.54 --xaxis 2theta --interactive
|
|
|
167
167
|
# Multiple files with different wavelengths
|
|
168
168
|
batplot file1.xye:1.5406 file2.xye:0.7093 pattern.cif:1.5406 --xaxis 2theta
|
|
169
169
|
# Each file uses its own wavelength; CIF ticks use 1.5406 Å
|
|
170
|
+
|
|
171
|
+
## Data Conversion (`--convert`)
|
|
172
|
+
|
|
173
|
+
The `--convert` flag allows you to convert XRD data files between different representations and export them to a new `converted` subfolder. This is useful for batch conversion of files without opening them in the interactive plotter.
|
|
174
|
+
|
|
175
|
+
### Conversion Modes
|
|
176
|
+
|
|
177
|
+
1. **Wavelength-to-wavelength conversion**:
|
|
178
|
+
- Convert 2θ values from one wavelength to another
|
|
179
|
+
- Syntax: `batplot file.xye --convert <wavelength1> <wavelength2>`
|
|
180
|
+
- Example: `batplot file.xye --convert 1.54 0.25`
|
|
181
|
+
- Converts 2θ values measured with λ=1.54 Å to equivalent 2θ values for λ=0.25 Å
|
|
182
|
+
- Process: 2θ(λ=1.54) → Q → 2θ(λ=0.25)
|
|
183
|
+
|
|
184
|
+
2. **Wavelength-to-Q conversion**:
|
|
185
|
+
- Convert 2θ values (with given wavelength) to Q space
|
|
186
|
+
- Syntax: `batplot file.xye --convert <wavelength> q`
|
|
187
|
+
- Example: `batplot file.xye --convert 1.54 q`
|
|
188
|
+
- Converts 2θ values measured with λ=1.54 Å to Q space
|
|
189
|
+
- Output file: `converted/file.qye`
|
|
190
|
+
|
|
191
|
+
3. **Q-to-wavelength conversion**:
|
|
192
|
+
- Convert Q space values to 2θ (with given wavelength)
|
|
193
|
+
- Syntax: `batplot file.qye --convert q <wavelength>`
|
|
194
|
+
- Example: `batplot file.qye --convert q 1.54`
|
|
195
|
+
- Converts Q values to 2θ values for λ=1.54 Å
|
|
196
|
+
- Output file: `converted/file.xy`
|
|
197
|
+
|
|
198
|
+
### Output Location
|
|
199
|
+
|
|
200
|
+
All converted files are saved in a `converted` subfolder within the directory containing the input files. The original file names are preserved, but extensions may change:
|
|
201
|
+
- Q-space files: `.qye` extension
|
|
202
|
+
- 2θ files: `.xy` or original extension
|
|
203
|
+
|
|
204
|
+
### Examples
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Convert 2θ from Cu Kα (1.54 Å) to Mo Kα (0.709 Å)
|
|
208
|
+
batplot pattern.xye --convert 1.54 0.709
|
|
209
|
+
|
|
210
|
+
# Convert 2θ to Q space
|
|
211
|
+
batplot pattern.xye --convert 1.54 q
|
|
212
|
+
|
|
213
|
+
# Convert Q to 2θ
|
|
214
|
+
batplot pattern.qye --convert q 1.54
|
|
215
|
+
|
|
216
|
+
# Convert multiple files
|
|
217
|
+
batplot file1.xye file2.xye --convert 1.54 0.25
|
|
218
|
+
```
|
|
170
219
|
```
|
|
171
220
|
|
|
172
221
|
**Note:** When using dual wavelength conversion, the crosshair (press `n` in interactive mode) will automatically display both the original 2theta (calculated from λ₁) and the current 2theta (displayed axis, calculated from λ₂), along with Q and d-spacing values.
|
|
@@ -1896,18 +1896,27 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1896
1896
|
continue
|
|
1897
1897
|
push_state("legend-position")
|
|
1898
1898
|
try:
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1899
|
+
# Store title before updating position
|
|
1900
|
+
_store_legend_title(fig, ax)
|
|
1901
|
+
# Sanitize and store the new position
|
|
1902
|
+
new_pos = _sanitize_legend_offset(fig, (x_in, xy_in[1]))
|
|
1903
|
+
if new_pos is not None:
|
|
1904
|
+
fig._ec_legend_xy_in = new_pos
|
|
1905
|
+
# If legend visible, reposition now
|
|
1906
|
+
leg = ax.get_legend()
|
|
1907
|
+
if leg is not None and leg.get_visible():
|
|
1908
|
+
if not _apply_legend_position(fig, ax):
|
|
1909
|
+
# Fallback: rebuild with title preserved
|
|
1910
|
+
handles, labels = _visible_legend_entries(ax)
|
|
1911
|
+
if handles:
|
|
1912
|
+
legend_title = _get_legend_title(fig)
|
|
1913
|
+
_legend_no_frame(ax, handles, labels, loc='best', borderaxespad=1.0, title=legend_title)
|
|
1914
|
+
fig.canvas.draw_idle()
|
|
1915
|
+
print(f"Legend position updated: x={new_pos[0]:.2f}, y={new_pos[1]:.2f}")
|
|
1916
|
+
else:
|
|
1917
|
+
print(f"Invalid position: x={x_in:.2f} is out of bounds. Position not updated.")
|
|
1918
|
+
except Exception as e:
|
|
1919
|
+
print(f"Error updating legend position: {e}")
|
|
1911
1920
|
elif pos_cmd == 'y':
|
|
1912
1921
|
# Y only: stay in loop
|
|
1913
1922
|
while True:
|
|
@@ -1923,18 +1932,27 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1923
1932
|
continue
|
|
1924
1933
|
push_state("legend-position")
|
|
1925
1934
|
try:
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1935
|
+
# Store title before updating position
|
|
1936
|
+
_store_legend_title(fig, ax)
|
|
1937
|
+
# Sanitize and store the new position
|
|
1938
|
+
new_pos = _sanitize_legend_offset(fig, (xy_in[0], y_in))
|
|
1939
|
+
if new_pos is not None:
|
|
1940
|
+
fig._ec_legend_xy_in = new_pos
|
|
1941
|
+
# If legend visible, reposition now
|
|
1942
|
+
leg = ax.get_legend()
|
|
1943
|
+
if leg is not None and leg.get_visible():
|
|
1944
|
+
if not _apply_legend_position(fig, ax):
|
|
1945
|
+
# Fallback: rebuild with title preserved
|
|
1946
|
+
handles, labels = _visible_legend_entries(ax)
|
|
1947
|
+
if handles:
|
|
1948
|
+
legend_title = _get_legend_title(fig)
|
|
1949
|
+
_legend_no_frame(ax, handles, labels, loc='best', borderaxespad=1.0, title=legend_title)
|
|
1950
|
+
fig.canvas.draw_idle()
|
|
1951
|
+
print(f"Legend position updated: x={new_pos[0]:.2f}, y={new_pos[1]:.2f}")
|
|
1952
|
+
else:
|
|
1953
|
+
print(f"Invalid position: y={y_in:.2f} is out of bounds. Position not updated.")
|
|
1954
|
+
except Exception as e:
|
|
1955
|
+
print(f"Error updating legend position: {e}")
|
|
1938
1956
|
else:
|
|
1939
1957
|
# Try to parse as "x y" format
|
|
1940
1958
|
parts = pos_cmd.replace(',', ' ').split()
|
|
@@ -1946,18 +1964,27 @@ def electrochem_interactive_menu(fig, ax, cycle_lines: Dict[int, Dict[str, Optio
|
|
|
1946
1964
|
print("Invalid numbers."); continue
|
|
1947
1965
|
push_state("legend-position")
|
|
1948
1966
|
try:
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1967
|
+
# Store title before updating position
|
|
1968
|
+
_store_legend_title(fig, ax)
|
|
1969
|
+
# Sanitize and store the new position
|
|
1970
|
+
new_pos = _sanitize_legend_offset(fig, (x_in, y_in))
|
|
1971
|
+
if new_pos is not None:
|
|
1972
|
+
fig._ec_legend_xy_in = new_pos
|
|
1973
|
+
# If legend visible, reposition now
|
|
1974
|
+
leg = ax.get_legend()
|
|
1975
|
+
if leg is not None and leg.get_visible():
|
|
1976
|
+
if not _apply_legend_position(fig, ax):
|
|
1977
|
+
# Fallback: rebuild with title preserved
|
|
1978
|
+
handles, labels = _visible_legend_entries(ax)
|
|
1979
|
+
if handles:
|
|
1980
|
+
legend_title = _get_legend_title(fig)
|
|
1981
|
+
_legend_no_frame(ax, handles, labels, loc='best', borderaxespad=1.0, title=legend_title)
|
|
1982
|
+
fig.canvas.draw_idle()
|
|
1983
|
+
print(f"Legend position updated: x={new_pos[0]:.2f}, y={new_pos[1]:.2f}")
|
|
1984
|
+
else:
|
|
1985
|
+
print(f"Invalid position: x={x_in:.2f}, y={y_in:.2f} is out of bounds. Position not updated.")
|
|
1986
|
+
except Exception as e:
|
|
1987
|
+
print(f"Error updating legend position: {e}")
|
|
1961
1988
|
else:
|
|
1962
1989
|
print("Unknown option.")
|
|
1963
1990
|
except Exception:
|