nxs-analysis-tools 0.1.10__py3-none-any.whl → 0.1.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nxs-analysis-tools might be problematic. Click here for more details.
- _meta/__init__.py +1 -1
- nxs_analysis_tools/__init__.py +1 -1
- nxs_analysis_tools/datareduction.py +80 -192
- nxs_analysis_tools/lineartransformations.py +51 -0
- nxs_analysis_tools/pairdistribution.py +28 -103
- {nxs_analysis_tools-0.1.10.dist-info → nxs_analysis_tools-0.1.11.dist-info}/METADATA +3 -4
- nxs_analysis_tools-0.1.11.dist-info/RECORD +13 -0
- nxs_analysis_tools-0.1.10.dist-info/RECORD +0 -12
- {nxs_analysis_tools-0.1.10.dist-info → nxs_analysis_tools-0.1.11.dist-info}/WHEEL +0 -0
- {nxs_analysis_tools-0.1.10.dist-info → nxs_analysis_tools-0.1.11.dist-info}/licenses/LICENSE +0 -0
- {nxs_analysis_tools-0.1.10.dist-info → nxs_analysis_tools-0.1.11.dist-info}/top_level.txt +0 -0
_meta/__init__.py
CHANGED
nxs_analysis_tools/__init__.py
CHANGED
|
@@ -9,7 +9,7 @@ from .chess import TempDependence
|
|
|
9
9
|
|
|
10
10
|
# What to import when running "from nxs_analysis_tools import *"
|
|
11
11
|
__all__ = ['load_data', 'load_transform', 'plot_slice', 'Scissors',
|
|
12
|
-
'reciprocal_lattice_params', 'rotate_data',
|
|
12
|
+
'reciprocal_lattice_params', 'rotate_data',
|
|
13
13
|
'convert_to_inverse_angstroms', 'array_to_nxdata', 'Padder',
|
|
14
14
|
'rebin_nxdata', 'rebin_3d', 'rebin_1d', 'TempDependence',
|
|
15
15
|
'animate_slice_temp', 'animate_slice_axis']
|
|
@@ -15,12 +15,14 @@ from matplotlib import colors
|
|
|
15
15
|
from matplotlib import patches
|
|
16
16
|
from IPython.display import display, Markdown, HTML, Image
|
|
17
17
|
from nexusformat.nexus import NXfield, NXdata, nxload, NeXusError, NXroot, NXentry, nxsave
|
|
18
|
-
from scipy import
|
|
18
|
+
from scipy.ndimage import rotate
|
|
19
|
+
|
|
20
|
+
from .lineartransformations import ShearTransformer
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
# Specify items on which users are allowed to perform standalone imports
|
|
22
24
|
__all__ = ['load_data', 'load_transform', 'plot_slice', 'Scissors',
|
|
23
|
-
'reciprocal_lattice_params', 'rotate_data',
|
|
25
|
+
'reciprocal_lattice_params', 'rotate_data',
|
|
24
26
|
'convert_to_inverse_angstroms', 'array_to_nxdata', 'Padder',
|
|
25
27
|
'rebin_nxdata', 'rebin_3d', 'rebin_1d', 'animate_slice_temp',
|
|
26
28
|
'animate_slice_axis']
|
|
@@ -503,17 +505,8 @@ def plot_slice(data, X=None, Y=None, sum_axis=None, transpose=False, vmin=None,
|
|
|
503
505
|
p = ax.pcolormesh(X.nxdata, Y.nxdata, data_arr, shading='auto', norm=norm, cmap=cmap, **kwargs)
|
|
504
506
|
|
|
505
507
|
## Transform data to new coordinate system if necessary
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
# Create blank 2D affine transformation
|
|
509
|
-
t = Affine2D()
|
|
510
|
-
# Scale y-axis to preserve norm while shearing
|
|
511
|
-
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180))
|
|
512
|
-
# Shear along x-axis
|
|
513
|
-
t += Affine2D().skew_deg(skew_angle_adj, 0)
|
|
514
|
-
# Return to original y-axis scaling
|
|
515
|
-
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180)).inverted()
|
|
516
|
-
## Correct for x-displacement after shearing
|
|
508
|
+
t = ShearTransformer(skew_angle)
|
|
509
|
+
|
|
517
510
|
# If ylims provided, use those
|
|
518
511
|
if ylim is not None:
|
|
519
512
|
# Set ylims
|
|
@@ -523,8 +516,8 @@ def plot_slice(data, X=None, Y=None, sum_axis=None, transpose=False, vmin=None,
|
|
|
523
516
|
else:
|
|
524
517
|
ymin, ymax = ax.get_ylim()
|
|
525
518
|
# Use ylims to calculate translation (necessary to display axes in correct position)
|
|
526
|
-
p.set_transform(t
|
|
527
|
-
+ Affine2D().translate(-ymin * np.sin(
|
|
519
|
+
p.set_transform(t.t
|
|
520
|
+
+ Affine2D().translate(-ymin * np.sin(t.shear_angle * np.pi / 180), 0)
|
|
528
521
|
+ ax.transData)
|
|
529
522
|
|
|
530
523
|
# Set x limits
|
|
@@ -533,12 +526,12 @@ def plot_slice(data, X=None, Y=None, sum_axis=None, transpose=False, vmin=None,
|
|
|
533
526
|
else:
|
|
534
527
|
xmin, xmax = ax.get_xlim()
|
|
535
528
|
if skew_angle <= 90:
|
|
536
|
-
ax.set(xlim=(xmin, xmax + (ymax - ymin) / np.tan((90 -
|
|
529
|
+
ax.set(xlim=(xmin, xmax + (ymax - ymin) / np.tan((90 - t.shear_angle) * np.pi / 180)))
|
|
537
530
|
else:
|
|
538
|
-
ax.set(xlim=(xmin - (ymax - ymin) / np.tan((
|
|
531
|
+
ax.set(xlim=(xmin - (ymax - ymin) / np.tan((t.shear_angle - 90) * np.pi / 180), xmax))
|
|
539
532
|
|
|
540
533
|
# Correct aspect ratio for the x/y axes after transformation
|
|
541
|
-
ax.set(aspect=np.cos(
|
|
534
|
+
ax.set(aspect=np.cos(t.shear_angle * np.pi / 180))
|
|
542
535
|
|
|
543
536
|
|
|
544
537
|
# Automatically set tick locations, only if NXdata or if X,Y axes are provided for an array
|
|
@@ -570,7 +563,7 @@ def plot_slice(data, X=None, Y=None, sum_axis=None, transpose=False, vmin=None,
|
|
|
570
563
|
line = ax.xaxis.get_majorticklines()[i]
|
|
571
564
|
if i % 2:
|
|
572
565
|
# Top ticks (translation here makes their direction="in")
|
|
573
|
-
m._transform.set(Affine2D().translate(0, -1) + Affine2D().skew_deg(
|
|
566
|
+
m._transform.set(Affine2D().translate(0, -1) + Affine2D().skew_deg(t.shear_angle, 0))
|
|
574
567
|
# This first method shifts the top ticks horizontally to match the skew angle.
|
|
575
568
|
# This does not look good in all cases.
|
|
576
569
|
# line.set_transform(Affine2D().translate((ymax-ymin)*np.sin(skew_angle*np.pi/180),0) +
|
|
@@ -580,7 +573,7 @@ def plot_slice(data, X=None, Y=None, sum_axis=None, transpose=False, vmin=None,
|
|
|
580
573
|
line.set_transform(line.get_transform()) # This does nothing
|
|
581
574
|
else:
|
|
582
575
|
# Bottom ticks
|
|
583
|
-
m._transform.set(Affine2D().skew_deg(
|
|
576
|
+
m._transform.set(Affine2D().skew_deg(t.shear_angle, 0))
|
|
584
577
|
|
|
585
578
|
line.set_marker(m)
|
|
586
579
|
|
|
@@ -588,9 +581,9 @@ def plot_slice(data, X=None, Y=None, sum_axis=None, transpose=False, vmin=None,
|
|
|
588
581
|
m = MarkerStyle(2)
|
|
589
582
|
line = ax.xaxis.get_minorticklines()[i]
|
|
590
583
|
if i % 2:
|
|
591
|
-
m._transform.set(Affine2D().translate(0, -1) + Affine2D().skew_deg(
|
|
584
|
+
m._transform.set(Affine2D().translate(0, -1) + Affine2D().skew_deg(t.shear_angle, 0))
|
|
592
585
|
else:
|
|
593
|
-
m._transform.set(Affine2D().skew_deg(
|
|
586
|
+
m._transform.set(Affine2D().skew_deg(t.shear_angle, 0))
|
|
594
587
|
|
|
595
588
|
line.set_marker(m)
|
|
596
589
|
|
|
@@ -1234,9 +1227,9 @@ def convert_to_inverse_angstroms(data, lattice_params):
|
|
|
1234
1227
|
return NXdata(new_data, (a_star, b_star, c_star))
|
|
1235
1228
|
|
|
1236
1229
|
|
|
1237
|
-
def rotate_data(data, lattice_angle, rotation_angle, rotation_axis, printout=False):
|
|
1230
|
+
def rotate_data(data, lattice_angle, rotation_angle, rotation_axis=None, printout=False):
|
|
1238
1231
|
"""
|
|
1239
|
-
Rotates
|
|
1232
|
+
Rotates slices of data around the normal axis.
|
|
1240
1233
|
|
|
1241
1234
|
Parameters
|
|
1242
1235
|
----------
|
|
@@ -1246,13 +1239,12 @@ def rotate_data(data, lattice_angle, rotation_angle, rotation_axis, printout=Fal
|
|
|
1246
1239
|
Angle between the two in-plane lattice axes in degrees.
|
|
1247
1240
|
rotation_angle : float
|
|
1248
1241
|
Angle of rotation in degrees.
|
|
1249
|
-
rotation_axis : int
|
|
1250
|
-
Axis of rotation (0, 1, or 2).
|
|
1242
|
+
rotation_axis : int, optional
|
|
1243
|
+
Axis of rotation (0, 1, or 2). Only necessary when data is three-dimensional.
|
|
1251
1244
|
printout : bool, optional
|
|
1252
|
-
Enables printout of rotation progress. If set to True,
|
|
1253
|
-
about each rotation slice will be printed to the console, indicating
|
|
1254
|
-
the axis being rotated and the corresponding coordinate value.
|
|
1255
|
-
Defaults to False.
|
|
1245
|
+
Enables printout of rotation progress for three-dimensional data. If set to True,
|
|
1246
|
+
information about each rotation slice will be printed to the console, indicating
|
|
1247
|
+
the axis being rotated and the corresponding coordinate value. Defaults to False.
|
|
1256
1248
|
|
|
1257
1249
|
|
|
1258
1250
|
Returns
|
|
@@ -1260,36 +1252,36 @@ def rotate_data(data, lattice_angle, rotation_angle, rotation_axis, printout=Fal
|
|
|
1260
1252
|
rotated_data : :class:`nexusformat.nexus.NXdata`
|
|
1261
1253
|
Rotated data as an NXdata object.
|
|
1262
1254
|
"""
|
|
1255
|
+
|
|
1256
|
+
if data.ndim == 3 and rotation_axis is None:
|
|
1257
|
+
raise ValueError('rotation_axis must be specified for three-dimensional datasets.')
|
|
1258
|
+
|
|
1259
|
+
if not((data.ndim == 2) or (data.ndim == 3)):
|
|
1260
|
+
raise ValueError('Data must be 2 or 3 dimensional.')
|
|
1261
|
+
|
|
1263
1262
|
# Define output array
|
|
1264
1263
|
output_array = np.zeros(data.nxsignal.shape)
|
|
1265
1264
|
|
|
1266
|
-
# Define shear transformation
|
|
1267
|
-
skew_angle_adj = 90 - lattice_angle
|
|
1268
|
-
t = Affine2D()
|
|
1269
|
-
# Scale y-axis to preserve norm while shearing
|
|
1270
|
-
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180))
|
|
1271
|
-
# Shear along x-axis
|
|
1272
|
-
t += Affine2D().skew_deg(skew_angle_adj, 0)
|
|
1273
|
-
# Return to original y-axis scaling
|
|
1274
|
-
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180)).inverted()
|
|
1275
|
-
|
|
1276
1265
|
# Iterate over all layers perpendicular to the rotation axis
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
sliced_data =
|
|
1266
|
+
if data.ndim == 3:
|
|
1267
|
+
num_slices = len(data.nxaxes[rotation_axis])
|
|
1268
|
+
elif data.ndim == 2:
|
|
1269
|
+
num_slices = 1
|
|
1270
|
+
|
|
1271
|
+
for i in range(num_slices):
|
|
1272
|
+
|
|
1273
|
+
if data.ndim == 3:
|
|
1274
|
+
# Print progress
|
|
1275
|
+
if printout:
|
|
1276
|
+
print(f'\rRotating {data.axes[rotation_axis]}'
|
|
1277
|
+
f'={data.nxaxes[rotation_axis][i]}... ',
|
|
1278
|
+
end='', flush=True)
|
|
1279
|
+
index = [slice(None)] * 3
|
|
1280
|
+
index[rotation_axis] = i
|
|
1281
|
+
sliced_data = data[tuple(index)]
|
|
1282
|
+
|
|
1283
|
+
elif data.ndim == 2:
|
|
1284
|
+
sliced_data = data
|
|
1293
1285
|
|
|
1294
1286
|
# Add padding to avoid data cutoff during rotation
|
|
1295
1287
|
p = Padder(sliced_data)
|
|
@@ -1297,76 +1289,38 @@ def rotate_data(data, lattice_angle, rotation_angle, rotation_axis, printout=Fal
|
|
|
1297
1289
|
counts = p.pad(padding)
|
|
1298
1290
|
counts = p.padded[p.padded.signal]
|
|
1299
1291
|
|
|
1300
|
-
#
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
offset=[counts.shape[0] / 2
|
|
1304
|
-
* np.sin(skew_angle_adj * np.pi / 180),
|
|
1305
|
-
0],
|
|
1306
|
-
order=0,
|
|
1307
|
-
)
|
|
1308
|
-
# Scale data based on skew angle
|
|
1309
|
-
scale1 = np.cos(skew_angle_adj * np.pi / 180)
|
|
1310
|
-
counts_scaled1 = ndimage.affine_transform(counts_skewed,
|
|
1311
|
-
Affine2D().scale(scale1, 1).get_matrix()[:2, :2],
|
|
1312
|
-
offset=[(1 - scale1) * counts.shape[0] / 2, 0],
|
|
1313
|
-
order=0,
|
|
1314
|
-
)
|
|
1315
|
-
# Scale data based on ratio of array dimensions
|
|
1316
|
-
scale2 = 1 # counts.shape[0] / counts.shape[1]
|
|
1317
|
-
counts_scaled2 = ndimage.affine_transform(counts_scaled1,
|
|
1318
|
-
Affine2D().scale(scale2, 1).get_matrix()[:2, :2],
|
|
1319
|
-
offset=[(1 - scale2) * counts.shape[0] / 2, 0],
|
|
1320
|
-
order=0,
|
|
1321
|
-
)
|
|
1292
|
+
# Skew data to match lattice angle
|
|
1293
|
+
t = ShearTransformer(lattice_angle)
|
|
1294
|
+
counts = t.apply(counts)
|
|
1322
1295
|
|
|
1323
1296
|
# Perform rotation
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
# Undo
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
scale2, 1
|
|
1330
|
-
).inverted().get_matrix()[:2, :2],
|
|
1331
|
-
offset=[-(1 - scale2) * counts.shape[
|
|
1332
|
-
0] / 2 / scale2, 0],
|
|
1333
|
-
order=0,
|
|
1334
|
-
)
|
|
1335
|
-
# Undo scaling 1
|
|
1336
|
-
counts_unscaled1 = ndimage.affine_transform(counts_unscaled2,
|
|
1337
|
-
Affine2D().scale(
|
|
1338
|
-
scale1, 1
|
|
1339
|
-
).inverted().get_matrix()[:2, :2],
|
|
1340
|
-
offset=[-(1 - scale1) * counts.shape[
|
|
1341
|
-
0] / 2 / scale1, 0],
|
|
1342
|
-
order=0,
|
|
1343
|
-
)
|
|
1344
|
-
# Undo shear operation
|
|
1345
|
-
counts_unskewed = ndimage.affine_transform(counts_unscaled1,
|
|
1346
|
-
t.get_matrix()[:2, :2],
|
|
1347
|
-
offset=[
|
|
1348
|
-
(-counts.shape[0] / 2
|
|
1349
|
-
* np.sin(skew_angle_adj * np.pi / 180)),
|
|
1350
|
-
0],
|
|
1351
|
-
order=0,
|
|
1352
|
-
)
|
|
1297
|
+
counts = rotate(counts, rotation_angle, reshape=False, order=0)
|
|
1298
|
+
|
|
1299
|
+
# Undo skew transformation
|
|
1300
|
+
counts = t.invert(counts)
|
|
1301
|
+
|
|
1353
1302
|
# Remove padding
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
# Write
|
|
1357
|
-
if
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
output_array[
|
|
1361
|
-
elif
|
|
1362
|
-
output_array
|
|
1363
|
-
|
|
1303
|
+
counts = p.unpad(counts)
|
|
1304
|
+
|
|
1305
|
+
# Write slice
|
|
1306
|
+
if data.ndim == 3:
|
|
1307
|
+
index = [slice(None)] * 3
|
|
1308
|
+
index[rotation_axis] = i
|
|
1309
|
+
output_array[tuple(index)] = counts
|
|
1310
|
+
elif data.ndim == 2:
|
|
1311
|
+
output_array = counts
|
|
1312
|
+
|
|
1313
|
+
print('\nRotation completed.')
|
|
1314
|
+
|
|
1364
1315
|
return NXdata(NXfield(output_array, name=p.padded.signal),
|
|
1365
|
-
(
|
|
1316
|
+
([axis for axis in data.nxaxes]))
|
|
1317
|
+
|
|
1366
1318
|
|
|
1367
1319
|
|
|
1368
1320
|
def rotate_data_2D(data, lattice_angle, rotation_angle):
|
|
1369
1321
|
"""
|
|
1322
|
+
DEPRECATED: Use `rotate_data` instead.
|
|
1323
|
+
|
|
1370
1324
|
Rotates 2D data.
|
|
1371
1325
|
|
|
1372
1326
|
Parameters
|
|
@@ -1378,86 +1332,20 @@ def rotate_data_2D(data, lattice_angle, rotation_angle):
|
|
|
1378
1332
|
rotation_angle : float
|
|
1379
1333
|
Angle of rotation in degrees.
|
|
1380
1334
|
|
|
1381
|
-
|
|
1382
1335
|
Returns
|
|
1383
1336
|
-------
|
|
1384
1337
|
rotated_data : :class:`nexusformat.nexus.NXdata`
|
|
1385
1338
|
Rotated data as an NXdata object.
|
|
1386
1339
|
"""
|
|
1340
|
+
warnings.warn(
|
|
1341
|
+
"rotate_data_2D is deprecated and will be removed in a future release. "
|
|
1342
|
+
"Use rotate_data instead.",
|
|
1343
|
+
DeprecationWarning,
|
|
1344
|
+
stacklevel=2,
|
|
1345
|
+
)
|
|
1387
1346
|
|
|
1388
|
-
#
|
|
1389
|
-
|
|
1390
|
-
t = Affine2D()
|
|
1391
|
-
# Scale y-axis to preserve norm while shearing
|
|
1392
|
-
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180))
|
|
1393
|
-
# Shear along x-axis
|
|
1394
|
-
t += Affine2D().skew_deg(skew_angle_adj, 0)
|
|
1395
|
-
# Return to original y-axis scaling
|
|
1396
|
-
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180)).inverted()
|
|
1397
|
-
|
|
1398
|
-
# Add padding to avoid data cutoff during rotation
|
|
1399
|
-
p = Padder(data)
|
|
1400
|
-
padding = tuple(len(data[axis]) for axis in data.axes)
|
|
1401
|
-
counts = p.pad(padding)
|
|
1402
|
-
counts = p.padded[p.padded.signal]
|
|
1403
|
-
|
|
1404
|
-
# Perform shear operation
|
|
1405
|
-
counts_skewed = ndimage.affine_transform(counts,
|
|
1406
|
-
t.inverted().get_matrix()[:2, :2],
|
|
1407
|
-
offset=[counts.shape[0] / 2
|
|
1408
|
-
* np.sin(skew_angle_adj * np.pi / 180), 0],
|
|
1409
|
-
order=0,
|
|
1410
|
-
)
|
|
1411
|
-
# Scale data based on skew angle
|
|
1412
|
-
scale1 = np.cos(skew_angle_adj * np.pi / 180)
|
|
1413
|
-
counts_scaled1 = ndimage.affine_transform(counts_skewed,
|
|
1414
|
-
Affine2D().scale(scale1, 1).get_matrix()[:2, :2],
|
|
1415
|
-
offset=[(1 - scale1) * counts.shape[0] / 2, 0],
|
|
1416
|
-
order=0,
|
|
1417
|
-
)
|
|
1418
|
-
# Scale data based on ratio of array dimensions
|
|
1419
|
-
scale2 = 1 # counts.shape[0] / counts.shape[1]
|
|
1420
|
-
counts_scaled2 = ndimage.affine_transform(counts_scaled1,
|
|
1421
|
-
Affine2D().scale(scale2, 1).get_matrix()[:2, :2],
|
|
1422
|
-
offset=[(1 - scale2) * counts.shape[0] / 2, 0],
|
|
1423
|
-
order=0,
|
|
1424
|
-
)
|
|
1425
|
-
# Perform rotation
|
|
1426
|
-
counts_rotated = ndimage.rotate(counts_scaled2, rotation_angle, reshape=False, order=0)
|
|
1427
|
-
|
|
1428
|
-
# Undo scaling 2
|
|
1429
|
-
counts_unscaled2 = ndimage.affine_transform(counts_rotated,
|
|
1430
|
-
Affine2D().scale(
|
|
1431
|
-
scale2, 1
|
|
1432
|
-
).inverted().get_matrix()[:2, :2],
|
|
1433
|
-
offset=[-(1 - scale2) * counts.shape[
|
|
1434
|
-
0] / 2 / scale2, 0],
|
|
1435
|
-
order=0,
|
|
1436
|
-
)
|
|
1437
|
-
# Undo scaling 1
|
|
1438
|
-
counts_unscaled1 = ndimage.affine_transform(counts_unscaled2,
|
|
1439
|
-
Affine2D().scale(
|
|
1440
|
-
scale1, 1
|
|
1441
|
-
).inverted().get_matrix()[:2, :2],
|
|
1442
|
-
offset=[-(1 - scale1) * counts.shape[
|
|
1443
|
-
0] / 2 / scale1, 0],
|
|
1444
|
-
order=0,
|
|
1445
|
-
)
|
|
1446
|
-
# Undo shear operation
|
|
1447
|
-
counts_unskewed = ndimage.affine_transform(counts_unscaled1,
|
|
1448
|
-
t.get_matrix()[:2, :2],
|
|
1449
|
-
offset=[
|
|
1450
|
-
(-counts.shape[0] / 2
|
|
1451
|
-
* np.sin(skew_angle_adj * np.pi / 180)),
|
|
1452
|
-
0],
|
|
1453
|
-
order=0,
|
|
1454
|
-
)
|
|
1455
|
-
# Remove padding
|
|
1456
|
-
counts_unpadded = p.unpad(counts_unskewed)
|
|
1457
|
-
|
|
1458
|
-
print('\nDone.')
|
|
1459
|
-
return NXdata(NXfield(counts_unpadded, name=p.padded.signal),
|
|
1460
|
-
(data.nxaxes[0], data.nxaxes[1]))
|
|
1347
|
+
# Call the new general function
|
|
1348
|
+
return rotate_data(data, lattice_angle=lattice_angle, rotation_angle=rotation_angle)
|
|
1461
1349
|
|
|
1462
1350
|
|
|
1463
1351
|
class Padder:
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.ndimage import affine_transform
|
|
3
|
+
from matplotlib.transforms import Affine2D
|
|
4
|
+
|
|
5
|
+
def shear_transformation(angle):
|
|
6
|
+
# Define shear transformation
|
|
7
|
+
t = Affine2D()
|
|
8
|
+
|
|
9
|
+
# Scale y-axis to preserve norm while shearing
|
|
10
|
+
t += Affine2D().scale(1, np.cos(angle * np.pi / 180))
|
|
11
|
+
|
|
12
|
+
# Shear along x-axis
|
|
13
|
+
t += Affine2D().skew_deg(angle, 0)
|
|
14
|
+
|
|
15
|
+
# Return to original y-axis scaling
|
|
16
|
+
t += Affine2D().scale(1, np.cos(angle * np.pi / 180)).inverted()
|
|
17
|
+
|
|
18
|
+
return t
|
|
19
|
+
|
|
20
|
+
class ShearTransformer():
|
|
21
|
+
def __init__(self, angle):
|
|
22
|
+
self.shear_angle = 90 - angle
|
|
23
|
+
self.t = shear_transformation(self.shear_angle)
|
|
24
|
+
self.scale = np.cos(self.shear_angle * np.pi / 180)
|
|
25
|
+
|
|
26
|
+
def apply(self, image):
|
|
27
|
+
# Perform shear operation
|
|
28
|
+
image_skewed = affine_transform(image, self.t.inverted().get_matrix()[:2, :2],
|
|
29
|
+
offset=[image.shape[0] / 2 * np.sin(self.shear_angle * np.pi / 180), 0],
|
|
30
|
+
order=0
|
|
31
|
+
)
|
|
32
|
+
# Scale data based on skew angle
|
|
33
|
+
image_scaled = affine_transform(image_skewed, Affine2D().scale(self.scale, 1).get_matrix()[:2, :2],
|
|
34
|
+
offset=[(1 - self.scale) * image.shape[0] / 2, 0],
|
|
35
|
+
order=0
|
|
36
|
+
)
|
|
37
|
+
return image_scaled
|
|
38
|
+
|
|
39
|
+
def invert(self, image):
|
|
40
|
+
|
|
41
|
+
# Undo scaling
|
|
42
|
+
image_unscaled = affine_transform(image, Affine2D().scale(self.scale, 1).inverted().get_matrix()[:2, :2],
|
|
43
|
+
offset=[-(1 - self.scale) * image.shape[0] / 2 / self.scale, 0],
|
|
44
|
+
order=0
|
|
45
|
+
)
|
|
46
|
+
# Undo shear operation
|
|
47
|
+
image_unskewed = affine_transform(image_unscaled, self.t.get_matrix()[:2, :2],
|
|
48
|
+
offset=[(-image.shape[0] / 2 * np.sin(self.shear_angle * np.pi / 180)), 0],
|
|
49
|
+
order=0
|
|
50
|
+
)
|
|
51
|
+
return image_unskewed
|
|
@@ -5,7 +5,7 @@ import time
|
|
|
5
5
|
import os
|
|
6
6
|
import gc
|
|
7
7
|
import math
|
|
8
|
-
from scipy import
|
|
8
|
+
from scipy.ndimage import rotate, affine_transform
|
|
9
9
|
import scipy
|
|
10
10
|
import matplotlib.pyplot as plt
|
|
11
11
|
from matplotlib.transforms import Affine2D
|
|
@@ -15,6 +15,7 @@ from astropy.convolution import Kernel, convolve_fft
|
|
|
15
15
|
import pyfftw
|
|
16
16
|
from .datareduction import plot_slice, reciprocal_lattice_params, Padder, \
|
|
17
17
|
array_to_nxdata
|
|
18
|
+
from .lineartransformations import ShearTransformer
|
|
18
19
|
|
|
19
20
|
__all__ = ['Symmetrizer2D', 'Symmetrizer3D', 'Puncher', 'Interpolator',
|
|
20
21
|
'fourier_transform_nxdata', 'Gaussian3DKernel', 'DeltaPDF',
|
|
@@ -37,9 +38,6 @@ class Symmetrizer2D:
|
|
|
37
38
|
symmetrized : NXdata or None
|
|
38
39
|
The symmetrized dataset after applying the symmetrization operations.
|
|
39
40
|
Default is None until symmetrization is performed.
|
|
40
|
-
wedges : NXdata or None
|
|
41
|
-
The wedges extracted from the dataset based on the angular limits.
|
|
42
|
-
Default is None until symmetrization is performed.
|
|
43
41
|
rotations : int or None
|
|
44
42
|
The number of rotations needed to reconstruct the full dataset from
|
|
45
43
|
a single wedge. Default is None until parameters are set.
|
|
@@ -93,7 +91,6 @@ class Symmetrizer2D:
|
|
|
93
91
|
"""
|
|
94
92
|
self.mirror_axis = None
|
|
95
93
|
self.symmetrized = None
|
|
96
|
-
self.wedges = None
|
|
97
94
|
self.rotations = None
|
|
98
95
|
self.transform = None
|
|
99
96
|
self.mirror = None
|
|
@@ -129,16 +126,8 @@ class Symmetrizer2D:
|
|
|
129
126
|
self.mirror = mirror
|
|
130
127
|
self.mirror_axis = mirror_axis
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
t = Affine2D()
|
|
135
|
-
# Scale y-axis to preserve norm while shearing
|
|
136
|
-
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180))
|
|
137
|
-
# Shear along x-axis
|
|
138
|
-
t += Affine2D().skew_deg(skew_angle_adj, 0)
|
|
139
|
-
# Return to original y-axis scaling
|
|
140
|
-
t += Affine2D().scale(1, np.cos(skew_angle_adj * np.pi / 180)).inverted()
|
|
141
|
-
self.transform = t
|
|
129
|
+
self.transformer = ShearTransformer(lattice_angle)
|
|
130
|
+
self.transform = self.transformer.t
|
|
142
131
|
|
|
143
132
|
# Calculate number of rotations needed to reconstruct the dataset
|
|
144
133
|
if mirror:
|
|
@@ -172,7 +161,6 @@ class Symmetrizer2D:
|
|
|
172
161
|
theta_max = self.theta_max
|
|
173
162
|
mirror = self.mirror
|
|
174
163
|
mirror_axis = self.mirror_axis
|
|
175
|
-
t = self.transform
|
|
176
164
|
rotations = self.rotations
|
|
177
165
|
|
|
178
166
|
# Pad the dataset so that rotations don't get cutoff if they extend
|
|
@@ -191,37 +179,9 @@ class Symmetrizer2D:
|
|
|
191
179
|
symmetrization_mask = np.logical_and(theta >= theta_min * np.pi / 180,
|
|
192
180
|
theta <= theta_max * np.pi / 180)
|
|
193
181
|
|
|
194
|
-
#
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
# Scale and skew counts
|
|
198
|
-
skew_angle_adj = 90 - self.skew_angle
|
|
199
|
-
|
|
200
|
-
scale2 = 1 # q1.max()/q2.max() # TODO: Need to double check this
|
|
201
|
-
counts_unscaled2 = ndimage.affine_transform(counts,
|
|
202
|
-
Affine2D().scale(scale2, 1).inverted().get_matrix()[:2, :2],
|
|
203
|
-
offset=[-(1 - scale2) * counts.shape[
|
|
204
|
-
0] / 2 / scale2, 0],
|
|
205
|
-
order=0,
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
scale1 = np.cos(skew_angle_adj * np.pi / 180)
|
|
209
|
-
counts_unscaled1 = ndimage.affine_transform(counts_unscaled2,
|
|
210
|
-
Affine2D().scale(scale1, 1).inverted().get_matrix()[:2, :2],
|
|
211
|
-
offset=[-(1 - scale1) * counts.shape[
|
|
212
|
-
0] / 2 / scale1, 0],
|
|
213
|
-
order=0,
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
mask = ndimage.affine_transform(counts_unscaled1,
|
|
217
|
-
t.get_matrix()[:2, :2],
|
|
218
|
-
offset=[-counts.shape[0] / 2
|
|
219
|
-
* np.sin(skew_angle_adj * np.pi / 180), 0],
|
|
220
|
-
order=0,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
# Convert mask to nxdata
|
|
224
|
-
mask = array_to_nxdata(mask, data_padded)
|
|
182
|
+
# Bring mask from skewed basis to data array basis
|
|
183
|
+
mask = array_to_nxdata(self.transformer.invert(symmetrization_mask), data_padded)
|
|
184
|
+
|
|
225
185
|
|
|
226
186
|
# Save mask for user interaction
|
|
227
187
|
self.symmetrization_mask = p.unpad(mask)
|
|
@@ -235,84 +195,49 @@ class Symmetrizer2D:
|
|
|
235
195
|
# Convert wedge back to array for further transformations
|
|
236
196
|
wedge = wedge[data.signal].nxdata
|
|
237
197
|
|
|
238
|
-
#
|
|
239
|
-
|
|
198
|
+
# Bring wedge from data array basis to skewed basis for reconstruction
|
|
199
|
+
wedge = self.transformer.apply(wedge)
|
|
240
200
|
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
* np.sin(skew_angle_adj * np.pi / 180), 0],
|
|
247
|
-
order=0,
|
|
248
|
-
)
|
|
249
|
-
scale1 = np.cos(skew_angle_adj * np.pi / 180)
|
|
250
|
-
wedge = ndimage.affine_transform(counts_skew,
|
|
251
|
-
Affine2D().scale(scale1, 1).get_matrix()[:2, :2],
|
|
252
|
-
offset=[(1 - scale1) * counts.shape[0] / 2, 0],
|
|
253
|
-
order=0,
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
scale2 = counts.shape[0]/counts.shape[1]
|
|
257
|
-
wedge = ndimage.affine_transform(wedge,
|
|
258
|
-
Affine2D().scale(scale2, 1).get_matrix()[:2, :2],
|
|
259
|
-
offset=[(1 - scale2) * counts.shape[0] / 2, 0],
|
|
201
|
+
# Apply additional scaling before rotations
|
|
202
|
+
scale = wedge.shape[0]/wedge.shape[1]
|
|
203
|
+
wedge = affine_transform(wedge,
|
|
204
|
+
Affine2D().scale(scale, 1).get_matrix()[:2, :2],
|
|
205
|
+
offset=[(1 - scale) * wedge.shape[0] / 2, 0],
|
|
260
206
|
order=0,
|
|
261
207
|
)
|
|
262
208
|
|
|
263
209
|
# Reconstruct full dataset from wedge
|
|
264
|
-
reconstructed = np.zeros(
|
|
210
|
+
reconstructed = np.zeros(wedge.shape)
|
|
211
|
+
|
|
265
212
|
for _ in range(0, rotations):
|
|
266
|
-
# The following are attempts to combine images with minimal overlapping pixels
|
|
267
213
|
reconstructed += wedge
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
wedge = ndimage.rotate(wedge, 360 / rotations, reshape=False, order=0)
|
|
271
|
-
|
|
272
|
-
# self.rotated_only = NXdata(NXfield(reconstructed, name=data.signal),
|
|
273
|
-
# (q1, q2))
|
|
214
|
+
wedge = rotate(wedge, 360 / rotations, reshape=False, order=0)
|
|
274
215
|
|
|
275
216
|
if mirror:
|
|
276
|
-
# The following are attempts to combine images with minimal overlapping pixels
|
|
277
217
|
reconstructed = np.where(reconstructed == 0,
|
|
278
218
|
reconstructed + np.flip(reconstructed, axis=mirror_axis),
|
|
279
219
|
reconstructed)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
# self.rotated_and_mirrored = NXdata(NXfield(reconstructed, name=data.signal),
|
|
283
|
-
# (q1, q2))
|
|
220
|
+
|
|
284
221
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
scale2, 1
|
|
288
|
-
).inverted().get_matrix()[:2, :2],
|
|
289
|
-
offset=[-(1 - scale2) * counts.shape[
|
|
290
|
-
0] / 2 / scale2, 0],
|
|
291
|
-
order=0,
|
|
292
|
-
)
|
|
293
|
-
reconstructed = ndimage.affine_transform(reconstructed,
|
|
222
|
+
# Undo scaling transformation
|
|
223
|
+
reconstructed = affine_transform(reconstructed,
|
|
294
224
|
Affine2D().scale(
|
|
295
|
-
|
|
225
|
+
scale, 1
|
|
296
226
|
).inverted().get_matrix()[:2, :2],
|
|
297
|
-
offset=[-(1 -
|
|
298
|
-
0] / 2 /
|
|
299
|
-
order=0,
|
|
300
|
-
)
|
|
301
|
-
reconstructed = ndimage.affine_transform(reconstructed,
|
|
302
|
-
t.get_matrix()[:2, :2],
|
|
303
|
-
offset=[(-counts.shape[0] / 2
|
|
304
|
-
* np.sin(skew_angle_adj * np.pi / 180)),
|
|
305
|
-
0],
|
|
227
|
+
offset=[-(1 - scale) * wedge.shape[
|
|
228
|
+
0] / 2 / scale, 0],
|
|
306
229
|
order=0,
|
|
307
230
|
)
|
|
231
|
+
|
|
232
|
+
reconstructed = self.transformer.invert(reconstructed)
|
|
308
233
|
|
|
309
|
-
|
|
234
|
+
reconstructed = p.unpad(reconstructed)
|
|
310
235
|
|
|
311
236
|
# Fix any overlapping pixels by truncating counts to max
|
|
312
|
-
|
|
237
|
+
reconstructed[reconstructed > data[data.signal].nxdata.max()] \
|
|
313
238
|
= data[data.signal].nxdata.max()
|
|
314
239
|
|
|
315
|
-
symmetrized = NXdata(NXfield(
|
|
240
|
+
symmetrized = NXdata(NXfield(reconstructed, name=data.signal),
|
|
316
241
|
(data[data.axes[0]],
|
|
317
242
|
data[data.axes[1]]))
|
|
318
243
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nxs-analysis-tools
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
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-Expression: MIT
|
|
@@ -14,11 +14,10 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
14
14
|
Classifier: Intended Audience :: Science/Research
|
|
15
15
|
Classifier: Programming Language :: Python
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
18
|
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
20
19
|
Classifier: Topic :: Scientific/Engineering
|
|
21
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.10
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
License-File: LICENSE
|
|
24
23
|
Requires-Dist: matplotlib>=3.10.0
|
|
@@ -57,7 +56,7 @@ Dynamic: license-file
|
|
|
57
56
|
|
|
58
57
|
## Overview
|
|
59
58
|
|
|
60
|
-
nxs-analysis-tools provides a suite of tools for
|
|
59
|
+
nxs-analysis-tools provides a suite of tools for performing slices (2D), cuts (1D), and transformations (_e.g._, symmetrization, interpolation, delta-PDF) on nexus format (.nxs) scattering data.
|
|
61
60
|
|
|
62
61
|
View the documentation [here](https://nxs-analysis-tools.readthedocs.io/en/stable/).
|
|
63
62
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
_meta/__init__.py,sha256=O-Csyugy_6ZYl7nR_6FgU1-D1-64dSBtRSRzDF1aiq4,347
|
|
2
|
+
nxs_analysis_tools/__init__.py,sha256=Gs61l3FrgmhVRv77oqz58W6vmxFhNARfcioYA0FZbqU,604
|
|
3
|
+
nxs_analysis_tools/chess.py,sha256=_gFkLZnoYSCOKPZHm0b1CileCveHJpOszLufeg4QKFg,34086
|
|
4
|
+
nxs_analysis_tools/datareduction.py,sha256=Ky8Q5izEXmOUj3OKOlvZ8A-YeKRdAOD22luggiZtLoY,52181
|
|
5
|
+
nxs_analysis_tools/datasets.py,sha256=KnJBdxuCV7n2Q6MOM0Cv5-Dq6x1hvrrEXIc3Jn7Xsos,3497
|
|
6
|
+
nxs_analysis_tools/fitting.py,sha256=kRMhjObetGqmZ5-Jk1OHKGrXW4qI4D37s8VeC2ygJV8,10275
|
|
7
|
+
nxs_analysis_tools/lineartransformations.py,sha256=-Ce2RzcRcUgg8_kM1o0kO9lOpS_nC2AxRyZTUA1tAe8,2144
|
|
8
|
+
nxs_analysis_tools/pairdistribution.py,sha256=u4WyOfK_nBX78QJr7QO8QWGKV9bH_sBWtdzVaqs2wWo,61238
|
|
9
|
+
nxs_analysis_tools-0.1.11.dist-info/licenses/LICENSE,sha256=bE6FnYixueAGAnEfUuumbkSeMgdBguAAkheVgjv47Jo,1086
|
|
10
|
+
nxs_analysis_tools-0.1.11.dist-info/METADATA,sha256=sdphx8gVih0ZeQOPEHw4_R-sS6D6XlQNZWc6JOxAB5g,3493
|
|
11
|
+
nxs_analysis_tools-0.1.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
nxs_analysis_tools-0.1.11.dist-info/top_level.txt,sha256=8U000GNPzo6T6pOMjRdgOSO5heMzLMGjkxa1CDtyMHM,25
|
|
13
|
+
nxs_analysis_tools-0.1.11.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
_meta/__init__.py,sha256=PpRK3SY3jA8q8ioGFF4DEjAaUUb35J7AB3hwAZ2iiMw,347
|
|
2
|
-
nxs_analysis_tools/__init__.py,sha256=lutfLk7oBaMpKq2G2hf6V59SNqAhzSUyKLXGwTI_iDg,622
|
|
3
|
-
nxs_analysis_tools/chess.py,sha256=_gFkLZnoYSCOKPZHm0b1CileCveHJpOszLufeg4QKFg,34086
|
|
4
|
-
nxs_analysis_tools/datareduction.py,sha256=BexXztaNQ4yNJko_MBQy-eVF2J5C7BTANehvqkwRu_E,59314
|
|
5
|
-
nxs_analysis_tools/datasets.py,sha256=KnJBdxuCV7n2Q6MOM0Cv5-Dq6x1hvrrEXIc3Jn7Xsos,3497
|
|
6
|
-
nxs_analysis_tools/fitting.py,sha256=kRMhjObetGqmZ5-Jk1OHKGrXW4qI4D37s8VeC2ygJV8,10275
|
|
7
|
-
nxs_analysis_tools/pairdistribution.py,sha256=bY80N9y50yPjVKPzWfcPSAtiA8UDPDNCblVXGOw2Lzo,65605
|
|
8
|
-
nxs_analysis_tools-0.1.10.dist-info/licenses/LICENSE,sha256=bE6FnYixueAGAnEfUuumbkSeMgdBguAAkheVgjv47Jo,1086
|
|
9
|
-
nxs_analysis_tools-0.1.10.dist-info/METADATA,sha256=f9qlf_zWAWqRPXqw6D-pJRiFcWV7xiS5vgrGXGJLE98,3529
|
|
10
|
-
nxs_analysis_tools-0.1.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
-
nxs_analysis_tools-0.1.10.dist-info/top_level.txt,sha256=8U000GNPzo6T6pOMjRdgOSO5heMzLMGjkxa1CDtyMHM,25
|
|
12
|
-
nxs_analysis_tools-0.1.10.dist-info/RECORD,,
|
|
File without changes
|
{nxs_analysis_tools-0.1.10.dist-info → nxs_analysis_tools-0.1.11.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|