ScatTools 0.0.1__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.
- gid/__init__.py +2 -0
- gid/gid.py +154 -0
- sas/__init__.py +2 -0
- sas/sas.py +96 -0
- scattools-0.0.1.dist-info/METADATA +43 -0
- scattools-0.0.1.dist-info/RECORD +7 -0
- scattools-0.0.1.dist-info/WHEEL +4 -0
gid/__init__.py
ADDED
gid/gid.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def calculate_cell_parameters(qxy1, qxy2, qxy3, qz1, qz2, qz3):
|
|
11
|
+
"""
|
|
12
|
+
Calculates crystallographic parameters based on scattering vectors.
|
|
13
|
+
Args:
|
|
14
|
+
qxy1 (float): q_{xy} position of the first peak
|
|
15
|
+
qxy2 (float): q_{xy} position of the second peak
|
|
16
|
+
qxy3 (float): q_{xy} position of the third peak
|
|
17
|
+
qz1 (float): q_{z} position of the first peak
|
|
18
|
+
qz2 (float): q_{z} position of the second peak
|
|
19
|
+
qz3 (float): q_{z} position of the third peak
|
|
20
|
+
Returns:
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
vectors = sorted([
|
|
25
|
+
(qxy1, qz1),
|
|
26
|
+
(qxy2, qz2),
|
|
27
|
+
(qxy3, qz3)
|
|
28
|
+
], key=lambda v: v[0])
|
|
29
|
+
|
|
30
|
+
qx10, qz10 = vectors[0]
|
|
31
|
+
qx01, qz01 = vectors[1]
|
|
32
|
+
qx11, qz11 = vectors[2]
|
|
33
|
+
|
|
34
|
+
cos_g = (qx10 ** 2 + qx01 ** 2 - qx11 ** 2) / (2 * qx10 * qx01)
|
|
35
|
+
if abs(cos_g) > 1.0:
|
|
36
|
+
logger.warning('Error: Check input values (cos G > 1).')
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
gam_rad = np.acos(cos_g) # Angle between A and B in real space
|
|
40
|
+
|
|
41
|
+
a = 2 * np.pi / (qx10 * np.sin(gam_rad))
|
|
42
|
+
b = 2 * np.pi / (qx01 * np.sin(gam_rad))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if abs(qz10 + qz01 - qz11) > 0.03:
|
|
46
|
+
qz10 = -qz10
|
|
47
|
+
|
|
48
|
+
if abs(qz10 + qz01 - qz11) > 0.03:
|
|
49
|
+
qz11 = -qz11
|
|
50
|
+
|
|
51
|
+
# Normal components
|
|
52
|
+
nz = qx10 * qx01 * np.sin(gam_rad)
|
|
53
|
+
nx = -qz10 * qx01 * np.sin(gam_rad)
|
|
54
|
+
ny = -(qx10 * qz01 + qz10 * qx01 * np.cos(gam_rad))
|
|
55
|
+
nmod = np.sqrt(nx ** 2 + ny ** 2 + nz ** 2)
|
|
56
|
+
|
|
57
|
+
if nmod == 0 or abs(nz / nmod) > 1.0:
|
|
58
|
+
logger.warning('Error: Check input values (Nz/Nmod > 1).')
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
t3_rad = math.acos(nz / nmod)
|
|
62
|
+
|
|
63
|
+
# Azimuth: Angle between projection of N on XOY where OX==a* and OY==b*
|
|
64
|
+
if t3_rad == 0.0:
|
|
65
|
+
azimuth3_rad = 0.0
|
|
66
|
+
else:
|
|
67
|
+
if nx == 0.0:
|
|
68
|
+
azimuth3_rad = 0.0
|
|
69
|
+
else:
|
|
70
|
+
azimuth3_rad = np.atan(ny / nx)
|
|
71
|
+
|
|
72
|
+
area = a * b * np.sin(gam_rad)
|
|
73
|
+
|
|
74
|
+
# Convert angles to degrees
|
|
75
|
+
gam_deg = np.rad2deg(gam_rad)
|
|
76
|
+
if gam_deg > 180.0:
|
|
77
|
+
gam_deg = 360.0 - gam_deg
|
|
78
|
+
|
|
79
|
+
t3_deg = np.rad2deg(t3_rad)
|
|
80
|
+
|
|
81
|
+
# Azimuth = counter-clockwise rotation from real space A-vector
|
|
82
|
+
if azimuth3_rad != 0.0:
|
|
83
|
+
azimuth3_deg = np.rad2deg(azimuth3_rad) + (gam_deg - 90.0)
|
|
84
|
+
else:
|
|
85
|
+
azimuth3_deg = 0.0
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"a": np.round(a, 5),
|
|
89
|
+
"b": np.round(b, 5),
|
|
90
|
+
"gamma": np.round(gam_deg,2),
|
|
91
|
+
"tilt": np.round(t3_deg,2),
|
|
92
|
+
"azimuth": np.round(azimuth3_deg,2),
|
|
93
|
+
"area": np.round(area, 3)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def calculate_q_values(a, b, gamma, tilt, azimuth):
|
|
97
|
+
"""
|
|
98
|
+
Calculates the scattering vectors (qxy, qz) based on 2D crystallographic parameters.
|
|
99
|
+
Args:
|
|
100
|
+
a (float): Unit cell parameter a
|
|
101
|
+
b (float): Unit cell parameter b
|
|
102
|
+
gamma (float): Angle between a and b in real space (degrees)
|
|
103
|
+
tilt (float): Tilt angle of the lattice (degrees)
|
|
104
|
+
azimuth (float): Azimuthal angle of the tilt (degrees)
|
|
105
|
+
Returns:
|
|
106
|
+
dict: The qxy and qz scattering vectors for the first three peaks
|
|
107
|
+
"""
|
|
108
|
+
gam_rad = np.deg2rad(gamma)
|
|
109
|
+
t3_rad = np.deg2rad(tilt)
|
|
110
|
+
|
|
111
|
+
# 1. Reverse A and B reciprocal relationships to get qxy1 (qx10) and qxy2 (qx01)
|
|
112
|
+
qx10 = 2 * np.pi / (a * np.sin(gam_rad))
|
|
113
|
+
qx01 = 2 * np.pi / (b * np.sin(gam_rad))
|
|
114
|
+
|
|
115
|
+
# 2. Calculate qxy3 (qx11) using the reciprocal space law of cosines
|
|
116
|
+
qx11 = np.sqrt(qx10**2 + qx01**2 - 2 * qx10 * qx01 * np.cos(gam_rad))
|
|
117
|
+
|
|
118
|
+
# 3. Reverse the azimuth logic
|
|
119
|
+
# azimuth3_deg = azimuth_internal_deg + (gam_deg - 90.0)
|
|
120
|
+
azimuth_internal_deg = azimuth - (gamma - 90.0)
|
|
121
|
+
azimuth_internal_rad = np.deg2rad(azimuth_internal_deg)
|
|
122
|
+
|
|
123
|
+
# 4. Reconstruct the normal vector components
|
|
124
|
+
nz = qx10 * qx01 * np.sin(gam_rad)
|
|
125
|
+
|
|
126
|
+
# Guard against perfectly vertical tilt dividing by zero
|
|
127
|
+
if np.cos(t3_rad) == 0.0:
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
nmod = nz / np.cos(t3_rad)
|
|
131
|
+
|
|
132
|
+
# Extract nx and ny using the inverted azimuthal angles
|
|
133
|
+
nxy = np.sqrt(max(0, nmod**2 - nz**2))
|
|
134
|
+
nx = nxy * np.cos(azimuth_internal_rad)
|
|
135
|
+
ny = nxy * np.sin(azimuth_internal_rad)
|
|
136
|
+
|
|
137
|
+
# 5. Reverse the cross product equations to solve for qz10 and qz01
|
|
138
|
+
# nx = -qz10 * qx01 * sin(gamma)
|
|
139
|
+
qz10 = nx / (qx01 * np.sin(gam_rad))
|
|
140
|
+
|
|
141
|
+
# ny = -(qx10 * qz01 + qz10 * qx01 * cos(gamma))
|
|
142
|
+
qz01 = (-ny - qz10 * qx01 * np.cos(gam_rad)) / qx10
|
|
143
|
+
|
|
144
|
+
# 6. Reconstruct qz11 (assuming ideal constructive addition for the 1,1 peak)
|
|
145
|
+
qz11 = qz10 + qz01
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
"qxy1": np.round(qx10, 5),
|
|
149
|
+
"qz1": np.round(qz10, 5),
|
|
150
|
+
"qxy2": np.round(qx01, 5),
|
|
151
|
+
"qz2": np.round(qz01, 5),
|
|
152
|
+
"qxy3": np.round(qx11, 5),
|
|
153
|
+
"qz3": np.round(qz11, 5)
|
|
154
|
+
}
|
sas/__init__.py
ADDED
sas/sas.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import h5py
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class D11_SANS:
|
|
7
|
+
|
|
8
|
+
def __init__(self, filename, data=True):
|
|
9
|
+
self.q = np.array([])
|
|
10
|
+
self.I = np.array([])
|
|
11
|
+
self.I_err = np.array([])
|
|
12
|
+
self.q_err = np.array([])
|
|
13
|
+
self.q_fit = np.array([])
|
|
14
|
+
self.I_fit = np.array([])
|
|
15
|
+
self.sample_name = ''
|
|
16
|
+
if data:
|
|
17
|
+
self.load_data(filename)
|
|
18
|
+
else:
|
|
19
|
+
self.load_fit(filename)
|
|
20
|
+
|
|
21
|
+
def __repr__(self):
|
|
22
|
+
return self.sample_name
|
|
23
|
+
|
|
24
|
+
def get_sample_name(self):
|
|
25
|
+
return self.sample_name
|
|
26
|
+
|
|
27
|
+
def load_data(self, filename):
|
|
28
|
+
with open(filename, 'r') as f:
|
|
29
|
+
a = f.read()
|
|
30
|
+
self.sample_name = a.split('Subtitle:')[1].split('\n')[0].replace(' ', '')
|
|
31
|
+
data = np.loadtxt(filename, skiprows=53).T
|
|
32
|
+
self.q = data[0] * 10
|
|
33
|
+
self.I = data[1]
|
|
34
|
+
self.I_err = data[2]
|
|
35
|
+
self.q_err = data[3] * 10
|
|
36
|
+
|
|
37
|
+
def plot(self, ax=None, bckg=0.0, label=None):
|
|
38
|
+
if not ax:
|
|
39
|
+
_fig, ax = plt.subplots(1, 1)
|
|
40
|
+
|
|
41
|
+
if not label:
|
|
42
|
+
label = self.sample_name
|
|
43
|
+
|
|
44
|
+
if isinstance(bckg, float):
|
|
45
|
+
ax.errorbar(self.q, self.I - bckg, yerr=self.I_err, label=label, fmt='.', capsize=3, alpha=0.25)
|
|
46
|
+
else:
|
|
47
|
+
ax.errorbar(self.q, self.I - bckg.I, yerr=self.I_err, label=label, fmt='.', capsize=3, alpha=0.25)
|
|
48
|
+
ax.set_xlabel('$q, \\mathrm{nm}^{-1}$')
|
|
49
|
+
ax.set_ylabel('$\\mathrm{\\Delta}\\Sigma / \\mathrm{\\Delta}\\Omega, \\mathrm{cm}^{-1}$')
|
|
50
|
+
ax.loglog()
|
|
51
|
+
ax.legend()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ID02_SAXS:
|
|
55
|
+
def __init__(self, filename, data=True, sample_thickness=0.2):
|
|
56
|
+
self.q = np.array([])
|
|
57
|
+
self.I = np.array([])
|
|
58
|
+
self.I_err = np.array([])
|
|
59
|
+
self.q_fit = np.array([])
|
|
60
|
+
self.I_fit = np.array([])
|
|
61
|
+
self.sample_name = ''
|
|
62
|
+
self.sample_thickness = sample_thickness # for 2 mm capillary to convert into 1/cm
|
|
63
|
+
if data:
|
|
64
|
+
self.load_data(filename)
|
|
65
|
+
else:
|
|
66
|
+
self.load_fit(filename)
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
return self.sample_name
|
|
70
|
+
|
|
71
|
+
def load_fit(self, filename):
|
|
72
|
+
fit = np.loadtxt(filename).T
|
|
73
|
+
# print(fit)
|
|
74
|
+
self.q_fit = fit[0]
|
|
75
|
+
self.I_fit = fit[1]
|
|
76
|
+
|
|
77
|
+
def load_data(self, filename):
|
|
78
|
+
with h5py.File(filename, 'r') as file:
|
|
79
|
+
self.I = np.array(file['entry_0000']['saxsutilities']['data']['I'])[0] / self.sample_thickness
|
|
80
|
+
self.I_err = np.array(file['entry_0000']['saxsutilities']['data']['Idev'])[0] / self.sample_thickness
|
|
81
|
+
self.q = np.array(file['entry_0000']['saxsutilities']['data']['q'])
|
|
82
|
+
self.sample_name = str(file['entry_0000']['saxsutilities']['data']['header_array']['title'][()]).split("\'")[1]
|
|
83
|
+
|
|
84
|
+
def plot(self, ax=None, norm_factor=1, label=None):
|
|
85
|
+
if not ax:
|
|
86
|
+
_fig, ax = plt.subplots(1, 1)
|
|
87
|
+
|
|
88
|
+
if not label:
|
|
89
|
+
label = self.sample_name
|
|
90
|
+
|
|
91
|
+
ax.errorbar(self.q, self.I / norm_factor, self.I_err / norm_factor, fmt='o', capsize=2, linewidth=1,
|
|
92
|
+
markersize=1, label=label)
|
|
93
|
+
ax.set_xlabel('$q, \\mathrm{nm}^{-1}$')
|
|
94
|
+
ax.set_ylabel('$\\mathrm{Intensity, cm}^{-1}$')
|
|
95
|
+
ax.legend()
|
|
96
|
+
ax.loglog()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ScatTools
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Tools for X-ray and neutron scattering analysis
|
|
5
|
+
Project-URL: Homepage, https://github.com/chelberserker/ScatTools/
|
|
6
|
+
Author: Egor A. Bersenev
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Requires-Dist: h5py
|
|
12
|
+
Requires-Dist: matplotlib
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Scatter Analysis
|
|
17
|
+
|
|
18
|
+
A Python package providing tools for the analysis of X-ray and neutron scattering data (SAXS, SANS, and GID).
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install ScatTools
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **GID**: Analysis of Grazing Incidence Diffraction data. Calculates crystallographic parameters based on scattering vectors.
|
|
29
|
+
- **SAS**: Analysis of Small Angle Scattering data, including Small Angle X-ray Scattering (SAXS) and Small Angle Neutron Scattering (SANS) with support for plotting and data loading.
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
Documentation will be made available shortly.
|
|
34
|
+
|
|
35
|
+
## Usage Example
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from gid import calculate_cell_parameters
|
|
39
|
+
|
|
40
|
+
# Calculate parameters based on scattering vectors
|
|
41
|
+
params = calculate_cell_parameters(qxy1=1.0, qxy2=1.0, qxy3=1.0, qz1=0.1, qz2=0.1, qz3=0.1)
|
|
42
|
+
print(params)
|
|
43
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
gid/__init__.py,sha256=BI387EbIgn0VM5BDIe9-9GkikQHQCuSCjJGFVl5EFVg,130
|
|
2
|
+
gid/gid.py,sha256=U2Bz8t6gMHXgJmQKjhfAbnlbfzFuATlhv6ehbqA6C_I,4703
|
|
3
|
+
sas/__init__.py,sha256=Lg7qDwrybzkIHKDnsLtcMfzRkwT7bBIbqVxc-1rzdsc,78
|
|
4
|
+
sas/sas.py,sha256=yjT6btsVIBfOWr3TlM-O2X7IQloua66HKV2IUz88cNw,3252
|
|
5
|
+
scattools-0.0.1.dist-info/METADATA,sha256=GQyQrT7TSJjcCV6HDJd1z2mkZmPugDY4Fug4OYonDQw,1284
|
|
6
|
+
scattools-0.0.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
7
|
+
scattools-0.0.1.dist-info/RECORD,,
|