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 ADDED
@@ -0,0 +1,2 @@
1
+ from .gid import calculate_cell_parameters as calculate_cell_parameters
2
+ from .gid import calculate_q_values as calculate_q_values
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
@@ -0,0 +1,2 @@
1
+ from .sas import D11_SANS as D11_SANS
2
+ from .sas import ID02_SAXS as ID02_SAXS
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any