nxs-analysis-tools 0.0.42__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.

Files changed (27) hide show
  1. {nxs_analysis_tools-0.0.42/src/nxs_analysis_tools.egg-info → nxs_analysis_tools-0.0.44}/PKG-INFO +3 -2
  2. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/pyproject.toml +1 -1
  3. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/_meta/__init__.py +1 -1
  4. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/chess.py +38 -10
  5. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/datareduction.py +5 -5
  6. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/pairdistribution.py +70 -20
  7. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44/src/nxs_analysis_tools.egg-info}/PKG-INFO +3 -2
  8. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/SOURCES.txt +4 -1
  9. nxs_analysis_tools-0.0.44/tests/test_mask_plotting.py +388 -0
  10. nxs_analysis_tools-0.0.44/tests/test_plot_slice_with_ndarray.py +277 -0
  11. nxs_analysis_tools-0.0.44/tests/test_symmetrizer_rectangular_plane.py +383 -0
  12. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/LICENSE +0 -0
  13. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/MANIFEST.in +0 -0
  14. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/README.md +0 -0
  15. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/setup.cfg +0 -0
  16. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/setup.py +0 -0
  17. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/__init__.py +0 -0
  18. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools/fitting.py +0 -0
  19. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/dependency_links.txt +0 -0
  20. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/requires.txt +0 -0
  21. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/src/nxs_analysis_tools.egg-info/top_level.txt +0 -0
  22. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/tests/test_chess.py +0 -0
  23. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/tests/test_chess_fitting.py +0 -0
  24. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/tests/test_datareduction.py +0 -0
  25. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/tests/test_fitting.py +0 -0
  26. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/tests/test_lmfit.py +0 -0
  27. {nxs_analysis_tools-0.0.42 → nxs_analysis_tools-0.0.44}/tests/test_pairdistribution.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: nxs-analysis-tools
3
- Version: 0.0.42
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
 
@@ -6,7 +6,7 @@ build-backend = 'setuptools.build_meta'
6
6
 
7
7
  [project]
8
8
  name = 'nxs-analysis-tools'
9
- version = '0.0.42'
9
+ version = '0.0.44'
10
10
  description = 'Reduce and transform nexus format (.nxs) scattering data.'
11
11
  readme = 'README.md'
12
12
  requires-python = '>=3.7'
@@ -6,5 +6,5 @@ __author__ = 'Steven J. Gomez Alvarado'
6
6
  __email__ = 'stevenjgomez@ucsb.edu'
7
7
  __copyright__ = f"2023, {__author__}"
8
8
  __license__ = 'MIT'
9
- __version__ = '0.0.42'
9
+ __version__ = '0.0.44'
10
10
  __repo_url__ = 'https://github.com/stevenjgomez/nxs_analysis_tools'
@@ -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
 
@@ -202,7 +230,13 @@ class TempDependence:
202
230
  path = os.path.join(self.sample_directory, item)
203
231
 
204
232
  # Save dataset
205
- self.datasets[self.temperatures[i]] = load_transform(path)
233
+ try:
234
+ self.datasets[self.temperatures[i]] = load_transform(path)
235
+ except Exception as e:
236
+ # Report temperature that was unable to load, then raise exception.
237
+ temp_failed = self.temperatures[i]
238
+ print(f"Failed to load data for temperature {temp_failed} K from file {item}. Error: {e}")
239
+ raise # Re-raise the exception
206
240
 
207
241
  # Initialize scissors object
208
242
  self.scissors[self.temperatures[i]] = Scissors()
@@ -232,9 +266,6 @@ class TempDependence:
232
266
  temperature_folders.sort() # Sort from low to high T
233
267
  temperature_folders = [str(i) for i in temperature_folders] # Convert to strings
234
268
 
235
- print('Found temperature folders:')
236
- [print('[' + str(i) + '] ' + folder) for i, folder in enumerate(temperature_folders)]
237
-
238
269
  self.temperatures = temperature_folders
239
270
 
240
271
  if temperatures_list is not None:
@@ -245,9 +276,6 @@ class TempDependence:
245
276
  for file in os.listdir(os.path.join(self.sample_directory, T)):
246
277
  if file.endswith(file_ending):
247
278
  filepath = os.path.join(self.sample_directory, T, file)
248
- print('-----------------------------------------------')
249
- print('Loading ' + T + ' K indexed .nxs files...')
250
- print('Found ' + filepath)
251
279
 
252
280
  # Load dataset at each temperature
253
281
  self.datasets[T] = load_data(filepath)
@@ -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
 
