SusMat-SWAXS 2.1.3__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.
- smswaxs/__init__.py +32 -0
- smswaxs/dat_conv.py +136 -0
- smswaxs/saxs_integration.py +142 -0
- smswaxs/saxs_integration_xeuss.py +225 -0
- smswaxs/sph_harm.py +160 -0
- smswaxs/topas_init.py +24 -0
- smswaxs/waxs_integration_xeuss.py +239 -0
- smswaxs/xeuss_dat_h5_multi.py +48 -0
- smswaxs/xeuss_edf_h5_multi.py +39 -0
- smswaxs/xeuss_edf_tiff.py +26 -0
- smswaxs/xrdml_conv.py +48 -0
- susmat_swaxs-2.1.3.dist-info/METADATA +173 -0
- susmat_swaxs-2.1.3.dist-info/RECORD +15 -0
- susmat_swaxs-2.1.3.dist-info/WHEEL +4 -0
- susmat_swaxs-2.1.3.dist-info/entry_points.txt +9 -0
smswaxs/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SusMat-SWAXS: A collection of python scripts for SWAXS data processing and conversion.
|
|
3
|
+
|
|
4
|
+
This package provides tools for:
|
|
5
|
+
- SAXS/WAXS data integration from Xenocs Xeuss instruments
|
|
6
|
+
- Data format conversions (EDF to HDF5, TIFF, etc.)
|
|
7
|
+
- Background subtraction and absorption corrections
|
|
8
|
+
- Data visualization and analysis
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "2.1.3"
|
|
12
|
+
__author__ = "Samuel Eyley"
|
|
13
|
+
__email__ = "samuel.eyley@kuleuven.be"
|
|
14
|
+
|
|
15
|
+
# Import CLI functions for programmatic access
|
|
16
|
+
from .dat_conv import cli as dat_conv_cli
|
|
17
|
+
from .xeuss_edf_h5_multi import add_edfs_to_hdf5
|
|
18
|
+
from .saxs_integration_xeuss import cli as saxs_integration_cli
|
|
19
|
+
from .waxs_integration_xeuss import cli as waxs_integration_cli
|
|
20
|
+
from .sph_harm import cli as sph_harm_cli
|
|
21
|
+
|
|
22
|
+
# Make CLI functions available at package level
|
|
23
|
+
__all__ = [
|
|
24
|
+
"__version__",
|
|
25
|
+
"__author__",
|
|
26
|
+
"__email__",
|
|
27
|
+
"dat_conv_cli",
|
|
28
|
+
"add_edfs_to_hdf5",
|
|
29
|
+
"saxs_integration_cli",
|
|
30
|
+
"waxs_integration_cli",
|
|
31
|
+
"sph_harm_cli"
|
|
32
|
+
]
|
smswaxs/dat_conv.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Thu May 7 11:44:10 2015
|
|
4
|
+
|
|
5
|
+
@author: u0092172
|
|
6
|
+
"""
|
|
7
|
+
import argparse as ap
|
|
8
|
+
import re
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
def get_line_number(phrase, file_name):
|
|
12
|
+
"""Get the line number containing phrase"""
|
|
13
|
+
with open(file_name, encoding="utf-8") as f:
|
|
14
|
+
for i, line in enumerate(f, 1):
|
|
15
|
+
p = re.compile(phrase)
|
|
16
|
+
if p.match(line) is not None:
|
|
17
|
+
return i
|
|
18
|
+
|
|
19
|
+
def get_number_lines(file_name):
|
|
20
|
+
"""Get the number of lines in a file"""
|
|
21
|
+
with open(file_name, encoding="utf-8") as f:
|
|
22
|
+
lines = len(f.readlines())
|
|
23
|
+
return lines
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def cli():
|
|
27
|
+
"""Convert dat files to xye format for use in TOPAS"""
|
|
28
|
+
parser = ap.ArgumentParser(description='Convert dat files to xye format for use in TOPAS')
|
|
29
|
+
parser.add_argument("input", help="Filename of the .dat (excl. ext.)")
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--wavelength", help="Wavelength in Angstroms", default=float(1.54189))
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--highscore", help="Output for highscore plus", action="store_true")
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--exposure", help="Counting time (convert to counts from CPS)", type=float, default=1)
|
|
36
|
+
# parser.add_argument("--dat", help="convert from dat file", action="store_true")
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--SAXS", help="output SAXS data (q and don't cut low angle)", action="store_true")
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--renormalize", help="boost counts", action="store_true")
|
|
41
|
+
parser.add_argument("--clip", help="clip range", action="store_true")
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--fullprof", help="general fullprof input", action="store_true")
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--kratky", help="Generate Kratky plot columns", action="store_true")
|
|
46
|
+
parser.add_argument("--pyfai", help="pyfai input", action="store_true")
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--zero", help="set minimum to zero", action="store_true")
|
|
49
|
+
args = parser.parse_args() # map arguments to args
|
|
50
|
+
|
|
51
|
+
fname = args.input
|
|
52
|
+
if args.highscore is True:
|
|
53
|
+
ext = ".asc"
|
|
54
|
+
elif args.fullprof is True:
|
|
55
|
+
ext = "_FP.dat"
|
|
56
|
+
elif args.kratky is True:
|
|
57
|
+
ext = "_kratky.dat"
|
|
58
|
+
else:
|
|
59
|
+
ext = ".xye"
|
|
60
|
+
|
|
61
|
+
# Import the data
|
|
62
|
+
|
|
63
|
+
if args.kratky is True:
|
|
64
|
+
args.SAXS = True
|
|
65
|
+
|
|
66
|
+
if args.pyfai is True:
|
|
67
|
+
data = np.genfromtxt(fname, comments='#',
|
|
68
|
+
usecols=(0, 1, 2), encoding="utf-8")
|
|
69
|
+
else:
|
|
70
|
+
head = get_line_number('.*q\\([AÅ].*', fname)
|
|
71
|
+
data = np.genfromtxt(fname, skip_header=head,
|
|
72
|
+
usecols=(0, 1, 2), encoding="utf-8")
|
|
73
|
+
|
|
74
|
+
data = data[~np.isnan(data).any(axis=1)]
|
|
75
|
+
|
|
76
|
+
q = data[:, 0]
|
|
77
|
+
i = data[:, 1]
|
|
78
|
+
e = data[:, 2]
|
|
79
|
+
|
|
80
|
+
if args.pyfai is True:
|
|
81
|
+
ttheta = q
|
|
82
|
+
if args.clip is True:
|
|
83
|
+
start = np.where(ttheta > 5)[0][0]
|
|
84
|
+
ttheta = ttheta[start:]
|
|
85
|
+
i = i[start:]
|
|
86
|
+
e = e[start:]
|
|
87
|
+
elif args.SAXS is False:
|
|
88
|
+
# Calc 2theta
|
|
89
|
+
|
|
90
|
+
ttheta = 2*np.degrees(np.arcsin((q*args.wavelength)/(4*np.pi)))
|
|
91
|
+
|
|
92
|
+
# Clip the data to start at 5 degrees to remove excessive intensity
|
|
93
|
+
if args.clip is True:
|
|
94
|
+
start = np.where(ttheta > 5)[0][0]
|
|
95
|
+
ttheta = ttheta[start:]
|
|
96
|
+
i = i[start:]
|
|
97
|
+
e = e[start:]
|
|
98
|
+
|
|
99
|
+
# Convert i to counts
|
|
100
|
+
if args.renormalize is True:
|
|
101
|
+
i = i*args.exposure*1e11
|
|
102
|
+
else:
|
|
103
|
+
i = i*args.exposure
|
|
104
|
+
|
|
105
|
+
if min(i) < 0:
|
|
106
|
+
i -= min(i)
|
|
107
|
+
|
|
108
|
+
if args.zero is True:
|
|
109
|
+
i -= min(i)
|
|
110
|
+
|
|
111
|
+
if args.highscore is True:
|
|
112
|
+
out = np.column_stack((ttheta.flatten(), i.flatten()))
|
|
113
|
+
else:
|
|
114
|
+
if args.SAXS is False:
|
|
115
|
+
out = np.column_stack((ttheta.flatten(), i.flatten(), e.flatten()))
|
|
116
|
+
elif args.kratky is False:
|
|
117
|
+
out = np.column_stack((q.flatten(), i.flatten(), e.flatten()))
|
|
118
|
+
else:
|
|
119
|
+
q2i = (q**2)*i
|
|
120
|
+
q2sig = (q**2)*e
|
|
121
|
+
out = np.column_stack(
|
|
122
|
+
(q.flatten(), i.flatten(), q2i.flatten(), e.flatten(), q2sig.flatten()))
|
|
123
|
+
|
|
124
|
+
if args.fullprof is True:
|
|
125
|
+
head2 = 'XYDATA' + '\n' + 'INTER 0 0 2' + 4*'\n'
|
|
126
|
+
elif args.kratky is True:
|
|
127
|
+
head2 = "q I(q) q2I(q) sig(q) q2sig(q)"
|
|
128
|
+
else:
|
|
129
|
+
head2 = ''
|
|
130
|
+
|
|
131
|
+
np.savetxt(fname[:-4]+ext, out, delimiter=' ', fmt='%.10f',
|
|
132
|
+
header=head2, comments='', newline='\n')
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == '__main__':
|
|
136
|
+
cli()
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Spyder Editor
|
|
4
|
+
|
|
5
|
+
This is a temporary script file.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import h5py
|
|
9
|
+
import hdf5plugin
|
|
10
|
+
import pyFAI
|
|
11
|
+
import fabio
|
|
12
|
+
import numpy as np
|
|
13
|
+
import os
|
|
14
|
+
import glob
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
from matplotlib.colors import LogNorm
|
|
17
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
18
|
+
import matplotlib.ticker as ticker
|
|
19
|
+
import argparse as ap
|
|
20
|
+
|
|
21
|
+
parser = ap.ArgumentParser(description=
|
|
22
|
+
"Perform data reduction on hdf5 data from DESY")
|
|
23
|
+
|
|
24
|
+
parser.add_argument("sample", help="sample file (.nxs)")
|
|
25
|
+
|
|
26
|
+
parser.add_argument("mask", help="mask file (.edf)")
|
|
27
|
+
parser.add_argument("poni", help="PONI configuration file (.poni)")
|
|
28
|
+
parser.add_argument("--empty", help="empty file (.nxs)")
|
|
29
|
+
parser.add_argument("--background", help="background file (.nxs)")
|
|
30
|
+
|
|
31
|
+
parser.add_argument("--bkg_factor", help="background correction factor",type=float,default=float(1))
|
|
32
|
+
parser.add_argument("--noscaling", help="background file (.nxs)",action="store_true")
|
|
33
|
+
parser.add_argument("--autobkg", help="Automatic background scaling",action="store_true")
|
|
34
|
+
args = parser.parse_args()
|
|
35
|
+
|
|
36
|
+
#path = "/home/u0092172/Documents/"
|
|
37
|
+
#folder = "DESY/"
|
|
38
|
+
#sample = "GA5/GA5_2.nxs"
|
|
39
|
+
#background = "water/water_2.nxs"
|
|
40
|
+
mask = fabio.open(args.mask)
|
|
41
|
+
poni = pyFAI.load(args.poni)#"config_saxs_sdetx_1500_12keV.poni")
|
|
42
|
+
file = h5py.File(args.sample,'r')
|
|
43
|
+
|
|
44
|
+
if args.noscaling == True:
|
|
45
|
+
transmission_sample = 1
|
|
46
|
+
else:
|
|
47
|
+
transmission_sample = file['scan']['data']['beamstop_2'][()]/file['scan']['data']['ic1'][()]
|
|
48
|
+
print("Sample transmission:"+str(round(transmission_sample,4)))
|
|
49
|
+
|
|
50
|
+
temperature = str(round(file['scan']['data']['p62']['t95tempproglinkam']['eh.01']['temperature'][()],2))
|
|
51
|
+
print("Sample temperature:"+temperature)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
data_1D = poni.integrate1d(file['scan']['data']['saxs_raw'][()][0]/transmission_sample,
|
|
55
|
+
1000, filename=args.sample+"_"+temperature+'_1D.dat', correctSolidAngle=True,
|
|
56
|
+
method='csr_ocl', radial_range=(None), azimuth_range=(None),
|
|
57
|
+
unit="q_A^-1", mask=mask.data, normalization_factor=1.0,
|
|
58
|
+
metadata=None,error_model="poisson")
|
|
59
|
+
|
|
60
|
+
if args.background != None:
|
|
61
|
+
bkg = h5py.File(args.background,'r')
|
|
62
|
+
if args.noscaling == True:
|
|
63
|
+
transmission_bkg = 1
|
|
64
|
+
else:
|
|
65
|
+
transmission_bkg = bkg['scan']['data']['beamstop_2'][()]/bkg['scan']['data']['ic1'][()]
|
|
66
|
+
try:
|
|
67
|
+
temperature_bkg = str(round(bkg['scan']['data']['p62']['t95tempproglinkam']['eh.01']['temperature'][()],2))
|
|
68
|
+
except KeyError:
|
|
69
|
+
temperature_bkg = str(-1000)
|
|
70
|
+
print("Background transmission:"+str(round(transmission_bkg,4)))
|
|
71
|
+
print("Background temperature:"+temperature_bkg)
|
|
72
|
+
|
|
73
|
+
bkg_1D = poni.integrate1d(bkg['scan']['data']['saxs_raw'][()][0]/transmission_bkg,
|
|
74
|
+
1000, filename=args.sample+"_"+temperature+'_bkg1D.dat', correctSolidAngle=True,
|
|
75
|
+
method='csr_ocl', radial_range=(None), azimuth_range=(None),
|
|
76
|
+
unit="q_A^-1", mask=mask.data, normalization_factor=1.0,
|
|
77
|
+
metadata=None,error_model="poisson")
|
|
78
|
+
if args.autobkg == True:
|
|
79
|
+
print("Use automated background scaling with caution!")
|
|
80
|
+
if max(bkg_1D[1][:np.where(bkg_1D[0]>0.02)[0][0]]) > max(data_1D[1][:np.where(data_1D[0]>0.02)[0][0]]):
|
|
81
|
+
bkg_1D = [bkg_1D[0],bkg_1D[1],bkg_1D[2]]
|
|
82
|
+
bkg_1D[1] -= max(bkg_1D[1][:np.where(bkg_1D[0]>0.02)[0][0]]) - max(data_1D[1][:np.where(data_1D[0]>0.02)[0][0]])
|
|
83
|
+
else:
|
|
84
|
+
data_I = data_1D[1]
|
|
85
|
+
data_sig = abs(data_1D[2])
|
|
86
|
+
if args.empty != None:
|
|
87
|
+
empty = h5py.File(args.empty,'r')
|
|
88
|
+
if args.noscaling == True:
|
|
89
|
+
transmission_empty = 1
|
|
90
|
+
else:
|
|
91
|
+
transmission_empty = empty['scan']['data']['beamstop_2'][()]/empty['scan']['data']['ic1'][()]
|
|
92
|
+
empty_1D = poni.integrate1d(empty['scan']['data']['saxs_raw'][()][0]/transmission_empty,
|
|
93
|
+
1000, filename=args.sample+"_"+temperature+'_empty1D.dat', correctSolidAngle=True,
|
|
94
|
+
method='csr_ocl', radial_range=(None), azimuth_range=(None),
|
|
95
|
+
unit="q_A^-1", mask=mask.data, normalization_factor=1.0,
|
|
96
|
+
metadata=None,error_model="poisson")
|
|
97
|
+
if args.autobkg == True:
|
|
98
|
+
if max(empty_1D[1][:np.where(empty_1D[0]>0.02)[0][0]]) > max(data_1D[1][:np.where(data_1D[0]>0.02)[0][0]]):
|
|
99
|
+
empty_1D[1] -= max(empty_1D[1][:np.where(empty_1D[0]>0.02)[0][0]]) - max(data_1D[1][:np.where(data_1D[0]>0.02)[0][0]])
|
|
100
|
+
if args.background != None:
|
|
101
|
+
data_I = (data_1D[1]-empty_1D[1])-(bkg_1D[1]-empty_1D[1])
|
|
102
|
+
data_sig = abs(data_1D[2])+abs(empty_1D[2])+abs(bkg_1D[2])+abs(empty_1D[2])
|
|
103
|
+
else:
|
|
104
|
+
data_I = data_1D[1]-empty_1D[1]
|
|
105
|
+
data_sig = abs(data_1D[2])+abs(empty_1D[2])
|
|
106
|
+
else:
|
|
107
|
+
if args.background == None:
|
|
108
|
+
data_I = data_1D[1]
|
|
109
|
+
data_sig = abs(data_1D[2])
|
|
110
|
+
else:
|
|
111
|
+
data_I = data_1D[1]-bkg_1D[1]
|
|
112
|
+
data_sig = abs(data_1D[2])+abs(bkg_1D[2])
|
|
113
|
+
fig, ax = plt.subplots(figsize=(25,10),constrained_layout=True)
|
|
114
|
+
"""fig1 = ax[0].imshow(data_cor,norm=LogNorm(vmin=1e-1, vmax=10**3),cmap=plt.get_cmap('plasma'))
|
|
115
|
+
# fig1 = ax[0].imshow(data_cor,norm=LogNorm(vmin=300, vmax=10**5),cmap=plt.get_cmap('plasma'))
|
|
116
|
+
# create an axes on the right side of ax. The width of cax will be 5%
|
|
117
|
+
# of ax and the padding between cax and ax will be fixed at 0.05 inch.
|
|
118
|
+
divider = make_axes_locatable(ax[0])
|
|
119
|
+
cax = divider.append_axes("right", size="5%", pad=0.05)
|
|
120
|
+
|
|
121
|
+
cbar = plt.colorbar(fig1, cax=cax)
|
|
122
|
+
cbar.ax.tick_params(labelsize=25)
|
|
123
|
+
|
|
124
|
+
ax[0].set_xlabel('pixel',fontsize=25)
|
|
125
|
+
ax[0].set_ylabel('pixel',fontsize=25)
|
|
126
|
+
ax[0].tick_params(labelsize=25)
|
|
127
|
+
"""
|
|
128
|
+
# fig2 = ax.plot(q, Intensity)
|
|
129
|
+
#fig2 = ax.plot(data_1D[0],data_I)
|
|
130
|
+
fig = ax.errorbar(data_1D[0],data_I,yerr=data_sig)
|
|
131
|
+
ax.set_xscale('log')
|
|
132
|
+
ax.set_yscale('log')
|
|
133
|
+
ax.set_xlabel(r'$Q\/[\AA^{-1}]$',fontsize=25)
|
|
134
|
+
ax.set_ylabel('intensity [a.u.]',fontsize=25)
|
|
135
|
+
# ax.set_xticks([0.01,0.1,0.4,0.6])
|
|
136
|
+
ax.get_xaxis().set_major_formatter(ticker.ScalarFormatter())
|
|
137
|
+
ax.tick_params(labelsize=25)
|
|
138
|
+
#plt.show()
|
|
139
|
+
plt.savefig(args.sample+"_"+temperature+".png", bbox_inches="tight", dpi=600)
|
|
140
|
+
|
|
141
|
+
data_out = np.column_stack((data_1D[0],data_I,data_sig))
|
|
142
|
+
np.savetxt(args.sample+"_subtracted.dat",data_out,delimiter='\t',fmt='%s')
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This script will integrate 2D SAXS data collected on the Xenocs
|
|
3
|
+
Xeuss SWAXS instrument and subtract background data if necessary.
|
|
4
|
+
"""
|
|
5
|
+
import argparse as ap
|
|
6
|
+
import matplotlib.ticker as ticker
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
from matplotlib.colors import LogNorm
|
|
9
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
10
|
+
import pyFAI
|
|
11
|
+
from pyFAI.integrator.azimuthal import AzimuthalIntegrator
|
|
12
|
+
import fabio
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
# pylint: disable=C0103, unused-variable
|
|
16
|
+
def cli():
|
|
17
|
+
"""Integrate 2D saxs data"""
|
|
18
|
+
parser = ap.ArgumentParser(
|
|
19
|
+
description="Perform data reduction on data from Xeuss 2.0C")
|
|
20
|
+
|
|
21
|
+
parser.add_argument("sample", help="sample file (.edf)")
|
|
22
|
+
|
|
23
|
+
parser.add_argument("mask", help="mask file (.edf)")
|
|
24
|
+
parser.add_argument("--poni", help="PONI configuration file (.poni)")
|
|
25
|
+
parser.add_argument("--empty", help="empty file (.edf)")
|
|
26
|
+
parser.add_argument("--background", help="background file (.edf)")
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--opencl", help="Use OpenCL (requires pyopencl)", action="store_true")
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--bkg_factor", help="background correction factor", type=float, default=float(1))
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--lac", help="Convert to absolute units using the calculated thickness - LAC in cm^-1",type=float, default=float(0))
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--thickness", help="thickness in cm used for transmission correction (otherwise, transmitted intensity used)",type=float, default=float(1))
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--autobkg", help="Automatic background scaling", action="store_true")
|
|
37
|
+
args = parser.parse_args()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
file = fabio.open(args.sample)
|
|
41
|
+
mask = fabio.open(args.mask)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if args.opencl:
|
|
45
|
+
intmeth = 'opencl'
|
|
46
|
+
else:
|
|
47
|
+
intmeth = 'csr'
|
|
48
|
+
if not args.poni:
|
|
49
|
+
geo = {
|
|
50
|
+
"detector": pyFAI.detectors.Detector(float(file.header['PSize_2']),float(file.header['PSize_1'])),
|
|
51
|
+
"dist": float(file.header['SampleDistance']),
|
|
52
|
+
"poni1": float(file.header['PSize_2'])*float(file.header['Center_2']),
|
|
53
|
+
"poni2": float(file.header['PSize_1'])*float(file.header['Center_1']),
|
|
54
|
+
"rot1": 0,
|
|
55
|
+
"rot2": 0,
|
|
56
|
+
"rot3": 0,
|
|
57
|
+
"wavelength": float(file.header['WaveLength'])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# poni = pyFAI.azimuthalIntegrator.AzimuthalIntegrator(**geo)
|
|
61
|
+
|
|
62
|
+
poni = AzimuthalIntegrator(**geo)
|
|
63
|
+
else:
|
|
64
|
+
poni = pyFAI.load(args.poni)
|
|
65
|
+
|
|
66
|
+
if np.allclose(args.lac, 0):
|
|
67
|
+
transmission_sample = args.thickness
|
|
68
|
+
else:
|
|
69
|
+
transmission_sample = -((1/args.lac)*np.log10(float(file.header['Transmission'])))
|
|
70
|
+
|
|
71
|
+
norm_sample = (float(file.header['Intensity1']) *
|
|
72
|
+
float(file.header['ExposureTime']) *
|
|
73
|
+
(
|
|
74
|
+
(
|
|
75
|
+
float(file.header['PSize_1']) /
|
|
76
|
+
float(file.header['SampleDistance']))
|
|
77
|
+
)**2
|
|
78
|
+
)/1e-15
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
data_1D = poni.integrate1d(file.data,
|
|
82
|
+
1000, filename=args.sample[:-4]+'_1D.dat', correctSolidAngle=True,
|
|
83
|
+
method=intmeth, radial_range=(None), azimuth_range=(None),
|
|
84
|
+
unit="q_A^-1", mask=mask.data, normalization_factor=norm_sample,
|
|
85
|
+
metadata=None, error_model="poisson")
|
|
86
|
+
|
|
87
|
+
data_2D = file.data/transmission_sample
|
|
88
|
+
|
|
89
|
+
if args.background is not None:
|
|
90
|
+
bkg = fabio.open(args.background)
|
|
91
|
+
if np.allclose(args.lac, 0):
|
|
92
|
+
transmission_bkg = args.thickness
|
|
93
|
+
elif np.allclose(float(bkg.header['Transmission']), 1, atol=5e-3):
|
|
94
|
+
transmission_bkg = -((1/args.lac)*np.log10(1))
|
|
95
|
+
else:
|
|
96
|
+
transmission_bkg = -((1/args.lac)*np.log10(float(bkg.header['Transmission'])))
|
|
97
|
+
|
|
98
|
+
norm_bkg = (float(bkg.header['Intensity1']) *
|
|
99
|
+
float(bkg.header['ExposureTime']) *
|
|
100
|
+
(
|
|
101
|
+
(
|
|
102
|
+
float(bkg.header['PSize_1']) /
|
|
103
|
+
float(bkg.header['SampleDistance']))
|
|
104
|
+
)**2
|
|
105
|
+
)/1e-15
|
|
106
|
+
bkg_1D = poni.integrate1d(bkg.data,
|
|
107
|
+
1000, filename=args.sample[:-4]+'_bkg1D.dat', correctSolidAngle=True,
|
|
108
|
+
method=intmeth, radial_range=(None), azimuth_range=(None),
|
|
109
|
+
unit="q_A^-1", mask=mask.data, normalization_factor=norm_bkg,
|
|
110
|
+
metadata=None, error_model="poisson")
|
|
111
|
+
|
|
112
|
+
bkg_2D = bkg.data/transmission_bkg
|
|
113
|
+
|
|
114
|
+
if args.autobkg is True:
|
|
115
|
+
print("Use automated background scaling with caution!")
|
|
116
|
+
if max(bkg_1D[1][:np.where(bkg_1D[0] > 0.02)[0][0]]) > max(data_1D[1][:np.where(data_1D[0] > 0.02)[0][0]]):
|
|
117
|
+
bkg_1D = [bkg_1D[0], bkg_1D[1], bkg_1D[2]]
|
|
118
|
+
bkg_1D[1] -= max(bkg_1D[1][:np.where(bkg_1D[0] > 0.02)[0][0]]) - \
|
|
119
|
+
max(data_1D[1][:np.where(data_1D[0] > 0.02)[0][0]])
|
|
120
|
+
else:
|
|
121
|
+
data_I = data_1D[1]
|
|
122
|
+
data_sig = abs(data_1D[2])
|
|
123
|
+
if args.empty is not None:
|
|
124
|
+
empty = fabio.open(args.empty)
|
|
125
|
+
if np.allclose(args.lac, 0):
|
|
126
|
+
transmission_empty = args.thickness
|
|
127
|
+
elif np.allclose(float(bkg.header['Transmission']), 1, atol=5e-3):
|
|
128
|
+
transmission_empty = -((1/args.lac)*np.log10(1))
|
|
129
|
+
else:
|
|
130
|
+
transmission_empty = -((1/args.lac)*np.log10(float(empty.header['Transmission'])))
|
|
131
|
+
norm_empty = (float(empty.header['Intensity1']) *
|
|
132
|
+
float(empty.header['ExposureTime']) *
|
|
133
|
+
(
|
|
134
|
+
(
|
|
135
|
+
float(empty.header['PSize_1']) /
|
|
136
|
+
float(empty.header['SampleDistance']))
|
|
137
|
+
)**2
|
|
138
|
+
)/1e-15
|
|
139
|
+
empty_1D = poni.integrate1d(empty.data,
|
|
140
|
+
1000, filename=args.sample[:-4]+'_empty1D.dat', correctSolidAngle=True,
|
|
141
|
+
method=intmeth, radial_range=(None), azimuth_range=(None),
|
|
142
|
+
unit="q_A^-1", mask=mask.data, normalization_factor=norm_empty,
|
|
143
|
+
metadata=None, error_model="poisson")
|
|
144
|
+
|
|
145
|
+
empty_2D = empty.data/transmission_empty
|
|
146
|
+
empty_1Df = empty_1D[1]
|
|
147
|
+
|
|
148
|
+
if args.autobkg is True:
|
|
149
|
+
if max(empty_1D[1][:np.where(empty_1D[0] > 0.02)[0][0]]) > max(data_1D[1][:np.where(data_1D[0] > 0.02)[0][0]]):
|
|
150
|
+
empty_1Df = empty_1D[1] - max(empty_1D[1][:np.where(empty_1D[0] > 0.02)[
|
|
151
|
+
0][0]]) - max(data_1D[1][:np.where(data_1D[0] > 0.02)[0][0]])
|
|
152
|
+
|
|
153
|
+
if args.background is not None:
|
|
154
|
+
data_I = ((data_1D[1]-empty_1Df) -
|
|
155
|
+
(bkg_1D[1]-empty_1Df))*transmission_sample
|
|
156
|
+
data_2I = (data_2D-empty_2D)-(bkg_2D-empty_2D)
|
|
157
|
+
data_sig = (abs(data_1D[2])+abs(empty_1D[2]) +
|
|
158
|
+
abs(bkg_1D[2])+abs(empty_1D[2]))*transmission_sample
|
|
159
|
+
else:
|
|
160
|
+
data_I = (data_1D[1]-empty_1Df)*transmission_sample
|
|
161
|
+
data_2I = data_2D-empty_2D
|
|
162
|
+
data_sig = (abs(data_1D[2])+abs(empty_1D[2]))*transmission_sample
|
|
163
|
+
else:
|
|
164
|
+
if args.background is None:
|
|
165
|
+
data_I = data_1D[1]*transmission_sample
|
|
166
|
+
data_2I = data_2D
|
|
167
|
+
data_sig = abs(data_1D[2])*transmission_sample
|
|
168
|
+
else:
|
|
169
|
+
data_I = (data_1D[1]-bkg_1D[1])*transmission_sample
|
|
170
|
+
data_2I = data_2D - bkg_2D
|
|
171
|
+
data_sig = (abs(data_1D[2])+abs(bkg_1D[2]))*transmission_sample
|
|
172
|
+
|
|
173
|
+
data_2I2 = poni.integrate2d(data_2I, 3000, 3600, unit="q_A^-1")
|
|
174
|
+
|
|
175
|
+
I2, q2, chi2 = data_2I2
|
|
176
|
+
|
|
177
|
+
if np.min(I2) < 0:
|
|
178
|
+
I2min = 1e-3
|
|
179
|
+
else:
|
|
180
|
+
I2min = np.min(I2)
|
|
181
|
+
I2max = np.max(I2)
|
|
182
|
+
|
|
183
|
+
fig, axs = plt.subplot_mosaic(
|
|
184
|
+
[['(a)', '(b)']], figsize=(7, 4), constrained_layout=True)
|
|
185
|
+
|
|
186
|
+
fig1 = axs['(a)'].imshow(I2, origin="lower", extent=[q2.min(), q2.max(), chi2.min(), chi2.max()],
|
|
187
|
+
norm=LogNorm(vmin=I2min, vmax=I2max), cmap=plt.get_cmap('cividis'), aspect="auto")
|
|
188
|
+
divider = make_axes_locatable(axs['(a)'])
|
|
189
|
+
cax = divider.append_axes("top", size="5%", pad=0.05)
|
|
190
|
+
|
|
191
|
+
cbar = plt.colorbar(fig1, cax=cax, orientation="horizontal")
|
|
192
|
+
cbar.ax.tick_params(labelsize=8)
|
|
193
|
+
cax.xaxis.set_ticks_position("top")
|
|
194
|
+
|
|
195
|
+
axs['(a)'].set_xlabel(r'q /$\AA^{-1}$', fontsize=8)
|
|
196
|
+
axs['(a)'].set_ylabel(r'$\phi/^{\circ}$', fontsize=8)
|
|
197
|
+
axs['(a)'].tick_params(labelsize=8)
|
|
198
|
+
|
|
199
|
+
fig2 = axs['(b)'].errorbar(data_1D[0], data_I, yerr=data_sig, linestyle='')
|
|
200
|
+
|
|
201
|
+
axs['(b)'].set_xscale('log')
|
|
202
|
+
axs['(b)'].set_yscale('log')
|
|
203
|
+
axs['(b)'].set_xlabel(r'q /$\AA^{-1}$', fontsize=8)
|
|
204
|
+
if np.allclose(args.lac,0):
|
|
205
|
+
if np.allclose(args.thickness,1):
|
|
206
|
+
axs['(b)'].set_ylabel('I(q) /Arb.', fontsize=8)
|
|
207
|
+
|
|
208
|
+
else:
|
|
209
|
+
axs['(b)'].set_ylabel(r'I(q) /cm$^{-1}$.', fontsize=8)
|
|
210
|
+
axs['(b)'].get_xaxis().set_major_formatter(ticker.ScalarFormatter())
|
|
211
|
+
# axs['(b)'].get_yaxis().set_major_formatter(ticker.ScalarFormatter())
|
|
212
|
+
# axs['(b)'].ticklabel_format(style='sci', axis='both',
|
|
213
|
+
# scilimits=(0, 0), useMathText=True)
|
|
214
|
+
#axs['(b)'].set_xticks([0.01, 0.1, 1])
|
|
215
|
+
# axs['(b)'].get_xaxis().set_major_formatter(ticker.ScalarFormatter())
|
|
216
|
+
axs['(b)'].tick_params(labelsize=8)
|
|
217
|
+
# plt.show()
|
|
218
|
+
plt.savefig(args.sample[:-4]+".png", bbox_inches="tight",dpi=600)
|
|
219
|
+
|
|
220
|
+
data_out = np.column_stack((data_1D[0], data_I, data_sig))
|
|
221
|
+
np.savetxt(args.sample[:-4]+"_subtracted.dat",
|
|
222
|
+
data_out, delimiter='\t', fmt='%s')
|
|
223
|
+
|
|
224
|
+
if __name__ == "__main__":
|
|
225
|
+
cli()
|
smswaxs/sph_harm.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Plot spherical harmonics functions from TOPAS coefficients.
|
|
4
|
+
|
|
5
|
+
This module visualizes spherical harmonics functions using coefficients
|
|
6
|
+
obtained from TOPAS academic output files.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
from scipy.special import factorial
|
|
12
|
+
from sympy.functions.special.polynomials import assoc_legendre
|
|
13
|
+
import numpy as np
|
|
14
|
+
from matplotlib import cm, colors
|
|
15
|
+
|
|
16
|
+
# pylint: disable=C0103
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def Nlm(l, m):
|
|
20
|
+
"""Calculate normalization factor for spherical harmonics."""
|
|
21
|
+
N = np.sqrt(((2*l+1)/(4*np.pi))*(factorial((l-m))/factorial((l+m))))
|
|
22
|
+
return N
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def spherical_harmonic(l, m, theta, phi=None):
|
|
26
|
+
"""
|
|
27
|
+
Calculate real spherical harmonic Y_lm.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
l: degree of the harmonic
|
|
31
|
+
m: order of the harmonic (0 for m=0, positive for cos, negative for sin)
|
|
32
|
+
theta: polar angle(s)
|
|
33
|
+
phi: azimuthal angle(s), required if m != 0
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Real spherical harmonic values
|
|
37
|
+
"""
|
|
38
|
+
if m == 0:
|
|
39
|
+
return Nlm(l, 0) * assoc_legendre(l, 0, np.cos(theta))
|
|
40
|
+
else:
|
|
41
|
+
abs_m = abs(m)
|
|
42
|
+
legendre_part = assoc_legendre(l, abs_m, np.cos(theta))
|
|
43
|
+
norm = np.sqrt(2) * Nlm(l, abs_m)
|
|
44
|
+
if m > 0: # cosine term (p for plus)
|
|
45
|
+
return norm * np.cos(abs_m * phi) * legendre_part
|
|
46
|
+
else: # sine term (m for minus)
|
|
47
|
+
return norm * np.sin(abs_m * phi) * legendre_part
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Define the coefficient structure: (l, m) pairs
|
|
51
|
+
# m=0 for Y_l0, m>0 for cos terms (p), m<0 for sin terms (m)
|
|
52
|
+
COEFFICIENT_STRUCTURE = [
|
|
53
|
+
(0, 0), # c00
|
|
54
|
+
(2, 0), # c20
|
|
55
|
+
(2, 2), # c22p
|
|
56
|
+
(2, -2), # c22m
|
|
57
|
+
(4, 0), # c40
|
|
58
|
+
(4, 2), # c42p
|
|
59
|
+
(4, -2), # c42m
|
|
60
|
+
(4, 4), # c44p
|
|
61
|
+
(4, -4), # c44m
|
|
62
|
+
(6, 0), # c60
|
|
63
|
+
(6, 2), # c62p
|
|
64
|
+
(6, -2), # c62m
|
|
65
|
+
(6, 4), # c64p
|
|
66
|
+
(6, -4), # c64m
|
|
67
|
+
(6, 6), # c66p
|
|
68
|
+
(6, -6), # c66m
|
|
69
|
+
(8, 0), # c80
|
|
70
|
+
(8, 2), # c82p
|
|
71
|
+
(8, -2), # c82m
|
|
72
|
+
(8, 4), # c84p
|
|
73
|
+
(8, -4), # c84m
|
|
74
|
+
(8, 6), # c86p
|
|
75
|
+
(8, -6), # c86m
|
|
76
|
+
(8, 8), # c88p
|
|
77
|
+
(8, -8), # c88m
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
@click.command()
|
|
81
|
+
@click.argument('coefficients', nargs=-1, type=float, required=True)
|
|
82
|
+
@click.option('--output', '-o', default='sph_harm.svg',
|
|
83
|
+
help='Output filename for the plot (default: sph_harm.svg)')
|
|
84
|
+
@click.option('--resolution', '-r', default=200,
|
|
85
|
+
help='Angular resolution for the plot (default: 200)')
|
|
86
|
+
def cli(coefficients, output, resolution):
|
|
87
|
+
"""
|
|
88
|
+
Plot spherical harmonics functions from TOPAS coefficients.
|
|
89
|
+
|
|
90
|
+
Takes 1 to 25 coefficients and generates a 3D visualization of the spherical
|
|
91
|
+
harmonics function. Coefficients should be provided in order:
|
|
92
|
+
c00, c20, c22p, c22m, c40, c42p, c42m, c44p, c44m, c60, c62p, c62m, c64p, c64m,
|
|
93
|
+
c66p, c66m, c80, c82p, c82m, c84p, c84m, c86p, c86m, c88p, c88m
|
|
94
|
+
|
|
95
|
+
If fewer than 25 coefficients are provided, the remaining ones are assumed to be zero.
|
|
96
|
+
This allows for 2nd order (5 coeffs), 4th order (9 coeffs), 6th order (16 coeffs), etc.
|
|
97
|
+
|
|
98
|
+
Use '--' before coefficients if any are negative to prevent them being interpreted as options.
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
# 2nd order (5 coefficients)
|
|
102
|
+
sm-spharm -- 1 -0.54889 0.48504 0.74711 0.79916
|
|
103
|
+
|
|
104
|
+
# 8th order (25 coefficients)
|
|
105
|
+
sm-spharm -- 1 -0.54889 0.48504 0.74711 0.79916 -0.33178 0.16323 0.45369 \
|
|
106
|
+
-0.29782 -0.29214 -0.43949 0.01524 0.09042 0.85340 0.40316 \
|
|
107
|
+
-0.01881 1.47673 -0.55751 -0.68970 0.02621 -0.06565 -0.56472 \
|
|
108
|
+
1.00981 -0.12339 -0.25831
|
|
109
|
+
"""
|
|
110
|
+
# Validate number of coefficients
|
|
111
|
+
if len(coefficients) > 25:
|
|
112
|
+
raise click.BadParameter(
|
|
113
|
+
f'Too many coefficients: got {len(coefficients)}, maximum is 25'
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Pad coefficients with zeros if fewer than 25 provided
|
|
117
|
+
coefficients = list(coefficients) + [0.0] * (25 - len(coefficients))
|
|
118
|
+
|
|
119
|
+
# Create angular grid
|
|
120
|
+
phi, theta = np.mgrid[0:2*np.pi:complex(0, resolution), 0:np.pi:complex(0, resolution//2)]
|
|
121
|
+
|
|
122
|
+
# Calculate radial function R by summing all harmonics
|
|
123
|
+
R = np.zeros_like(theta, dtype=float)
|
|
124
|
+
|
|
125
|
+
for (l_val, m_val), coeff in zip(COEFFICIENT_STRUCTURE, coefficients):
|
|
126
|
+
if m_val == 0:
|
|
127
|
+
# For m=0, only theta is needed
|
|
128
|
+
y_lm_func = np.frompyfunc(lambda t, lv=l_val, mv=m_val: spherical_harmonic(lv, mv, t), 1, 1)
|
|
129
|
+
y_values = y_lm_func(theta).astype(float)
|
|
130
|
+
else:
|
|
131
|
+
# For m!=0, both theta and phi are needed
|
|
132
|
+
y_lm_func = np.frompyfunc(lambda t, p, lv=l_val, mv=m_val: spherical_harmonic(lv, mv, t, p), 2, 1)
|
|
133
|
+
y_values = y_lm_func(theta, phi).astype(float)
|
|
134
|
+
|
|
135
|
+
R += coeff * y_values
|
|
136
|
+
|
|
137
|
+
# Convert to Cartesian coordinates
|
|
138
|
+
x = R*np.sin(theta)*np.cos(phi)
|
|
139
|
+
y = R*np.sin(theta)*np.sin(phi)
|
|
140
|
+
z = R*np.cos(theta)
|
|
141
|
+
|
|
142
|
+
# Create 3D plot
|
|
143
|
+
# pylint: disable=E1101
|
|
144
|
+
_, ax = plt.subplots(subplot_kw=dict(projection='3d'), figsize=(14, 10))
|
|
145
|
+
norm = colors.Normalize()
|
|
146
|
+
ax.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=cm.jet(norm(R)))
|
|
147
|
+
|
|
148
|
+
ax.set_xlabel('X')
|
|
149
|
+
ax.set_ylabel('Y')
|
|
150
|
+
ax.set_zlabel('Z')
|
|
151
|
+
ax.set_title('Spherical Harmonics Visualization')
|
|
152
|
+
|
|
153
|
+
# Save the plot
|
|
154
|
+
plt.savefig(output, bbox_inches='tight')
|
|
155
|
+
click.echo(f'Plot saved to {output}')
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
# pylint: disable=E1120
|
|
160
|
+
cli()
|
smswaxs/topas_init.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Wed Dec 20 11:38:05 2023
|
|
4
|
+
|
|
5
|
+
@author: u0092172
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
def cli():
|
|
10
|
+
if len(sys.argv) != 2:
|
|
11
|
+
print("Usage: python topas_init.py <input_file>")
|
|
12
|
+
sys.exit(1)
|
|
13
|
+
with open(sys.argv[1],'r',encoding="utf-8") as inputfile:
|
|
14
|
+
data = inputfile.readlines()
|
|
15
|
+
|
|
16
|
+
for i, line in enumerate(data):
|
|
17
|
+
if 'xdd' in line:
|
|
18
|
+
data[i] = 'xdd '+sys.argv[1][:-3]+'xye\n'
|
|
19
|
+
|
|
20
|
+
with open(sys.argv[1], 'w',encoding="utf-8") as outfile:
|
|
21
|
+
outfile.writelines(data)
|
|
22
|
+
|
|
23
|
+
if __name__ == "__main__":
|
|
24
|
+
cli()
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This script will integrate 2D SAXS data collected on the Xenocs
|
|
3
|
+
Xeuss SWAXS instrument and subtract background data if necessary.
|
|
4
|
+
"""
|
|
5
|
+
import argparse as ap
|
|
6
|
+
import matplotlib.ticker as ticker
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
from matplotlib.colors import LogNorm
|
|
9
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
10
|
+
import pyFAI
|
|
11
|
+
from pyFAI.integrator.azimuthal import AzimuthalIntegrator
|
|
12
|
+
import fabio
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
# pylint: disable=C0103, unused-variable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def cli():
|
|
19
|
+
"""Integrate 2D saxs data"""
|
|
20
|
+
parser = ap.ArgumentParser(
|
|
21
|
+
description="Perform data reduction on data from Xeuss 2.0C")
|
|
22
|
+
|
|
23
|
+
parser.add_argument("sample", help="sample file (.edf)")
|
|
24
|
+
|
|
25
|
+
parser.add_argument("mask", help="mask file (.edf)")
|
|
26
|
+
parser.add_argument("--poni", help="PONI configuration file (.poni)")
|
|
27
|
+
parser.add_argument("--background", help="background file (.edf)")
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--opencl", help="Use OpenCL (requires pyopencl)", action="store_true")
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--bkg_factor", help="background correction factor", type=float, default=float(1))
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--lac", help="Convert to absolute units using the calculated thickness - LAC in cm^-1",
|
|
34
|
+
type=float, default=float(0))
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--thickness", help="thickness /cm used for trans. cor. (otherwise, trans. int. used)",
|
|
37
|
+
type=float, default=float(0))
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--xmin", help="Minimum value of radial unit (q)", type=float)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--xmax", help="Max value of radial unit (q)", type=float, default=3.8)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--raw2D", help="Display the 2D image in the lab frame", action="store_true")
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--noabs", help="No absorption correction", action="store_true")
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--unit", help="Radial unit for integration, q or 2th", type=str, default="q")
|
|
48
|
+
args = parser.parse_args()
|
|
49
|
+
|
|
50
|
+
if args.unit.lower() == "q":
|
|
51
|
+
UNIT = "q_A^-1"
|
|
52
|
+
|
|
53
|
+
elif args.unit.lower() == "2th":
|
|
54
|
+
UNIT = "2th_deg"
|
|
55
|
+
else:
|
|
56
|
+
raise ValueError("Unit must be 'q' or '2th'")
|
|
57
|
+
if args.xmin:
|
|
58
|
+
radran = (args.xmin, args.xmax)
|
|
59
|
+
else:
|
|
60
|
+
radran = (None)
|
|
61
|
+
file = fabio.open(args.sample)
|
|
62
|
+
mask = fabio.open(args.mask)
|
|
63
|
+
if args.noabs is False:
|
|
64
|
+
def abscor(tth, intensity, trans):
|
|
65
|
+
"""Absorption correction for WAXS according to the Bragg corrections section of the
|
|
66
|
+
XSACT user manual (2020)"""
|
|
67
|
+
if np.allclose(trans, 1, atol=5e-3):
|
|
68
|
+
return intensity
|
|
69
|
+
else:
|
|
70
|
+
fabs = (np.log(trans)*((1/np.cos(np.radians(tth)))-1)) / \
|
|
71
|
+
(np.exp(np.log(trans)*((1/np.cos(np.radians(tth)))-1))-1)
|
|
72
|
+
cor_int = intensity/fabs
|
|
73
|
+
return cor_int
|
|
74
|
+
else:
|
|
75
|
+
def abscor(_, intensity, __):
|
|
76
|
+
return intensity
|
|
77
|
+
|
|
78
|
+
if args.opencl:
|
|
79
|
+
intmeth = 'opencl'
|
|
80
|
+
else:
|
|
81
|
+
intmeth = 'csr'
|
|
82
|
+
if not args.poni:
|
|
83
|
+
geo = {
|
|
84
|
+
"detector": pyFAI.detectors.Detector(float(file.header['PSize_2']),
|
|
85
|
+
float(file.header['PSize_1'])),
|
|
86
|
+
"dist": float(file.header['SampleDistance']),
|
|
87
|
+
"poni1": float(file.header['PSize_2'])*float(file.header['Center_2']),
|
|
88
|
+
"poni2": float(file.header['PSize_1'])*float(file.header['Center_1']),
|
|
89
|
+
"rot1": 0,
|
|
90
|
+
"rot2": 0,
|
|
91
|
+
"rot3": 0,
|
|
92
|
+
"wavelength": float(file.header['WaveLength'])
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# poni = pyFAI.azimuthalIntegrator.AzimuthalIntegrator(**geo)
|
|
96
|
+
|
|
97
|
+
poni = AzimuthalIntegrator(**geo)
|
|
98
|
+
else:
|
|
99
|
+
poni = pyFAI.load(args.poni)
|
|
100
|
+
try:
|
|
101
|
+
transmission_sample = float(file.header['Transmission'])
|
|
102
|
+
except KeyError:
|
|
103
|
+
if np.allclose(args.lac, 0):
|
|
104
|
+
args.noabs = True
|
|
105
|
+
transmission_sample = 1
|
|
106
|
+
else:
|
|
107
|
+
if np.allclose(args.thickness, 0) is False:
|
|
108
|
+
transmission_sample = np.exp(-args.lac*args.thickness)
|
|
109
|
+
else:
|
|
110
|
+
args.noabs = True
|
|
111
|
+
transmission_sample = 1
|
|
112
|
+
norm_sample = (float(file.header['Intensity1']) *
|
|
113
|
+
float(file.header['ExposureTime']) *
|
|
114
|
+
(
|
|
115
|
+
(
|
|
116
|
+
float(file.header['PSize_1']) /
|
|
117
|
+
float(file.header['SampleDistance']))
|
|
118
|
+
)**2
|
|
119
|
+
)/1e-15
|
|
120
|
+
|
|
121
|
+
data_1D = poni.integrate1d(file.data,
|
|
122
|
+
1000, filename=args.sample[:-4]+'_1D.dat', correctSolidAngle=True,
|
|
123
|
+
method=intmeth, radial_range=radran, azimuth_range=(None),
|
|
124
|
+
unit=UNIT, mask=mask.data, normalization_factor=norm_sample,
|
|
125
|
+
metadata=None, error_model="poisson")
|
|
126
|
+
|
|
127
|
+
data_1D_corr = [data_1D[0], data_1D[1], data_1D[2]]
|
|
128
|
+
if args.unit.lower() == "2th":
|
|
129
|
+
data_1D_corr[1] = abscor(data_1D[0], data_1D[1], transmission_sample)
|
|
130
|
+
else:
|
|
131
|
+
data_1D_corr[1] = abscor(2*np.degrees(np.arcsin(((float(file.header['WaveLength'])/1e-10)
|
|
132
|
+
* data_1D[0])/(4*np.pi))), data_1D[1], transmission_sample)
|
|
133
|
+
data_2D = file.data/transmission_sample
|
|
134
|
+
|
|
135
|
+
if args.background is not None:
|
|
136
|
+
bkg = fabio.open(args.background)
|
|
137
|
+
if np.allclose(float(bkg.header['Transmission']), 1, atol=5e-3):
|
|
138
|
+
transmission_bkg = 1
|
|
139
|
+
else:
|
|
140
|
+
transmission_bkg = float(bkg.header['Transmission'])
|
|
141
|
+
|
|
142
|
+
norm_bkg = (float(bkg.header['Intensity1']) *
|
|
143
|
+
float(bkg.header['ExposureTime']) *
|
|
144
|
+
(
|
|
145
|
+
(
|
|
146
|
+
float(bkg.header['PSize_1']) /
|
|
147
|
+
float(bkg.header['SampleDistance']))
|
|
148
|
+
)**2
|
|
149
|
+
)/1e-15
|
|
150
|
+
bkg_1D = poni.integrate1d(bkg.data,
|
|
151
|
+
1000, filename=args.sample[:-4]+'_bkg1D.dat',
|
|
152
|
+
correctSolidAngle=True, method=intmeth,
|
|
153
|
+
radial_range=radran, azimuth_range=(None),
|
|
154
|
+
unit=UNIT, mask=mask.data, normalization_factor=norm_bkg,
|
|
155
|
+
metadata=None, error_model="poisson")
|
|
156
|
+
|
|
157
|
+
bkg_1D_corr = [bkg_1D[0], bkg_1D[1], bkg_1D[2]]
|
|
158
|
+
if args.unit.lower() == "2th":
|
|
159
|
+
bkg_1D_corr[1] = abscor(bkg_1D[0], bkg_1D[1], transmission_bkg)
|
|
160
|
+
else:
|
|
161
|
+
bkg_1D_corr[1] = abscor(
|
|
162
|
+
2*np.degrees(np.arcsin(((float(file.header['WaveLength'])/1e-10)*bkg_1D[0])/
|
|
163
|
+
(4*np.pi))), bkg_1D[1], transmission_bkg)
|
|
164
|
+
bkg_2D = bkg.data/transmission_bkg * args.bkg_factor
|
|
165
|
+
data_I = data_1D_corr[1]-bkg_1D_corr[1]
|
|
166
|
+
data_2I = data_2D - bkg_2D
|
|
167
|
+
data_sig = abs(data_1D_corr[2])+abs(bkg_1D_corr[2])
|
|
168
|
+
else:
|
|
169
|
+
data_I = data_1D_corr[1]
|
|
170
|
+
data_2I = data_2D
|
|
171
|
+
data_sig = data_1D_corr[2]
|
|
172
|
+
|
|
173
|
+
data_2I2 = poni.integrate2d(data_2I, 3000, 3600, unit=UNIT)
|
|
174
|
+
|
|
175
|
+
I2, q2, chi2 = data_2I2
|
|
176
|
+
|
|
177
|
+
if np.min(I2) < 0:
|
|
178
|
+
I2min = 1e-3
|
|
179
|
+
else:
|
|
180
|
+
I2min = np.min(I2)
|
|
181
|
+
I2max = np.max(I2)
|
|
182
|
+
|
|
183
|
+
fig, axs = plt.subplot_mosaic(
|
|
184
|
+
[['(a)', '(b)']], figsize=(7, 4), constrained_layout=True)
|
|
185
|
+
if args.raw2D is False:
|
|
186
|
+
fig1 = axs['(a)'].imshow(I2, origin="lower",
|
|
187
|
+
extent=[q2.min(), q2.max(), chi2.min(), chi2.max()],
|
|
188
|
+
norm=LogNorm(vmin=I2min, vmax=I2max), cmap=plt.get_cmap('cividis'),
|
|
189
|
+
aspect="auto")
|
|
190
|
+
if args.unit.lower() == "q":
|
|
191
|
+
axs['(a)'].set_xlabel(r'q /$\AA^{-1}$', fontsize=8)
|
|
192
|
+
else:
|
|
193
|
+
axs['(a)'].set_xlabel(r'2$\theta$ /$^{\circ}$', fontsize=8)
|
|
194
|
+
axs['(a)'].set_ylabel(r'$\phi/^{\circ}$', fontsize=8)
|
|
195
|
+
else:
|
|
196
|
+
fig1 = axs['(a)'].imshow(data_2I, norm=LogNorm(
|
|
197
|
+
vmin=I2min, vmax=I2max), cmap=plt.get_cmap('cividis'), aspect="auto")
|
|
198
|
+
axs['(a)'].set_xlabel('X /px', fontsize=8)
|
|
199
|
+
axs['(a)'].set_ylabel('Z /px', fontsize=8)
|
|
200
|
+
divider = make_axes_locatable(axs['(a)'])
|
|
201
|
+
cax = divider.append_axes("top", size="5%", pad=0.05)
|
|
202
|
+
|
|
203
|
+
cbar = plt.colorbar(fig1, cax=cax, orientation="horizontal")
|
|
204
|
+
cbar.ax.tick_params(labelsize=8)
|
|
205
|
+
cax.xaxis.set_ticks_position("top")
|
|
206
|
+
|
|
207
|
+
axs['(a)'].tick_params(labelsize=8)
|
|
208
|
+
|
|
209
|
+
fig2 = axs['(b)'].plot(data_1D[0], data_I, linewidth=0.5)
|
|
210
|
+
|
|
211
|
+
# axs['(b)'].set_xscale('log')
|
|
212
|
+
# axs['(b)'].set_yscale('log')
|
|
213
|
+
if args.unit.lower() == "q":
|
|
214
|
+
axs['(b)'].set_xlabel(r'q /$\AA^{-1}$', fontsize=8)
|
|
215
|
+
else:
|
|
216
|
+
axs['(b)'].set_xlabel(r'2$\theta$ /$^{\circ}$', fontsize=8)
|
|
217
|
+
axs['(b)'].set_ylabel('I(q) /Arb.', fontsize=8)
|
|
218
|
+
axs['(b)'].get_xaxis().set_major_formatter(ticker.ScalarFormatter())
|
|
219
|
+
# axs['(b)'].get_yaxis().set_major_formatter(ticker.ScalarFormatter())
|
|
220
|
+
# axs['(b)'].ticklabel_format(style='sci', axis='both',
|
|
221
|
+
# scilimits=(0, 0), useMathText=True)
|
|
222
|
+
# axs['(b)'].set_xticks([0.01, 0.1, 1])
|
|
223
|
+
# axs['(b)'].get_xaxis().set_major_formatter(ticker.ScalarFormatter())
|
|
224
|
+
axs['(b)'].tick_params(labelsize=8)
|
|
225
|
+
# plt.show()
|
|
226
|
+
plt.savefig(args.sample[:-4]+".png", bbox_inches="tight",dpi=600)
|
|
227
|
+
|
|
228
|
+
data_out = np.column_stack((data_1D[0], data_I, data_sig))
|
|
229
|
+
if args.unit.lower() == "q":
|
|
230
|
+
header_str = "q(A^-1)\tI(Arb.)\tSigma(Arb.)"
|
|
231
|
+
np.savetxt(args.sample[:-4]+"_subtracted.dat",
|
|
232
|
+
data_out, delimiter='\t', fmt='%s', header=header_str)
|
|
233
|
+
elif args.unit.lower() == "2th":
|
|
234
|
+
np.savetxt(args.sample[:-4]+".xye",
|
|
235
|
+
data_out, delimiter='\t', fmt='%s')
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
cli()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""This script converts XSACT or Foxtrot dat files to HDF5 format for archival storage"""
|
|
2
|
+
import sys
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
|
+
import h5py
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
def get_line_number(phrase: str, file_name: str):
|
|
10
|
+
"""Determine the line in the text file containing the specified str"""
|
|
11
|
+
with open(file_name, encoding="utf-8") as f:
|
|
12
|
+
for i, line in enumerate(f, 1):
|
|
13
|
+
p = re.compile(phrase)
|
|
14
|
+
if p.match(line) is not None:
|
|
15
|
+
return i
|
|
16
|
+
|
|
17
|
+
def process_dat_file(file_path: str, hdf):
|
|
18
|
+
"""determine the header and data block in the dat file and create an
|
|
19
|
+
hdf5 dataset"""
|
|
20
|
+
dataset_name = file_path.stem
|
|
21
|
+
with open(file_path, encoding="utf-8") as infile:
|
|
22
|
+
lines = infile.read().splitlines()
|
|
23
|
+
lines = [line.replace('##', ' ') for line in lines]
|
|
24
|
+
meta = {s.split(" ")[0].replace("# ", "").strip(): s.split(
|
|
25
|
+
" ")[-1].strip() for s in lines if '#' in s}
|
|
26
|
+
meta = {key.replace("#",""): value.replace("#","") for key,value in meta.items()}
|
|
27
|
+
meta = {key: value for key, value in meta.items() if key}
|
|
28
|
+
head = get_line_number('.*q\\([AÅ].*',file_path)
|
|
29
|
+
meta["columns"] = lines[head-1].split()
|
|
30
|
+
data = np.genfromtxt(file_path,skip_header=head,usecols=(0,1,2),encoding="utf-8")
|
|
31
|
+
|
|
32
|
+
dset = hdf.create_dataset(
|
|
33
|
+
dataset_name, data=data, compression='gzip')
|
|
34
|
+
|
|
35
|
+
for attr_name, attr_value in meta.items():
|
|
36
|
+
dset.attrs[attr_name] = attr_value
|
|
37
|
+
|
|
38
|
+
def add_dats_to_hdf5(directory: str, hdf5_filename: str):
|
|
39
|
+
"""find all dat files in the current folder and subfolders, then
|
|
40
|
+
add them to a single hdf5 file"""
|
|
41
|
+
files = list(Path(directory).glob('**/*.dat'))
|
|
42
|
+
hdf = h5py.File(hdf5_filename,'w')
|
|
43
|
+
with ThreadPoolExecutor() as executor:
|
|
44
|
+
executor.map(lambda file: process_dat_file(file, hdf), files)
|
|
45
|
+
hdf.close()
|
|
46
|
+
|
|
47
|
+
if __name__ == '__main__':
|
|
48
|
+
add_dats_to_hdf5(sys.argv[1],sys.argv[2])
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""This script converts Xeuss edf files to HDF5 format for archival storage"""
|
|
2
|
+
import argparse as ap
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
+
import fabio
|
|
6
|
+
import h5py
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def process_edf_file(file_path: Path, hdf):
|
|
11
|
+
"""Create hdf5 dataset from edf file"""
|
|
12
|
+
infile = fabio.open(file_path)
|
|
13
|
+
dataset_name = file_path.stem
|
|
14
|
+
|
|
15
|
+
dset = hdf.create_dataset(
|
|
16
|
+
dataset_name, data=infile.data, compression='gzip')
|
|
17
|
+
|
|
18
|
+
for attr_name, attr_value in infile.header.items():
|
|
19
|
+
dset.attrs[attr_name] = attr_value
|
|
20
|
+
|
|
21
|
+
def add_edfs_to_hdf5():
|
|
22
|
+
"""Find all edf files in all subdirectories of the specified
|
|
23
|
+
path and add them to the hdf5"""
|
|
24
|
+
parser = ap.ArgumentParser(description="Convert Xeuss edf files to HDF5 format")
|
|
25
|
+
parser.add_argument("directory", help="Directory containing edf files")
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
if ".edf" in args.directory:
|
|
28
|
+
files = [Path(args.directory)]
|
|
29
|
+
args.hdf5_filename = files[0].with_suffix('.h5').name
|
|
30
|
+
else:
|
|
31
|
+
files = list(Path(args.directory).glob('**/*.edf'))
|
|
32
|
+
args.hdf5_filename = Path(args.directory).name + '.h5'
|
|
33
|
+
hdf = h5py.File(args.hdf5_filename,'w')
|
|
34
|
+
with ThreadPoolExecutor() as executor:
|
|
35
|
+
executor.map(lambda file: process_edf_file(file, hdf), files)
|
|
36
|
+
hdf.close()
|
|
37
|
+
|
|
38
|
+
if __name__ == '__main__':
|
|
39
|
+
add_edfs_to_hdf5()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""This script converts Xeuss edf files to TIFF format + JSON for archival storage"""
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import json
|
|
5
|
+
import fabio
|
|
6
|
+
from PIL import Image
|
|
7
|
+
|
|
8
|
+
file_path = Path(sys.argv[1])
|
|
9
|
+
|
|
10
|
+
infile = fabio.open(file_path)
|
|
11
|
+
|
|
12
|
+
output_path = file_path.with_suffix(".tif")
|
|
13
|
+
json_output = file_path.with_suffix(".json")
|
|
14
|
+
|
|
15
|
+
"""The original edf files contain 32-bit floats, so the F mode is used to
|
|
16
|
+
retain precision, however, mask files are only 8-bit and will throw an error,
|
|
17
|
+
in this case we fall back to auto detection"""
|
|
18
|
+
try:
|
|
19
|
+
image = Image.fromarray(infile.data,mode='F')
|
|
20
|
+
except ValueError:
|
|
21
|
+
image = Image.fromarray(infile.data)
|
|
22
|
+
|
|
23
|
+
image.save(output_path,format='TIFF',compression='tiff_deflate')
|
|
24
|
+
|
|
25
|
+
with open(json_output, 'w',encoding="utf-8") as json_f:
|
|
26
|
+
json.dump(infile.header, json_f, indent=4)
|
smswaxs/xrdml_conv.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Convert xrdml files to xy format for use in TOPAS. Extract metadata, but drop units from pint quantities.
|
|
4
|
+
@author: u0092172
|
|
5
|
+
"""
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import json
|
|
8
|
+
import argparse as ap
|
|
9
|
+
import numpy as np
|
|
10
|
+
from fairmat_readers_xrd import read_file
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _to_magnitude(obj):
|
|
14
|
+
"""Recursively convert pint quantities to their magnitudes."""
|
|
15
|
+
if hasattr(obj, 'magnitude'):
|
|
16
|
+
return obj.magnitude
|
|
17
|
+
if isinstance(obj, dict):
|
|
18
|
+
return {k: _to_magnitude(v) for k, v in obj.items()}
|
|
19
|
+
if isinstance(obj, (list, tuple)):
|
|
20
|
+
converted = [_to_magnitude(v) for v in obj]
|
|
21
|
+
return tuple(converted) if isinstance(obj, tuple) else converted
|
|
22
|
+
return obj
|
|
23
|
+
|
|
24
|
+
def cli():
|
|
25
|
+
"""Convert xrdml files to xy format for use in TOPAS"""
|
|
26
|
+
parser = ap.ArgumentParser(description='Sort out xrdml files')
|
|
27
|
+
parser.add_argument("input", help="xrdml file")
|
|
28
|
+
args = parser.parse_args() # map arguments to args
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
data = read_file(args.input)
|
|
33
|
+
two_theta = data['2Theta'].magnitude
|
|
34
|
+
intensity = data['intensity'].magnitude
|
|
35
|
+
|
|
36
|
+
metadata = _to_magnitude(data['metadata'])
|
|
37
|
+
|
|
38
|
+
outfile = Path(args.input).with_suffix('.xy')
|
|
39
|
+
|
|
40
|
+
np.savetxt(outfile, np.column_stack((two_theta, intensity)), fmt='%1.6f', delimiter='\t')
|
|
41
|
+
|
|
42
|
+
meta_outfile = Path(args.input).with_suffix('.json')
|
|
43
|
+
with open(meta_outfile, 'w') as f:
|
|
44
|
+
json.dump(metadata, f, indent=4)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == '__main__':
|
|
48
|
+
cli()
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: SusMat-SWAXS
|
|
3
|
+
Version: 2.1.3
|
|
4
|
+
Summary: A collection of python scripts for uploading analytical data to iRODS
|
|
5
|
+
Author: Samuel Eyley
|
|
6
|
+
Author-email: Samuel Eyley <susmat-swaxspypi.xepsj@simplelogin.com>
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Dist: pyfai>2024.10.0
|
|
11
|
+
Requires-Dist: pyopencl
|
|
12
|
+
Requires-Dist: click
|
|
13
|
+
Requires-Dist: pyside6
|
|
14
|
+
Requires-Dist: h5py
|
|
15
|
+
Requires-Dist: matplotlib
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: scipy
|
|
18
|
+
Requires-Dist: sympy
|
|
19
|
+
Requires-Dist: fabio
|
|
20
|
+
Requires-Dist: hdf5plugin
|
|
21
|
+
Requires-Dist: pillow
|
|
22
|
+
Requires-Dist: fairmat-readers-xrd>=0.0.8
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Project-URL: Homepage, https://github.com/Sustainable-Materials-Lab/SusMat-SWAXS
|
|
25
|
+
Project-URL: Issues, https://github.com/Sustainable-Materials-Lab/SusMat-SWAXS/issues
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# SWAXS tools
|
|
29
|
+
|
|
30
|
+
## Description
|
|
31
|
+
This is a collection of scripts to perform data conversion and integration on X-ray scattering data, specifically for data collected on the Xenocs Xeuss SWAXS instrument.
|
|
32
|
+
|
|
33
|
+
**Available tools:**
|
|
34
|
+
- `sm-datconv` - Converts Xenocs format 1D data (.dat) to TOPAS compatible .xye format
|
|
35
|
+
- `sm-edfh5` - Convert EDF files to HDF5 format with metadata preservation
|
|
36
|
+
- `sm-saxsint` - Integrate 2D SAXS data from Xenocs Xeuss instruments with background subtraction
|
|
37
|
+
- `sm-waxsint` - Integrate 2D WAXS data from Xenocs Xeuss instruments with absorption correction
|
|
38
|
+
- `sm-spharm` - Visualize spherical harmonics functions from TOPAS coefficients
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
Install in a virtual environment to avoid dependency conflicts:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install SusMat-SWAXS
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
All scripts are available as command-line tools after installation. Use the `--help` flag with any command to see detailed usage information.
|
|
50
|
+
|
|
51
|
+
### sm-datconv
|
|
52
|
+
Converts Xenocs format 1D data (.dat) to TOPAS compatible .xye format.
|
|
53
|
+
|
|
54
|
+
**Input:** `.dat` file with tab-separated columns for q, I(q) (and optionally sigma).
|
|
55
|
+
|
|
56
|
+
**Usage:**
|
|
57
|
+
```bash
|
|
58
|
+
sm-datconv <filename_without_extension>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Output:** `.xye` file with tab-separated columns for 2θ, I, and error (if present in input).
|
|
62
|
+
|
|
63
|
+
**Batch processing example (PowerShell):**
|
|
64
|
+
```powershell
|
|
65
|
+
Get-ChildItem -Filter *.dat | ForEach-Object -Process {sm-datconv $_.BaseName}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### sm-xrdmlconv
|
|
69
|
+
Converts Malvern PANalytical format 1D data (.xrdml) to TOPAS compatible .xy format.
|
|
70
|
+
|
|
71
|
+
**Input:** `.xrdml` file .
|
|
72
|
+
|
|
73
|
+
**Usage:**
|
|
74
|
+
```bash
|
|
75
|
+
sm-xrdmlconv <filename_without_extension>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Output:** `.xy` file with tab-separated columns for 2θ and I and a `.json` file containing metadata.
|
|
79
|
+
|
|
80
|
+
### sm-edfh5
|
|
81
|
+
Convert multiple EDF files to a single HDF5 file with metadata preservation.
|
|
82
|
+
|
|
83
|
+
**Usage:**
|
|
84
|
+
```bash
|
|
85
|
+
sm-edfh5 <path to directory containing edf files>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### sm-saxsint
|
|
89
|
+
Integrate 2D SAXS data from Xenocs Xeuss instruments with background subtraction and absorption correction.
|
|
90
|
+
|
|
91
|
+
**Usage:**
|
|
92
|
+
```bash
|
|
93
|
+
sm-saxsint <sample.edf> <mask.edf> [OPTIONS]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Key options:**
|
|
97
|
+
- `--poni <file.poni>` - PONI calibration file
|
|
98
|
+
- `--background <bkg.edf>` - Background file for subtraction
|
|
99
|
+
- `--bkg_factor <float>` - Background scaling factor (default: 1.0)
|
|
100
|
+
- `--unit <q|2th>` - Radial unit for integration (default: q)
|
|
101
|
+
- `--xmin <float>` - Minimum q value (default: 0.004)
|
|
102
|
+
- `--xmax <float>` - Maximum q value (default: 0.7)
|
|
103
|
+
- `--opencl` - Use OpenCL acceleration (requires pyopencl)
|
|
104
|
+
- `--raw2D` - Display 2D image in lab frame
|
|
105
|
+
|
|
106
|
+
**Output:**
|
|
107
|
+
- `*_1D.dat` - Integrated 1D data
|
|
108
|
+
- `*_subtracted.dat` - Background-subtracted data
|
|
109
|
+
- `*.svg` - Visualization plot
|
|
110
|
+
|
|
111
|
+
### sm-waxsint
|
|
112
|
+
Integrate 2D WAXS data from Xenocs Xeuss instruments with absorption correction.
|
|
113
|
+
|
|
114
|
+
**Usage:**
|
|
115
|
+
```bash
|
|
116
|
+
sm-waxsint <sample.edf> <mask.edf> [OPTIONS]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Key options:**
|
|
120
|
+
- `--poni <file.poni>` - PONI calibration file
|
|
121
|
+
- `--background <bkg.edf>` - Background file for subtraction
|
|
122
|
+
- `--bkg_factor <float>` - Background scaling factor (default: 1.0)
|
|
123
|
+
- `--unit <q|2th>` - Radial unit for integration (default: q)
|
|
124
|
+
- `--xmin <float>` - Minimum q value (default: 0.36)
|
|
125
|
+
- `--xmax <float>` - Maximum q value (default: 3.8)
|
|
126
|
+
- `--lac <float>` - Linear absorption coefficient in cm⁻¹ for thickness correction
|
|
127
|
+
- `--thickness <float>` - Sample thickness in cm for transmission correction
|
|
128
|
+
- `--noabs` - Disable absorption correction
|
|
129
|
+
- `--opencl` - Use OpenCL acceleration (requires pyopencl)
|
|
130
|
+
- `--raw2D` - Display 2D image in lab frame
|
|
131
|
+
|
|
132
|
+
**Output:**
|
|
133
|
+
- `*_1D.dat` - Integrated 1D data
|
|
134
|
+
- `*_subtracted.dat` - Background-subtracted, absorption-corrected data
|
|
135
|
+
- `*.svg` - Visualization plot
|
|
136
|
+
|
|
137
|
+
### sm-spharm
|
|
138
|
+
Visualize spherical harmonics functions using coefficients from TOPAS academic output files.
|
|
139
|
+
|
|
140
|
+
**Usage:**
|
|
141
|
+
```bash
|
|
142
|
+
sm-spharm C00 C20 C22P C22M C40 C42P C42M C44P C44M C60 C62P C62M C64P C64M C66P C66M C80 C82P C82M C84P C84M C86P C86M C88P C88M [OPTIONS]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Arguments:** 25 spherical harmonics coefficients (c00 through c88m) from TOPAS output file
|
|
146
|
+
|
|
147
|
+
**Options:**
|
|
148
|
+
- `--output <filename>` / `-o <filename>` - Output filename for the plot (default: sph_harm.svg)
|
|
149
|
+
- `--resolution <int>` / `-r <int>` - Angular resolution for the plot (default: 200)
|
|
150
|
+
|
|
151
|
+
**Example:**
|
|
152
|
+
```bash
|
|
153
|
+
sm-spharm 1 -0.54889 0.48504 0.74711 0.79916 -0.33178 0.16323 0.45369 \
|
|
154
|
+
-0.29782 -0.29214 -0.43949 0.01524 0.09042 0.85340 0.40316 \
|
|
155
|
+
-0.01881 1.47673 -0.55751 -0.68970 0.02621 -0.06565 -0.56472 \
|
|
156
|
+
1.00981 -0.12339 -0.25831 --output my_spherical_harmonics.svg
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Output:** 3D visualization plot showing the spherical harmonics function in real space
|
|
160
|
+
|
|
161
|
+
## Support
|
|
162
|
+
Submit an issue on github: https://github.com/Sustainable-Materials-Lab/SusMat-SWAXS
|
|
163
|
+
|
|
164
|
+
## Contributing
|
|
165
|
+
Contact Samuel Eyley if you are interested in contributing.
|
|
166
|
+
|
|
167
|
+
## Authors and acknowledgment
|
|
168
|
+
Originally authored by Samuel Eyley.
|
|
169
|
+
|
|
170
|
+
Significant portions of code were developed with the help of Github Copilot (2025).
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
MIT License
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
smswaxs/__init__.py,sha256=kXT2_MXyf8y-GSFimdeq6rMi8To60hKqiSIyhrS0-Pk,962
|
|
2
|
+
smswaxs/dat_conv.py,sha256=MuHSmV9Yd9pJVAkkLOo7sVzlmgwXneAjYauhxnuurMo,4373
|
|
3
|
+
smswaxs/saxs_integration.py,sha256=qNNPoK26n0qS0ZsmMzhLINkpR9BzDkksc2VGfWeE4MI,6472
|
|
4
|
+
smswaxs/saxs_integration_xeuss.py,sha256=WJu4IvkqY1LfcGokT-KIP0wNxKvRN47AmOy34vo-0OI,9764
|
|
5
|
+
smswaxs/sph_harm.py,sha256=PKpSpLVXYoCjLWPb6bzzXPTpnheQ3LCL6jHd6KDuVeA,5554
|
|
6
|
+
smswaxs/topas_init.py,sha256=5zRSGK0Qz8cqFdcL9EJQ9Oe-q7BjBe_EByCW2TikQbI,563
|
|
7
|
+
smswaxs/waxs_integration_xeuss.py,sha256=a9-3vNAFH8dAHc0PyFD-oT4ksdjYABsfJoJvuhFj6ps,9925
|
|
8
|
+
smswaxs/xeuss_dat_h5_multi.py,sha256=U2BY8fh0tEXmIWewfthzqltT1aEIeluoJGXg6b7lTr0,1979
|
|
9
|
+
smswaxs/xeuss_edf_h5_multi.py,sha256=kiG8qG7LSlHm7d6sCCgC-lkSRwSykUa-BIYsSplp31Q,1370
|
|
10
|
+
smswaxs/xeuss_edf_tiff.py,sha256=25XxAk9o8C3zV--pu8EyMtfQXbqdRymYUcdKFL3fsHE,807
|
|
11
|
+
smswaxs/xrdml_conv.py,sha256=yM0h037OqX2AxMdim6UUUAD3AUCc7M7hL-yRaglYmuA,1441
|
|
12
|
+
susmat_swaxs-2.1.3.dist-info/WHEEL,sha256=M4DeIjVCA49okfALADZoWX5JOGwnmHb-JOpQHtI-1c0,80
|
|
13
|
+
susmat_swaxs-2.1.3.dist-info/entry_points.txt,sha256=Jbp7q-UnvB425gUtK4fx7uUDUg2M1zhs-RrDLzVi5kw,313
|
|
14
|
+
susmat_swaxs-2.1.3.dist-info/METADATA,sha256=wovQaqxLi5iqSX6aOCZj-0Lhx-zHpYTkh17zx-DQijo,5888
|
|
15
|
+
susmat_swaxs-2.1.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
sm-datconv = smswaxs.dat_conv:cli
|
|
3
|
+
sm-edfh5 = smswaxs.xeuss_edf_h5_multi:add_edfs_to_hdf5
|
|
4
|
+
sm-saxsint = smswaxs.saxs_integration_xeuss:cli
|
|
5
|
+
sm-spharm = smswaxs.sph_harm:cli
|
|
6
|
+
sm-topasinit = smswaxs.topas_init:cli
|
|
7
|
+
sm-waxsint = smswaxs.waxs_integration_xeuss:cli
|
|
8
|
+
sm-xrdmlconv = smswaxs.xrdml_conv:cli
|
|
9
|
+
|