batplot 1.8.0__py3-none-any.whl → 1.8.2__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 +5 -3
- batplot/batplot.py +44 -4
- batplot/cpc_interactive.py +96 -3
- batplot/electrochem_interactive.py +28 -0
- batplot/interactive.py +18 -2
- batplot/modes.py +12 -12
- batplot/operando.py +2 -0
- batplot/operando_ec_interactive.py +112 -11
- batplot/session.py +35 -1
- batplot/utils.py +40 -0
- batplot/version_check.py +85 -6
- {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/METADATA +1 -1
- batplot-1.8.2.dist-info/RECORD +75 -0
- {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/top_level.txt +1 -0
- batplot_backup_20251221_101150/__init__.py +5 -0
- batplot_backup_20251221_101150/args.py +625 -0
- batplot_backup_20251221_101150/batch.py +1176 -0
- batplot_backup_20251221_101150/batplot.py +3589 -0
- batplot_backup_20251221_101150/cif.py +823 -0
- batplot_backup_20251221_101150/cli.py +149 -0
- batplot_backup_20251221_101150/color_utils.py +547 -0
- batplot_backup_20251221_101150/config.py +198 -0
- batplot_backup_20251221_101150/converters.py +204 -0
- batplot_backup_20251221_101150/cpc_interactive.py +4409 -0
- batplot_backup_20251221_101150/electrochem_interactive.py +4520 -0
- batplot_backup_20251221_101150/interactive.py +3894 -0
- batplot_backup_20251221_101150/manual.py +323 -0
- batplot_backup_20251221_101150/modes.py +799 -0
- batplot_backup_20251221_101150/operando.py +603 -0
- batplot_backup_20251221_101150/operando_ec_interactive.py +5487 -0
- batplot_backup_20251221_101150/plotting.py +228 -0
- batplot_backup_20251221_101150/readers.py +2607 -0
- batplot_backup_20251221_101150/session.py +2951 -0
- batplot_backup_20251221_101150/style.py +1441 -0
- batplot_backup_20251221_101150/ui.py +790 -0
- batplot_backup_20251221_101150/utils.py +1046 -0
- batplot_backup_20251221_101150/version_check.py +253 -0
- batplot-1.8.0.dist-info/RECORD +0 -52
- {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/WHEEL +0 -0
- {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/entry_points.txt +0 -0
- {batplot-1.8.0.dist-info → batplot-1.8.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Configuration management for batplot.
|
|
2
|
+
|
|
3
|
+
This module handles persistent user preferences that are saved between sessions.
|
|
4
|
+
Currently, it manages user-defined color lists, but can be extended for other
|
|
5
|
+
preferences like default styles, font settings, etc.
|
|
6
|
+
|
|
7
|
+
HOW CONFIGURATION WORKS:
|
|
8
|
+
-----------------------
|
|
9
|
+
User preferences are stored in a JSON file at ~/.batplot/config.json.
|
|
10
|
+
This file persists between batplot sessions, so your custom colors are
|
|
11
|
+
remembered the next time you run batplot.
|
|
12
|
+
|
|
13
|
+
Example config.json structure:
|
|
14
|
+
{
|
|
15
|
+
"user_colors": [
|
|
16
|
+
"#FF0000",
|
|
17
|
+
"#00FF00",
|
|
18
|
+
"#0000FF",
|
|
19
|
+
"red",
|
|
20
|
+
"blue"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
The config file is created automatically the first time you save a preference.
|
|
25
|
+
If the file doesn't exist or is corrupted, we return empty defaults (graceful degradation).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import os
|
|
31
|
+
import json
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import List, Optional, Dict, Any
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_config_dir() -> Path:
|
|
37
|
+
"""
|
|
38
|
+
Get batplot configuration directory, creating it if needed.
|
|
39
|
+
|
|
40
|
+
HOW IT WORKS:
|
|
41
|
+
------------
|
|
42
|
+
Returns the path to ~/.batplot directory (user's home directory + .batplot).
|
|
43
|
+
If the directory doesn't exist, it's created automatically.
|
|
44
|
+
|
|
45
|
+
WHY ~/.batplot?
|
|
46
|
+
--------------
|
|
47
|
+
The tilde (~) represents the user's home directory. This is a standard
|
|
48
|
+
location for application configuration files on Unix-like systems (Linux, macOS).
|
|
49
|
+
It keeps user data separate from system files and other users' data.
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
Linux/macOS: /home/username/.batplot or /Users/username/.batplot
|
|
53
|
+
Windows: C:\\Users\\username\\.batplot
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Path object pointing to ~/.batplot directory
|
|
57
|
+
"""
|
|
58
|
+
config_dir = Path.home() / '.batplot'
|
|
59
|
+
# mkdir(exist_ok=True) creates directory if it doesn't exist,
|
|
60
|
+
# but doesn't raise error if it already exists
|
|
61
|
+
config_dir.mkdir(exist_ok=True)
|
|
62
|
+
return config_dir
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_config_file() -> Path:
|
|
66
|
+
"""
|
|
67
|
+
Get path to main configuration file.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Path object pointing to ~/.batplot/config.json
|
|
71
|
+
"""
|
|
72
|
+
return get_config_dir() / 'config.json'
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_config() -> Dict[str, Any]:
|
|
76
|
+
"""
|
|
77
|
+
Load configuration from JSON file.
|
|
78
|
+
|
|
79
|
+
HOW IT WORKS:
|
|
80
|
+
------------
|
|
81
|
+
1. Check if config file exists
|
|
82
|
+
2. If not, return empty dictionary (defaults)
|
|
83
|
+
3. If exists, read JSON and parse it
|
|
84
|
+
4. If file is corrupted or unreadable, return empty dictionary (graceful failure)
|
|
85
|
+
|
|
86
|
+
WHY GRACEFUL FAILURE?
|
|
87
|
+
--------------------
|
|
88
|
+
If the config file is corrupted (invalid JSON) or can't be read (permissions),
|
|
89
|
+
we don't want to crash the program. Instead, we return empty defaults and
|
|
90
|
+
let the user continue. The next time they save a preference, a new valid
|
|
91
|
+
file will be created.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Dictionary with configuration values, or empty dict if:
|
|
95
|
+
- File doesn't exist (first run)
|
|
96
|
+
- File is corrupted (invalid JSON)
|
|
97
|
+
- File can't be read (permissions error)
|
|
98
|
+
"""
|
|
99
|
+
config_file = get_config_file()
|
|
100
|
+
|
|
101
|
+
# If file doesn't exist, return empty defaults (first run)
|
|
102
|
+
if not config_file.exists():
|
|
103
|
+
return {}
|
|
104
|
+
|
|
105
|
+
# Try to read and parse JSON file
|
|
106
|
+
try:
|
|
107
|
+
with open(config_file, 'r') as f:
|
|
108
|
+
return json.load(f) # Parse JSON string into Python dictionary
|
|
109
|
+
except (json.JSONDecodeError, IOError):
|
|
110
|
+
# File exists but is corrupted or unreadable
|
|
111
|
+
# Return empty defaults instead of crashing
|
|
112
|
+
return {}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def save_config(config: Dict[str, Any]) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Save configuration dictionary to JSON file.
|
|
118
|
+
|
|
119
|
+
HOW IT WORKS:
|
|
120
|
+
------------
|
|
121
|
+
1. Get path to config file (~/.batplot/config.json)
|
|
122
|
+
2. Write dictionary as formatted JSON (indented for readability)
|
|
123
|
+
3. If write fails (permissions, disk full, etc.), silently ignore
|
|
124
|
+
|
|
125
|
+
WHY SILENT FAILURE?
|
|
126
|
+
------------------
|
|
127
|
+
Configuration saving is not critical for program operation. If it fails,
|
|
128
|
+
the program should continue working (just won't remember preferences).
|
|
129
|
+
We don't want to interrupt the user's workflow with error messages about
|
|
130
|
+
config file issues.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
config: Dictionary with configuration values to save.
|
|
134
|
+
Example: {'user_colors': ['#FF0000', '#00FF00']}
|
|
135
|
+
"""
|
|
136
|
+
config_file = get_config_file()
|
|
137
|
+
try:
|
|
138
|
+
with open(config_file, 'w') as f:
|
|
139
|
+
# indent=2 makes JSON file human-readable (pretty-printed)
|
|
140
|
+
json.dump(config, f, indent=2)
|
|
141
|
+
except IOError:
|
|
142
|
+
# Silently ignore write errors (permissions, disk full, etc.)
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_user_colors() -> List[str]:
|
|
147
|
+
"""
|
|
148
|
+
Get user-defined color list from configuration file.
|
|
149
|
+
|
|
150
|
+
HOW IT WORKS:
|
|
151
|
+
------------
|
|
152
|
+
1. Load entire config file
|
|
153
|
+
2. Extract 'user_colors' key (list of color codes)
|
|
154
|
+
3. Return list, or empty list if not found
|
|
155
|
+
|
|
156
|
+
WHAT ARE USER COLORS?
|
|
157
|
+
--------------------
|
|
158
|
+
Users can save custom color codes (hex like '#FF0000' or named like 'red')
|
|
159
|
+
for quick access in interactive menus. These colors are stored persistently
|
|
160
|
+
and can be referenced by index (e.g., 'u1' for first user color).
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
List of color codes (hex strings like '#FF0000' or named colors like 'red').
|
|
164
|
+
Empty list if no user colors have been saved yet.
|
|
165
|
+
"""
|
|
166
|
+
config = load_config()
|
|
167
|
+
# .get('user_colors', []) returns the list if key exists, or [] if not
|
|
168
|
+
return config.get('user_colors', [])
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def save_user_colors(colors: List[str]) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Save user-defined color list to configuration file.
|
|
174
|
+
|
|
175
|
+
HOW IT WORKS:
|
|
176
|
+
------------
|
|
177
|
+
1. Load existing config (to preserve other settings)
|
|
178
|
+
2. Update 'user_colors' key with new list
|
|
179
|
+
3. Save entire config back to file
|
|
180
|
+
|
|
181
|
+
WHY UPDATE ENTIRE CONFIG?
|
|
182
|
+
------------------------
|
|
183
|
+
The config file might contain other settings in the future (font preferences,
|
|
184
|
+
default styles, etc.). We want to preserve those when updating colors.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
colors: List of color codes to save.
|
|
188
|
+
Example: ['#FF0000', '#00FF00', 'red', 'blue']
|
|
189
|
+
"""
|
|
190
|
+
config = load_config() # Load existing config (preserves other settings)
|
|
191
|
+
config['user_colors'] = colors # Update user_colors key
|
|
192
|
+
save_config(config) # Save entire config back to file
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
__all__ = [
|
|
196
|
+
'get_user_colors',
|
|
197
|
+
'save_user_colors',
|
|
198
|
+
]
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Data conversion utilities for batplot.
|
|
2
|
+
|
|
3
|
+
This module provides functions to convert X-ray diffraction data between
|
|
4
|
+
different representations, primarily from angle-based (2θ) to momentum
|
|
5
|
+
transfer (Q) space.
|
|
6
|
+
|
|
7
|
+
WHY CONVERT BETWEEN 2θ AND Q?
|
|
8
|
+
-----------------------------
|
|
9
|
+
X-ray diffraction data can be represented in two ways:
|
|
10
|
+
|
|
11
|
+
1. **2θ space (angle-based)**: Traditional representation in degrees
|
|
12
|
+
- Pros: Directly matches experimental setup (detector angle)
|
|
13
|
+
- Cons: Depends on X-ray wavelength (different wavelengths = different scales)
|
|
14
|
+
- Example: Peak at 2θ = 20° for Cu Kα (λ=1.5406 Å)
|
|
15
|
+
|
|
16
|
+
2. **Q space (momentum transfer)**: Physical quantity independent of wavelength
|
|
17
|
+
- Pros: Universal scale (same Q value regardless of wavelength)
|
|
18
|
+
- Cons: Requires wavelength to convert
|
|
19
|
+
- Example: Peak at Q = 2.0 Å⁻¹ (same for any wavelength)
|
|
20
|
+
|
|
21
|
+
Converting to Q-space is useful for:
|
|
22
|
+
- Comparing data from different X-ray sources (synchrotron vs lab)
|
|
23
|
+
- Pair Distribution Function (PDF) analysis (requires Q-space)
|
|
24
|
+
- Direct comparison with theoretical calculations (often in Q-space)
|
|
25
|
+
- Combining datasets from different experiments
|
|
26
|
+
|
|
27
|
+
CONVERSION FORMULA:
|
|
28
|
+
------------------
|
|
29
|
+
The conversion uses Bragg's law:
|
|
30
|
+
Q = (4π sin(θ)) / λ
|
|
31
|
+
|
|
32
|
+
Where:
|
|
33
|
+
- Q: Momentum transfer in Å⁻¹
|
|
34
|
+
- θ: Half of the diffraction angle (2θ/2) in radians
|
|
35
|
+
- λ: X-ray wavelength in Angstroms
|
|
36
|
+
|
|
37
|
+
Physical meaning:
|
|
38
|
+
- Q represents the momentum transferred from X-ray to sample
|
|
39
|
+
- Higher Q = smaller d-spacing (shorter distances in crystal)
|
|
40
|
+
- Q is proportional to sin(θ), so it increases non-linearly with angle
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
import os
|
|
46
|
+
import numpy as np
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def convert_to_qye(filenames, wavelength: float):
|
|
50
|
+
"""
|
|
51
|
+
Convert 2θ-based XRD files to Q-based .qye files.
|
|
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:
|
|
58
|
+
|
|
59
|
+
1. Read input file (2θ in degrees, intensity, optional error bars)
|
|
60
|
+
2. Convert 2θ to θ (half-angle): θ = 2θ / 2
|
|
61
|
+
3. Convert θ from degrees to radians: θ_rad = θ × π/180
|
|
62
|
+
4. Apply conversion formula: Q = 4π sin(θ_rad) / λ
|
|
63
|
+
5. Save output as .qye file (Q, intensity, [error])
|
|
64
|
+
|
|
65
|
+
INPUT FORMAT:
|
|
66
|
+
------------
|
|
67
|
+
Expected columns in input file:
|
|
68
|
+
- Column 1: 2θ values in degrees (e.g., 10.0, 10.1, 10.2, ...)
|
|
69
|
+
- Column 2: Intensity values (e.g., 100, 150, 200, ...)
|
|
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
|
|
79
|
+
|
|
80
|
+
WAVELENGTH VALUES:
|
|
81
|
+
-----------------
|
|
82
|
+
Common X-ray wavelengths:
|
|
83
|
+
- Cu Kα: 1.5406 Å (most common lab source)
|
|
84
|
+
- Mo Kα: 0.7093 Å (higher energy, shorter wavelength)
|
|
85
|
+
- Synchrotron: 0.6199 Å (or other, depends on beamline)
|
|
86
|
+
- Co Kα: 1.7889 Å
|
|
87
|
+
- Fe Kα: 1.9360 Å
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
filenames: List of file paths to convert (e.g., ['data.xy', 'pattern.xye'])
|
|
91
|
+
wavelength: X-ray wavelength in Angstroms (Å)
|
|
92
|
+
Examples:
|
|
93
|
+
- 1.5406 for Cu Kα (most common)
|
|
94
|
+
- 0.7093 for Mo Kα
|
|
95
|
+
- 0.6199 for synchrotron
|
|
96
|
+
|
|
97
|
+
Output:
|
|
98
|
+
Creates .qye files alongside input files with same basename.
|
|
99
|
+
Example: data.xy → data.qye
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
>>> # Convert Cu Kα data to Q-space
|
|
103
|
+
>>> convert_to_qye(['pattern.xy'], wavelength=1.5406)
|
|
104
|
+
Saved pattern.qye
|
|
105
|
+
|
|
106
|
+
>>> # Convert multiple files from synchrotron
|
|
107
|
+
>>> convert_to_qye(['scan1.xy', 'scan2.xy'], wavelength=0.6199)
|
|
108
|
+
Saved scan1.qye
|
|
109
|
+
Saved scan2.qye
|
|
110
|
+
"""
|
|
111
|
+
# ====================================================================
|
|
112
|
+
# PROCESS EACH FILE
|
|
113
|
+
# ====================================================================
|
|
114
|
+
# Loop through each input file and convert it to Q-space.
|
|
115
|
+
# Each file is processed independently (errors in one don't stop others).
|
|
116
|
+
# ====================================================================
|
|
117
|
+
for fname in filenames:
|
|
118
|
+
# STEP 1: Validate file exists
|
|
119
|
+
if not os.path.isfile(fname):
|
|
120
|
+
print(f"File not found: {fname}")
|
|
121
|
+
continue # Skip this file, continue with next
|
|
122
|
+
|
|
123
|
+
# STEP 2: Read data from file
|
|
124
|
+
# np.loadtxt() reads numeric data, skipping lines starting with '#'
|
|
125
|
+
# This handles comment lines in data files
|
|
126
|
+
try:
|
|
127
|
+
data = np.loadtxt(fname, comments="#")
|
|
128
|
+
except Exception as e:
|
|
129
|
+
print(f"Error reading {fname}: {e}")
|
|
130
|
+
continue # Skip if file can't be read
|
|
131
|
+
|
|
132
|
+
# STEP 3: Ensure data is 2D array (handle edge cases)
|
|
133
|
+
# Some files might have only one row, which numpy reads as 1D array
|
|
134
|
+
# We reshape to 2D so indexing works consistently
|
|
135
|
+
if data.ndim == 1:
|
|
136
|
+
data = data.reshape(1, -1) # Reshape to (1, n_columns)
|
|
137
|
+
|
|
138
|
+
# STEP 4: Validate data format
|
|
139
|
+
# Need at least 2 columns: x (2θ) and y (intensity)
|
|
140
|
+
if data.shape[1] < 2:
|
|
141
|
+
print(f"Invalid data format in {fname}: need at least 2 columns (x, y)")
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
# STEP 5: Extract columns
|
|
145
|
+
x = data[:, 0] # 2θ values in degrees (first column)
|
|
146
|
+
y = data[:, 1] # Intensity values (second column)
|
|
147
|
+
e = data[:, 2] if data.shape[1] >= 3 else None # Error bars (third column, optional)
|
|
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
|
+
# ====================================================================
|
|
170
|
+
|
|
171
|
+
# Get θ (half of 2θ) and convert to radians
|
|
172
|
+
# x contains 2θ values in degrees, so x/2 gives θ in degrees
|
|
173
|
+
theta_rad = np.radians(x / 2) # Convert degrees to radians
|
|
174
|
+
|
|
175
|
+
# Apply conversion formula: Q = 4π sin(θ) / λ
|
|
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
|
|
181
|
+
if e is None:
|
|
182
|
+
# No error bars: output has 2 columns (Q, intensity)
|
|
183
|
+
out_data = np.column_stack((q, y))
|
|
184
|
+
else:
|
|
185
|
+
# With error bars: output has 3 columns (Q, intensity, error)
|
|
186
|
+
out_data = np.column_stack((q, y, e))
|
|
187
|
+
|
|
188
|
+
# STEP 8: Generate output filename
|
|
189
|
+
# Replace input extension with .qye
|
|
190
|
+
# Example: data.xy → data.qye, pattern.xye → pattern.qye
|
|
191
|
+
base, _ = os.path.splitext(fname)
|
|
192
|
+
out_fname = f"{base}.qye"
|
|
193
|
+
|
|
194
|
+
# STEP 9: Save converted data
|
|
195
|
+
# Save with:
|
|
196
|
+
# - Header comment documenting the conversion (wavelength used)
|
|
197
|
+
# - 6 decimal places precision (sufficient for most XRD data)
|
|
198
|
+
# - Space-padded format for readability
|
|
199
|
+
np.savetxt(out_fname, out_data, fmt="% .6f",
|
|
200
|
+
header=f"# Converted from {fname} using λ={wavelength} Å")
|
|
201
|
+
print(f"Saved {out_fname}")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
__all__ = ["convert_to_qye"]
|