@@ -185,14 +185,14 @@ def plot_slice(data, X=None, Y=None, transpose=False, vmin=None, vmax=None,
185
185
  """
186
186
  if isinstance(data, np.ndarray):
187
187
  if X is None:
188
- X = NXfield(np.linspace(0, data.shape[1], data.shape[1]), name='x')
188
+ X = NXfield(np.linspace(0, data.shape[0], data.shape[0]), name='x')
189
189
  if Y is None:
190
- Y = NXfield(np.linspace(0, data.shape[0], data.shape[0]), name='y')
190
+ Y = NXfield(np.linspace(0, data.shape[1], data.shape[1]), name='y')
191
191
  if transpose:
192
192
  X, Y = Y, X
193
193
  data = data.transpose()
194
194
  data = NXdata(NXfield(data, name='value'), (X, Y))
195
- data_arr = data
195
+ data_arr = data[data.signal].nxdata.transpose()
196
196
  elif isinstance(data, (NXdata, NXfield)):
197
197
  if X is None:
198
198
  X = data[data.axes[0]]
@@ -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 rotate_data2D(data, lattice_angle, rotation_angle):
856
+ def rotate_data_2D(data, lattice_angle, rotation_angle):
857
857
  """
858
858
  Rotates 2D data.
859
859
 
@@ -185,23 +185,58 @@ class Symmetrizer2D:
185
185
  q1 = data_padded[data.axes[0]]
186
186
  q2 = data_padded[data.axes[1]]
187
187
 
188
- # Define signal to be symmetrized
189
- counts = data_padded[data.signal].nxdata
190
-
191
188
  # Calculate the angle for each data point
192
189
  theta = np.arctan2(q1.reshape((-1, 1)), q2.reshape((1, -1)))
193
190
  # Create a boolean array for the range of angles
194
191
  symmetrization_mask = np.logical_and(theta >= theta_min * np.pi / 180,
195
192
  theta <= theta_max * np.pi / 180)
196
- self.symmetrization_mask = NXdata(NXfield(p.unpad(symmetrization_mask),
197
- name='mask'),
198
- (data[data.axes[0]], data[data.axes[1]])
199
- )
200
193
 
201
- self.wedge = NXdata(NXfield(p.unpad(counts * symmetrization_mask),
202
- name=data.signal),
203
- (data[data.axes[0]], data[data.axes[1]])
204
- )
194
+ # Define signal to be transformed
195
+ counts = symmetrization_mask
196
+
197
+ # Scale and skew counts
198
+ skew_angle_adj = 90 - self.skew_angle
199
+
200
+ scale2 = q1.max()/q2.max()
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)
225
+
226
+ # Save mask for user interaction
227
+ self.symmetrization_mask = p.unpad(mask)
228
+
229
+ # Perform masking
230
+ wedge = mask * data_padded
231
+
232
+ # Save wedge for user interaction
233
+ self.wedge = p.unpad(wedge)
234
+
235
+ # Convert wedge back to array for further transformations
236
+ wedge = wedge[data.signal].nxdata
237
+
238
+ # Define signal to be transformed
239
+ counts = wedge
205
240
 
206
241
  # Scale and skew counts
207
242
  skew_angle_adj = 90 - self.skew_angle
@@ -216,9 +251,9 @@ class Symmetrizer2D:
216
251
  Affine2D().scale(scale1, 1).get_matrix()[:2, :2],
217
252
  offset=[(1 - scale1) * counts.shape[0] / 2, 0],
218
253
  order=0,
219
- ) * symmetrization_mask
254
+ )
220
255
 
