nxs-analysis-tools 0.0.43__tar.gz → 0.0.44__tar.gz
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 nxs-analysis-tools might be problematic. Click here for more details.
- {nxs_analysis_tools-0.0.43/src/nxs_analysis_tools.egg-info → nxs_analysis_tools-0.0.44}/PKG-INFO +3 -2
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/pyproject.toml +1 -1
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/_meta/__init__.py +1 -1
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/chess.py +31 -9
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/datareduction.py +2 -2
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/pairdistribution.py +13 -7
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44/src/nxs_analysis_tools.egg-info}/PKG-INFO +3 -2
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/SOURCES.txt +2 -1
- nxs_analysis_tools-0.0.44/tests/test_symmetrizer_rectangular_plane.py +383 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/LICENSE +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/MANIFEST.in +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/README.md +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/setup.cfg +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/setup.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/__init__.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/fitting.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/dependency_links.txt +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/requires.txt +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/top_level.txt +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_chess.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_chess_fitting.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_datareduction.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_fitting.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_lmfit.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_mask_plotting.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_pairdistribution.py +0 -0
- {nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_plot_slice_with_ndarray.py +0 -0
{nxs_analysis_tools-0.0.43/src/nxs_analysis_tools.egg-info → nxs_analysis_tools-0.0.44}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: nxs-analysis-tools
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.44
|
|
4
4
|
Summary: Reduce and transform nexus format (.nxs) scattering data.
|
|
5
5
|
Author-email: "Steven J. Gomez Alvarado" <stevenjgomez@ucsb.edu>
|
|
6
6
|
License: MIT License
|
|
@@ -66,6 +66,7 @@ Requires-Dist: sphinx-autobuild>=2021.3.14; extra == "dev"
|
|
|
66
66
|
Requires-Dist: sphinx-copybutton>=0.5.0; extra == "dev"
|
|
67
67
|
Requires-Dist: sphinxext-opengraph>=0.6.3; extra == "dev"
|
|
68
68
|
Requires-Dist: twine>=4.0.1; extra == "dev"
|
|
69
|
+
Dynamic: license-file
|
|
69
70
|
|
|
70
71
|
# nxs-analysis-tools
|
|
71
72
|
|
|
@@ -50,6 +50,8 @@ class TempDependence:
|
|
|
50
50
|
-------
|
|
51
51
|
set_temperatures(temperatures):
|
|
52
52
|
Set the list of temperatures for the datasets.
|
|
53
|
+
find_temperatures():
|
|
54
|
+
Set the list of temperatures by automatically scanning the sample directory.
|
|
53
55
|
set_sample_directory(path):
|
|
54
56
|
Set the directory path where the datasets are located.
|
|
55
57
|
initialize():
|
|
@@ -105,7 +107,7 @@ class TempDependence:
|
|
|
105
107
|
Initialize the TempDependence class with default values.
|
|
106
108
|
"""
|
|
107
109
|
|
|
108
|
-
self.sample_directory =
|
|
110
|
+
self.sample_directory = None
|
|
109
111
|
self.xlabel = ''
|
|
110
112
|
self.datasets = {}
|
|
111
113
|
self.temperatures = []
|
|
@@ -127,6 +129,31 @@ class TempDependence:
|
|
|
127
129
|
"""
|
|
128
130
|
self.temperatures = temperatures
|
|
129
131
|
|
|
132
|
+
def find_temperatures(self):
|
|
133
|
+
"""
|
|
134
|
+
Set the list of temperatures by automatically scanning the sample directory.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
# Assert that self.sample_directory must exist
|
|
138
|
+
if self.sample_directory is None:
|
|
139
|
+
raise ValueError("Sample directory is not set. Use set_sample_directory(path) first.")
|
|
140
|
+
|
|
141
|
+
# Clear existing temperatures
|
|
142
|
+
self.temperatures = []
|
|
143
|
+
|
|
144
|
+
# Search for nxrefine .nxs files
|
|
145
|
+
for item in os.listdir(self.sample_directory):
|
|
146
|
+
pattern = r'_(\d+)\.nxs'
|
|
147
|
+
match = re.search(pattern, item)
|
|
148
|
+
if match:
|
|
149
|
+
# Identify temperature
|
|
150
|
+
temperature = match.group(1)
|
|
151
|
+
self.temperatures.append(temperature)
|
|
152
|
+
# Convert all temperatures to int temporarily to sort temperatures list
|
|
153
|
+
self.temperatures = [int(t) for t in self.temperatures]
|
|
154
|
+
self.temperatures.sort()
|
|
155
|
+
self.temperatures = [str(t) for t in self.temperatures]
|
|
156
|
+
|
|
130
157
|
def set_sample_directory(self, path):
|
|
131
158
|
"""
|
|
132
159
|
Set the directory path where the datasets are located.
|
|
@@ -172,6 +199,9 @@ class TempDependence:
|
|
|
172
199
|
if temperatures_list:
|
|
173
200
|
temperatures_list = [str(t) for t in temperatures_list]
|
|
174
201
|
|
|
202
|
+
# Clear existing temperatures before loading files
|
|
203
|
+
self.temperatures = []
|
|
204
|
+
|
|
175
205
|
# Identify files to load
|
|
176
206
|
items_to_load = []
|
|
177
207
|
# Search for nxrefine .nxs files
|
|
@@ -179,7 +209,6 @@ class TempDependence:
|
|
|
179
209
|
pattern = r'_(\d+)\.nxs'
|
|
180
210
|
match = re.search(pattern, item)
|
|
181
211
|
if match:
|
|
182
|
-
print(f'Found {item}')
|
|
183
212
|
# Identify temperature
|
|
184
213
|
temperature = match.group(1)
|
|
185
214
|
# print(f'Temperature = {temperature}')
|
|
@@ -188,7 +217,6 @@ class TempDependence:
|
|
|
188
217
|
self.temperatures.append(temperature)
|
|
189
218
|
items_to_load.append(item)
|
|
190
219
|
# print(f'Preparing to load {temperature} K data: {item}')
|
|
191
|
-
|
|
192
220
|
# Convert all temperatures to int temporarily to sort temperatures list before loading
|
|
193
221
|
self.temperatures = [int(t) for t in self.temperatures]
|
|
194
222
|
|
|
@@ -238,9 +266,6 @@ class TempDependence:
|
|
|
238
266
|
temperature_folders.sort() # Sort from low to high T
|
|
239
267
|
temperature_folders = [str(i) for i in temperature_folders] # Convert to strings
|
|
240
268
|
|
|
241
|
-
print('Found temperature folders:')
|
|
242
|
-
[print('[' + str(i) + '] ' + folder) for i, folder in enumerate(temperature_folders)]
|
|
243
|
-
|
|
244
269
|
self.temperatures = temperature_folders
|
|
245
270
|
|
|
246
271
|
if temperatures_list is not None:
|
|
@@ -251,9 +276,6 @@ class TempDependence:
|
|
|
251
276
|
for file in os.listdir(os.path.join(self.sample_directory, T)):
|
|
252
277
|
if file.endswith(file_ending):
|
|
253
278
|
filepath = os.path.join(self.sample_directory, T, file)
|
|
254
|
-
print('-----------------------------------------------')
|
|
255
|
-
print('Loading ' + T + ' K indexed .nxs files...')
|
|
256
|
-
print('Found ' + filepath)
|
|
257
279
|
|
|
258
280
|
# Load dataset at each temperature
|
|
259
281
|
self.datasets[T] = load_data(filepath)
|
{nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/datareduction.py
RENAMED
|
@@ -15,7 +15,7 @@ from scipy import ndimage
|
|
|
15
15
|
|
|
16
16
|
# Specify items on which users are allowed to perform standalone imports
|
|
17
17
|
__all__ = ['load_data', 'load_transform', 'plot_slice', 'Scissors',
|
|
18
|
-
'reciprocal_lattice_params', 'rotate_data',
|
|
18
|
+
'reciprocal_lattice_params', 'rotate_data', 'rotate_data_2D'
|
|
19
19
|
'array_to_nxdata', 'Padder']
|
|
20
20
|
|
|
21
21
|
|
|
@@ -853,7 +853,7 @@ def rotate_data(data, lattice_angle, rotation_angle, rotation_axis, printout=Fal
|
|
|
853
853
|
(data[data.axes[0]], data[data.axes[1]], data[data.axes[2]]))
|
|
854
854
|
|
|
855
855
|
|
|
856
|
-
def
|
|
856
|
+
def rotate_data_2D(data, lattice_angle, rotation_angle):
|
|
857
857
|
"""
|
|
858
858
|
Rotates 2D data.
|
|
859
859
|
|
{nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/pairdistribution.py
RENAMED
|
@@ -197,7 +197,7 @@ class Symmetrizer2D:
|
|
|
197
197
|
# Scale and skew counts
|
|
198
198
|
skew_angle_adj = 90 - self.skew_angle
|
|
199
199
|
|
|
200
|
-
scale2 =
|
|
200
|
+
scale2 = q1.max()/q2.max()
|
|
201
201
|
counts_unscaled2 = ndimage.affine_transform(counts,
|
|
202
202
|
Affine2D().scale(scale2, 1).inverted().get_matrix()[:2, :2],
|
|
203
203
|
offset=[-(1 - scale2) * counts.shape[
|
|
@@ -253,7 +253,7 @@ class Symmetrizer2D:
|
|
|
253
253
|
order=0,
|
|
254
254
|
)
|
|
255
255
|
|
|
256
|
-
scale2 =
|
|
256
|
+
scale2 = q1.max()/q2.max()
|
|
257
257
|
wedge = ndimage.affine_transform(wedge,
|
|
258
258
|
Affine2D().scale(scale2, 1).get_matrix()[:2, :2],
|
|
259
259
|
offset=[(1 - scale2) * counts.shape[0] / 2, 0],
|
|
@@ -477,7 +477,7 @@ class Symmetrizer3D:
|
|
|
477
477
|
q1, q2, q3 = self.q1, self.q2, self.q3
|
|
478
478
|
out_array = np.zeros(data[data.signal].shape)
|
|
479
479
|
|
|
480
|
-
if self.plane1symmetrizer.theta_max:
|
|
480
|
+
if self.plane1symmetrizer.theta_max is not None:
|
|
481
481
|
print('Symmetrizing ' + self.plane1 + ' planes...')
|
|
482
482
|
for k, value in enumerate(q3):
|
|
483
483
|
print(f'Symmetrizing {q3.nxname}={value:.02f}...', end='\r')
|
|
@@ -485,7 +485,7 @@ class Symmetrizer3D:
|
|
|
485
485
|
out_array[:, :, k] = data_symmetrized[data.signal].nxdata
|
|
486
486
|
print('\nSymmetrized ' + self.plane1 + ' planes.')
|
|
487
487
|
|
|
488
|
-
if self.plane2symmetrizer.theta_max:
|
|
488
|
+
if self.plane2symmetrizer.theta_max is not None:
|
|
489
489
|
print('Symmetrizing ' + self.plane2 + ' planes...')
|
|
490
490
|
for j, value in enumerate(q2):
|
|
491
491
|
print(f'Symmetrizing {q2.nxname}={value:.02f}...', end='\r')
|
|
@@ -495,7 +495,7 @@ class Symmetrizer3D:
|
|
|
495
495
|
out_array[:, j, :] = data_symmetrized[data.signal].nxdata
|
|
496
496
|
print('\nSymmetrized ' + self.plane2 + ' planes.')
|
|
497
497
|
|
|
498
|
-
if self.plane3symmetrizer.theta_max:
|
|
498
|
+
if self.plane3symmetrizer.theta_max is not None:
|
|
499
499
|
print('Symmetrizing ' + self.plane3 + ' planes...')
|
|
500
500
|
for i, value in enumerate(q1):
|
|
501
501
|
print(f'Symmetrizing {q1.nxname}={value:.02f}...', end='\r')
|
|
@@ -536,7 +536,7 @@ class Symmetrizer3D:
|
|
|
536
536
|
print("Output file saved to: " + os.path.join(os.getcwd(), fout_name))
|
|
537
537
|
|
|
538
538
|
|
|
539
|
-
def generate_gaussian(H, K, L, amp, stddev, lattice_params, coeffs=None):
|
|
539
|
+
def generate_gaussian(H, K, L, amp, stddev, lattice_params, coeffs=None, center=None):
|
|
540
540
|
"""
|
|
541
541
|
Generate a 3D Gaussian distribution.
|
|
542
542
|
|
|
@@ -558,16 +558,23 @@ def generate_gaussian(H, K, L, amp, stddev, lattice_params, coeffs=None):
|
|
|
558
558
|
Coefficients for the Gaussian expression, including cross-terms between axes.
|
|
559
559
|
Default is [1, 0, 1, 0, 1, 0],
|
|
560
560
|
corresponding to (1*H**2 + 0*H*K + 1*K**2 + 0*K*L + 1*L**2 + 0*L*H).
|
|
561
|
+
center : tuple
|
|
562
|
+
Tuple of coordinates for the center of the Gaussian. Default is (0,0,0).
|
|
561
563
|
|
|
562
564
|
Returns
|
|
563
565
|
-------
|
|
564
566
|
gaussian : ndarray
|
|
565
567
|
3D Gaussian distribution array.
|
|
566
568
|
"""
|
|
569
|
+
if center is None:
|
|
570
|
+
center=(0,0,0)
|
|
567
571
|
if coeffs is None:
|
|
568
572
|
coeffs = [1, 0, 1, 0, 1, 0]
|
|
569
573
|
a, b, c, al, be, ga = lattice_params
|
|
570
574
|
a_, b_, c_, _, _, _ = reciprocal_lattice_params((a, b, c, al, be, ga))
|
|
575
|
+
H = H-center[0]
|
|
576
|
+
K = K-center[1]
|
|
577
|
+
L = L-center[2]
|
|
571
578
|
H, K, L = np.meshgrid(H, K, L, indexing='ij')
|
|
572
579
|
gaussian = amp * np.exp(-(coeffs[0] * H ** 2 +
|
|
573
580
|
coeffs[1] * (b_ * a_ / (a_ ** 2)) * H * K +
|
|
@@ -581,7 +588,6 @@ def generate_gaussian(H, K, L, amp, stddev, lattice_params, coeffs=None):
|
|
|
581
588
|
gaussian = gaussian.transpose()
|
|
582
589
|
return gaussian.transpose(1, 0, 2)
|
|
583
590
|
|
|
584
|
-
|
|
585
591
|
class Puncher:
|
|
586
592
|
"""
|
|
587
593
|
A class for applying masks to 3D datasets, typically for data processing in reciprocal space.
|
{nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44/src/nxs_analysis_tools.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: nxs-analysis-tools
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.44
|
|
4
4
|
Summary: Reduce and transform nexus format (.nxs) scattering data.
|
|
5
5
|
Author-email: "Steven J. Gomez Alvarado" <stevenjgomez@ucsb.edu>
|
|
6
6
|
License: MIT License
|
|
@@ -66,6 +66,7 @@ Requires-Dist: sphinx-autobuild>=2021.3.14; extra == "dev"
|
|
|
66
66
|
Requires-Dist: sphinx-copybutton>=0.5.0; extra == "dev"
|
|
67
67
|
Requires-Dist: sphinxext-opengraph>=0.6.3; extra == "dev"
|
|
68
68
|
Requires-Dist: twine>=4.0.1; extra == "dev"
|
|
69
|
+
Dynamic: license-file
|
|
69
70
|
|
|
70
71
|
# nxs-analysis-tools
|
|
71
72
|
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
from nxs_analysis_tools import load_data, plot_slice
|
|
2
|
+
from nxs_analysis_tools.datareduction import Padder, array_to_nxdata
|
|
3
|
+
from nxs_analysis_tools.pairdistribution import Symmetrizer3D
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
import os
|
|
8
|
+
import gc
|
|
9
|
+
import math
|
|
10
|
+
from scipy import ndimage
|
|
11
|
+
import scipy
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
from matplotlib.transforms import Affine2D
|
|
14
|
+
from nexusformat.nexus import nxsave, NXroot, NXentry, NXdata, NXfield
|
|
15
|
+
import numpy as np
|
|
16
|
+
from astropy.convolution import Kernel, convolve_fft
|
|
17
|
+
import pyfftw
|
|
18
|
+
|
|
19
|
+
data = load_data('../docs/source/examples/example_data/pairdistribution_data/test_hkli.nxs')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Symmetrizer2D:
|
|
23
|
+
"""
|
|
24
|
+
A class for symmetrizing 2D datasets.
|
|
25
|
+
|
|
26
|
+
The `Symmetrizer2D` class provides functionality to apply symmetry
|
|
27
|
+
operations such as rotation and mirroring to 2D datasets.
|
|
28
|
+
|
|
29
|
+
Attributes
|
|
30
|
+
----------
|
|
31
|
+
mirror_axis : int or None
|
|
32
|
+
The axis along which mirroring is performed. Default is None, meaning
|
|
33
|
+
no mirroring is applied.
|
|
34
|
+
symmetrized : NXdata or None
|
|
35
|
+
The symmetrized dataset after applying the symmetrization operations.
|
|
36
|
+
Default is None until symmetrization is performed.
|
|
37
|
+
wedges : NXdata or None
|
|
38
|
+
The wedges extracted from the dataset based on the angular limits.
|
|
39
|
+
Default is None until symmetrization is performed.
|
|
40
|
+
rotations : int or None
|
|
41
|
+
The number of rotations needed to reconstruct the full dataset from
|
|
42
|
+
a single wedge. Default is None until parameters are set.
|
|
43
|
+
transform : Affine2D or None
|
|
44
|
+
The transformation matrix used for skewing and scaling the dataset.
|
|
45
|
+
Default is None until parameters are set.
|
|
46
|
+
mirror : bool or None
|
|
47
|
+
Indicates whether mirroring is performed during symmetrization.
|
|
48
|
+
Default is None until parameters are set.
|
|
49
|
+
skew_angle : float or None
|
|
50
|
+
The skew angle (in degrees) between the principal axes of the plane
|
|
51
|
+
to be symmetrized. Default is None until parameters are set.
|
|
52
|
+
theta_max : float or None
|
|
53
|
+
The maximum angle (in degrees) for symmetrization. Default is None
|
|
54
|
+
until parameters are set.
|
|
55
|
+
theta_min : float or None
|
|
56
|
+
The minimum angle (in degrees) for symmetrization. Default is None
|
|
57
|
+
until parameters are set.
|
|
58
|
+
wedge : NXdata or None
|
|
59
|
+
The dataset wedge used in the symmetrization process. Default is
|
|
60
|
+
None until symmetrization is performed.
|
|
61
|
+
symmetrization_mask : NXdata or None
|
|
62
|
+
The mask used for selecting the region of the dataset to be symmetrized.
|
|
63
|
+
Default is None until symmetrization is performed.
|
|
64
|
+
|
|
65
|
+
Methods
|
|
66
|
+
-------
|
|
67
|
+
__init__(**kwargs):
|
|
68
|
+
Initializes the Symmetrizer2D object and optionally sets the parameters
|
|
69
|
+
using `set_parameters`.
|
|
70
|
+
set_parameters(theta_min, theta_max, lattice_angle=90, mirror=True, mirror_axis=0):
|
|
71
|
+
Sets the parameters for the symmetrization operation, including angle limits,
|
|
72
|
+
lattice angle, and mirroring options.
|
|
73
|
+
symmetrize_2d(data):
|
|
74
|
+
Symmetrizes a 2D dataset based on the set parameters.
|
|
75
|
+
test(data, **kwargs):
|
|
76
|
+
Performs a test visualization of the symmetrization process, displaying the
|
|
77
|
+
original data, mask, wedge, and symmetrized result.
|
|
78
|
+
"""
|
|
79
|
+
symmetrization_mask: NXdata
|
|
80
|
+
|
|
81
|
+
def __init__(self, **kwargs):
|
|
82
|
+
"""
|
|
83
|
+
Initializes the Symmetrizer2D object.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
**kwargs : dict, optional
|
|
88
|
+
Keyword arguments that can be passed to the `set_parameters` method to
|
|
89
|
+
set the symmetrization parameters during initialization.
|
|
90
|
+
"""
|
|
91
|
+
self.mirror_axis = None
|
|
92
|
+
self.symmetrized = None
|
|
93
|
+
self.wedges = None
|
|
94
|
+
self.rotations = None
|
|
95
|
+
self.transform = None
|
|
96
|
+
self.mirror = None
|
|
97
|
+
self.skew_angle = None
|
|
98
|
+
self.theta_max = None
|
|
99
|
+
self.theta_min = None
|
|
100
|
+
self.wedge = None
|
|
101
|
+
if kwargs:
|
|
102
|
+
self.set_parameters(**kwargs)
|
|
103
|
+
|
|
104
|
+
def set_parameters(self, theta_min, theta_max, lattice_angle=90, mirror=True, mirror_axis=0):
|
|
105
|
+
"""
|
|
106
|
+
Sets the parameters for the symmetrization operation, and calculates the
|
|
107
|
+
required transformations and rotations.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
theta_min : float
|
|
112
|
+
The minimum angle in degrees for symmetrization.
|
|
113
|
+
theta_max : float
|
|
114
|
+
The maximum angle in degrees for symmetrization.
|
|
115
|
+
lattice_angle : float, optional
|
|
116
|
+
The angle in degrees between the two principal axes of the plane to be
|
|
117
|
+
symmetrized (default: 90).
|
|
118
|
+
mirror : bool, optional
|
|
119
|
+
If True, perform mirroring during symmetrization (default: True).
|
|
120
|
+
mirror_axis : int, optional
|
|
121
|
+
The axis along which to perform mirroring (default: 0).
|
|
122
|
+
"""
|
|
123
|
+
self.theta_min = theta_min
|
|
124
|
+
self.theta_max = theta_max
|
|
125
|
+
self.skew_angle = lattice_angle
|
|
126
|
+
self.mirror = mirror
|
|
127
|
+
self.mirror_axis = mirror_axis
|
|
128
|
+
|
|
129
|
+
# Define Transformation
|
|
130
|
+
skew_angle_adj = 90 - lattice_angle
|
|
131
|
+
t = Affine2D()
|
|
132
|
+
# Scale y-axis to preserve norm while shearing
|
|
133
|
+
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180))
|
|
134
|
+
# Shear along x-axis
|
|
135
|
+
t += Affine2D().skew_deg(skew_angle_adj, 0)
|
|
136
|
+
# Return to original y-axis scaling
|
|
137
|
+
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180)).inverted()
|
|
138
|
+
self.transform = t
|
|
139
|
+
|
|
140
|
+
# Calculate number of rotations needed to reconstruct the dataset
|
|
141
|
+
if mirror:
|
|
142
|
+
rotations = abs(int(360 / (theta_max - theta_min) / 2))
|
|
143
|
+
else:
|
|
144
|
+
rotations = abs(int(360 / (theta_max - theta_min)))
|
|
145
|
+
self.rotations = rotations
|
|
146
|
+
|
|
147
|
+
self.symmetrization_mask = None
|
|
148
|
+
|
|
149
|
+
self.wedges = None
|
|
150
|
+
|
|
151
|
+
self.symmetrized = None
|
|
152
|
+
|
|
153
|
+
def symmetrize_2d(self, data):
|
|
154
|
+
"""
|
|
155
|
+
Symmetrizes a 2D dataset based on the set parameters, applying padding
|
|
156
|
+
to prevent rotation cutoff and handling overlapping pixels.
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
data : NXdata
|
|
161
|
+
The input 2D dataset to be symmetrized.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
symmetrized : NXdata
|
|
166
|
+
The symmetrized 2D dataset.
|
|
167
|
+
"""
|
|
168
|
+
theta_min = self.theta_min
|
|
169
|
+
theta_max = self.theta_max
|
|
170
|
+
mirror = self.mirror
|
|
171
|
+
mirror_axis = self.mirror_axis
|
|
172
|
+
t = self.transform
|
|
173
|
+
rotations = self.rotations
|
|
174
|
+
|
|
175
|
+
# Pad the dataset so that rotations don't get cutoff if they extend
|
|
176
|
+
# past the extent of the dataset
|
|
177
|
+
p = Padder(data)
|
|
178
|
+
padding = tuple(len(data[axis]) for axis in data.axes)
|
|
179
|
+
data_padded = p.pad(padding)
|
|
180
|
+
|
|
181
|
+
# Define axes that span the plane to be transformed
|
|
182
|
+
q1 = data_padded[data.axes[0]]
|
|
183
|
+
q2 = data_padded[data.axes[1]]
|
|
184
|
+
|
|
185
|
+
# Calculate the angle for each data point
|
|
186
|
+
theta = np.arctan2(q1.reshape((-1, 1)), q2.reshape((1, -1)))
|
|
187
|
+
# Create a boolean array for the range of angles
|
|
188
|
+
symmetrization_mask = np.logical_and(theta >= theta_min * np.pi / 180,
|
|
189
|
+
theta <= theta_max * np.pi / 180)
|
|
190
|
+
|
|
191
|
+
# Define signal to be transformed
|
|
192
|
+
counts = symmetrization_mask
|
|
193
|
+
|
|
194
|
+
# Scale and skew counts
|
|
195
|
+
skew_angle_adj = 90 - self.skew_angle
|
|
196
|
+
|
|
197
|
+
scale2 = q1.max()/q2.max()
|
|
198
|
+
counts_unscaled2 = ndimage.affine_transform(counts,
|
|
199
|
+
Affine2D().scale(scale2, 1).inverted().get_matrix()[:2, :2],
|
|
200
|
+
offset=[-(1 - scale2) * counts.shape[
|
|
201
|
+
0] / 2 / scale2, 0],
|
|
202
|
+
order=0,
|
|
203
|
+
)
|
|
204
|
+
plot_slice(counts_unscaled2)
|
|
205
|
+
|
|
206
|
+
scale1 = np.cos(skew_angle_adj * np.pi / 180)
|
|
207
|
+
counts_unscaled1 = ndimage.affine_transform(counts_unscaled2,
|
|
208
|
+
Affine2D().scale(scale1, 1).inverted().get_matrix()[:2, :2],
|
|
209
|
+
offset=[-(1 - scale1) * counts.shape[
|
|
210
|
+
0] / 2 / scale1, 0],
|
|
211
|
+
order=0,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
mask = ndimage.affine_transform(counts_unscaled1,
|
|
215
|
+
t.get_matrix()[:2, :2],
|
|
216
|
+
offset=[-counts.shape[0] / 2
|
|
217
|
+
* np.sin(skew_angle_adj * np.pi / 180), 0],
|
|
218
|
+
order=0,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Convert mask to nxdata
|
|
222
|
+
mask = array_to_nxdata(mask, data_padded)
|
|
223
|
+
|
|
224
|
+
# Save mask for user interaction
|
|
225
|
+
self.symmetrization_mask = p.unpad(mask)
|
|
226
|
+
|
|
227
|
+
# Perform masking
|
|
228
|
+
wedge = mask * data_padded
|
|
229
|
+
|
|
230
|
+
# Save wedge for user interaction
|
|
231
|
+
self.wedge = p.unpad(wedge)
|
|
232
|
+
|
|
233
|
+
# Convert wedge back to array for further transformations
|
|
234
|
+
wedge = wedge[data.signal].nxdata
|
|
235
|
+
|
|
236
|
+
# Define signal to be transformed
|
|
237
|
+
counts = wedge
|
|
238
|
+
|
|
239
|
+
# Scale and skew counts
|
|
240
|
+
skew_angle_adj = 90 - self.skew_angle
|
|
241
|
+
counts_skew = ndimage.affine_transform(counts,
|
|
242
|
+
t.inverted().get_matrix()[:2, :2],
|
|
243
|
+
offset=[counts.shape[0] / 2
|
|
244
|
+
* np.sin(skew_angle_adj * np.pi / 180), 0],
|
|
245
|
+
order=0,
|
|
246
|
+
)
|
|
247
|
+
scale1 = np.cos(skew_angle_adj * np.pi / 180)
|
|
248
|
+
wedge = ndimage.affine_transform(counts_skew,
|
|
249
|
+
Affine2D().scale(scale1, 1).get_matrix()[:2, :2],
|
|
250
|
+
offset=[(1 - scale1) * counts.shape[0] / 2, 0],
|
|
251
|
+
order=0,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
scale2 = q1.max()/q2.max()
|
|
255
|
+
wedge = ndimage.affine_transform(wedge,
|
|
256
|
+
Affine2D().scale(scale2, 1).get_matrix()[:2, :2],
|
|
257
|
+
offset=[(1 - scale2) * counts.shape[0] / 2, 0],
|
|
258
|
+
order=0,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Reconstruct full dataset from wedge
|
|
262
|
+
reconstructed = np.zeros(counts.shape)
|
|
263
|
+
for _ in range(0, rotations):
|
|
264
|
+
# The following are attempts to combine images with minimal overlapping pixels
|
|
265
|
+
reconstructed += wedge
|
|
266
|
+
# reconstructed = np.where(reconstructed == 0, reconstructed + wedge, reconstructed)
|
|
267
|
+
|
|
268
|
+
wedge = ndimage.rotate(wedge, 360 / rotations, reshape=False, order=0)
|
|
269
|
+
|
|
270
|
+
# self.rotated_only = NXdata(NXfield(reconstructed, name=data.signal),
|
|
271
|
+
# (q1, q2))
|
|
272
|
+
|
|
273
|
+
if mirror:
|
|
274
|
+
# The following are attempts to combine images with minimal overlapping pixels
|
|
275
|
+
reconstructed = np.where(reconstructed == 0,
|
|
276
|
+
reconstructed + np.flip(reconstructed, axis=mirror_axis),
|
|
277
|
+
reconstructed)
|
|
278
|
+
# reconstructed += np.flip(reconstructed, axis=0)
|
|
279
|
+
|
|
280
|
+
# self.rotated_and_mirrored = NXdata(NXfield(reconstructed, name=data.signal),
|
|
281
|
+
# (q1, q2))
|
|
282
|
+
|
|
283
|
+
reconstructed = ndimage.affine_transform(reconstructed,
|
|
284
|
+
Affine2D().scale(
|
|
285
|
+
scale2, 1
|
|
286
|
+
).inverted().get_matrix()[:2, :2],
|
|
287
|
+
offset=[-(1 - scale2) * counts.shape[
|
|
288
|
+
0] / 2 / scale2, 0],
|
|
289
|
+
order=0,
|
|
290
|
+
)
|
|
291
|
+
reconstructed = ndimage.affine_transform(reconstructed,
|
|
292
|
+
Affine2D().scale(
|
|
293
|
+
scale1, 1
|
|
294
|
+
).inverted().get_matrix()[:2, :2],
|
|
295
|
+
offset=[-(1 - scale1) * counts.shape[
|
|
296
|
+
0] / 2 / scale1, 0],
|
|
297
|
+
order=0,
|
|
298
|
+
)
|
|
299
|
+
reconstructed = ndimage.affine_transform(reconstructed,
|
|
300
|
+
t.get_matrix()[:2, :2],
|
|
301
|
+
offset=[(-counts.shape[0] / 2
|
|
302
|
+
* np.sin(skew_angle_adj * np.pi / 180)),
|
|
303
|
+
0],
|
|
304
|
+
order=0,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
reconstructed_unpadded = p.unpad(reconstructed)
|
|
308
|
+
|
|
309
|
+
# Fix any overlapping pixels by truncating counts to max
|
|
310
|
+
reconstructed_unpadded[reconstructed_unpadded > data[data.signal].nxdata.max()] \
|
|
311
|
+
= data[data.signal].nxdata.max()
|
|
312
|
+
|
|
313
|
+
symmetrized = NXdata(NXfield(reconstructed_unpadded, name=data.signal),
|
|
314
|
+
(data[data.axes[0]],
|
|
315
|
+
data[data.axes[1]]))
|
|
316
|
+
|
|
317
|
+
return symmetrized
|
|
318
|
+
|
|
319
|
+
def test(self, data, **kwargs):
|
|
320
|
+
"""
|
|
321
|
+
Performs a test visualization of the symmetrization process to help assess
|
|
322
|
+
the effect of the parameters.
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
data : ndarray
|
|
327
|
+
The input 2D dataset to be used for the test visualization.
|
|
328
|
+
**kwargs : dict
|
|
329
|
+
Additional keyword arguments to be passed to the plot_slice function.
|
|
330
|
+
|
|
331
|
+
Returns
|
|
332
|
+
-------
|
|
333
|
+
fig : Figure
|
|
334
|
+
The matplotlib Figure object that contains the test visualization plot.
|
|
335
|
+
axesarr : ndarray
|
|
336
|
+
The numpy array of Axes objects representing the subplots in the test
|
|
337
|
+
visualization.
|
|
338
|
+
|
|
339
|
+
Notes
|
|
340
|
+
-----
|
|
341
|
+
This method uses the `symmetrize_2d` method to perform the symmetrization on
|
|
342
|
+
the input data and visualize the process.
|
|
343
|
+
|
|
344
|
+
The test visualization plot includes the following subplots:
|
|
345
|
+
- Subplot 1: The original dataset.
|
|
346
|
+
- Subplot 2: The symmetrization mask.
|
|
347
|
+
- Subplot 3: The wedge slice used for reconstruction of the full symmetrized dataset.
|
|
348
|
+
- Subplot 4: The symmetrized dataset.
|
|
349
|
+
|
|
350
|
+
Example usage:
|
|
351
|
+
```
|
|
352
|
+
s = Symmetrizer2D()
|
|
353
|
+
s.set_parameters(theta_min, theta_max, skew_angle, mirror)
|
|
354
|
+
s.test(data)
|
|
355
|
+
```
|
|
356
|
+
"""
|
|
357
|
+
s = self
|
|
358
|
+
symm_test = s.symmetrize_2d(data)
|
|
359
|
+
fig, axesarr = plt.subplots(2, 2, figsize=(10, 8))
|
|
360
|
+
axes = axesarr.reshape(-1)
|
|
361
|
+
|
|
362
|
+
# Plot the data
|
|
363
|
+
plot_slice(data, skew_angle=s.skew_angle, ax=axes[0], title='data', **kwargs)
|
|
364
|
+
|
|
365
|
+
# Filter kwargs to exclude 'vmin' and 'vmax'
|
|
366
|
+
filtered_kwargs = {key: value for key, value in kwargs.items() if key not in ('vmin', 'vmax')}
|
|
367
|
+
# Plot the mask
|
|
368
|
+
plot_slice(s.symmetrization_mask, skew_angle=s.skew_angle, ax=axes[1], title='mask', **filtered_kwargs)
|
|
369
|
+
|
|
370
|
+
# Plot the wedge
|
|
371
|
+
plot_slice(s.wedge, skew_angle=s.skew_angle, ax=axes[2], title='wedge', **kwargs)
|
|
372
|
+
|
|
373
|
+
# Plot the symmetrized data
|
|
374
|
+
plot_slice(symm_test, skew_angle=s.skew_angle, ax=axes[3], title='symmetrized', **kwargs)
|
|
375
|
+
plt.subplots_adjust(wspace=0.4)
|
|
376
|
+
plt.show()
|
|
377
|
+
return fig, axesarr
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
s2d = Symmetrizer2D(theta_min=0, theta_max=45)
|
|
381
|
+
|
|
382
|
+
symmetrized_data = s2d.test(data[:, :, 0.0])
|
|
383
|
+
plt.show()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nxs_analysis_tools-0.0.43 → nxs_analysis_tools-0.0.44}/tests/test_plot_slice_with_ndarray.py
RENAMED
|
File without changes
|