221
- scale2 = counts.shape[0] / counts.shape[1]
256
+ scale2 = q1.max()/q2.max()
222
257
  wedge = ndimage.affine_transform(wedge,
223
258
  Affine2D().scale(scale2, 1).get_matrix()[:2, :2],
224
259
  offset=[(1 - scale2) * counts.shape[0] / 2, 0],
@@ -325,15 +360,24 @@ class Symmetrizer2D:
325
360
  symm_test = s.symmetrize_2d(data)
326
361
  fig, axesarr = plt.subplots(2, 2, figsize=(10, 8))
327
362
  axes = axesarr.reshape(-1)
363
+
364
+ # Plot the data
328
365
  plot_slice(data, skew_angle=s.skew_angle, ax=axes[0], title='data', **kwargs)
329
- plot_slice(s.symmetrization_mask, skew_angle=s.skew_angle, ax=axes[1], title='mask')
366
+
367
+ # Filter kwargs to exclude 'vmin' and 'vmax'
368
+ filtered_kwargs = {key: value for key, value in kwargs.items() if key not in ('vmin', 'vmax')}
369
+ # Plot the mask
370
+ plot_slice(s.symmetrization_mask, skew_angle=s.skew_angle, ax=axes[1], title='mask', **filtered_kwargs)
371
+
372
+ # Plot the wedge
330
373
  plot_slice(s.wedge, skew_angle=s.skew_angle, ax=axes[2], title='wedge', **kwargs)
374
+
375
+ # Plot the symmetrized data
331
376
  plot_slice(symm_test, skew_angle=s.skew_angle, ax=axes[3], title='symmetrized', **kwargs)
332
377
  plt.subplots_adjust(wspace=0.4)
333
378
  plt.show()
334
379
  return fig, axesarr
335
380
 
336
-
337
381
  class Symmetrizer3D:
338
382
  """
339
383
  A class to symmetrize 3D datasets by performing sequential 2D symmetrization on
@@ -433,7 +477,7 @@ class Symmetrizer3D:
433
477
  q1, q2, q3 = self.q1, self.q2, self.q3
434
478
  out_array = np.zeros(data[data.signal].shape)
435
479
 
436
- if self.plane1symmetrizer.theta_max:
480
+ if self.plane1symmetrizer.theta_max is not None:
437
481
  print('Symmetrizing ' + self.plane1 + ' planes...')
438
482
  for k, value in enumerate(q3):
439
483
  print(f'Symmetrizing {q3.nxname}={value:.02f}...', end='\r')
@@ -441,7 +485,7 @@ class Symmetrizer3D:
441
485
  out_array[:, :, k] = data_symmetrized[data.signal].nxdata
442
486
  print('\nSymmetrized ' + self.plane1 + ' planes.')
443
487
 
444
- if self.plane2symmetrizer.theta_max:
488
+ if self.plane2symmetrizer.theta_max is not None:
445
489
  print('Symmetrizing ' + self.plane2 + ' planes...')
446
490
  for j, value in enumerate(q2):
447
491
  print(f'Symmetrizing {q2.nxname}={value:.02f}...', end='\r')
@@ -451,7 +495,7 @@ class Symmetrizer3D:
451
495
  out_array[:, j, :] = data_symmetrized[data.signal].nxdata
452
496
  print('\nSymmetrized ' + self.plane2 + ' planes.')
453
497
 
454
- if self.plane3symmetrizer.theta_max:
498
+ if self.plane3symmetrizer.theta_max is not None:
455
499
  print('Symmetrizing ' + self.plane3 + ' planes...')
456
500
  for i, value in enumerate(q1):
457
501
  print(f'Symmetrizing {q1.nxname}={value:.02f}...', end='\r')
@@ -492,7 +536,7 @@ class Symmetrizer3D:
492
536
  print("Output file saved to: " + os.path.join(os.getcwd(), fout_name))
493
537
 
494
538
 
495
- 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):
496
540
  """
497
541
  Generate a 3D Gaussian distribution.
498
542
 
@@ -514,16 +558,23 @@ def generate_gaussian(H, K, L, amp, stddev, lattice_params, coeffs=None):
514
558
  Coefficients for the Gaussian expression, including cross-terms between axes.
515
559
  Default is [1, 0, 1, 0, 1, 0],
516
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).
517
563
 
518
564
  Returns
519
565
  -------
520
566
  gaussian : ndarray
521
567
  3D Gaussian distribution array.
522
568
  """
569
+ if center is None:
570
+ center=(0,0,0)
523
571
  if coeffs is None:
524
572
  coeffs = [1, 0, 1, 0, 1, 0]
525
573
  a, b, c, al, be, ga = lattice_params
526
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]
527
578
  H, K, L = np.meshgrid(H, K, L, indexing='ij')
528
579
  gaussian = amp * np.exp(-(coeffs[0] * H ** 2 +
529
580
  coeffs[1] * (b_ * a_ / (a_ ** 2)) * H * K +
@@ -537,7 +588,6 @@ def generate_gaussian(H, K, L, amp, stddev, lattice_params, coeffs=None):
537
588
  gaussian = gaussian.transpose()
538
589
  return gaussian.transpose(1, 0, 2)
539
590
 
540
-
541
591
  class Puncher:
542
592
  """
543
593
  A class for applying masks to 3D datasets, typically for data processing in reciprocal space.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: nxs-analysis-tools
3
- Version: 0.0.42
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
 
@@ -19,4 +19,7 @@ tests/test_chess_fitting.py
19
19
  tests/test_datareduction.py
20
20
  tests/test_fitting.py
21
21
  tests/test_lmfit.py
22
- tests/test_pairdistribution.py
22
+ tests/test_mask_plotting.py
23
+ tests/test_pairdistribution.py
24
+ tests/test_plot_slice_with_ndarray.py
25
+ tests/test_symmetrizer_rectangular_plane.py