nxs-analysis-tools 0.0.35__py3-none-any.whl → 0.0.36__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 CHANGED
@@ -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.35'
9
+ __version__= '0.0.36'
10
10
  __repo_url__ = 'https://github.com/stevenjgomez/nxs_analysis_tools'
@@ -4,7 +4,8 @@ Reduce and transform nexus format (.nxs) scattering data.
4
4
 
5
5
  import numpy as np
6
6
  from _meta import __author__, __copyright__, __license__, __version__
7
- from .datareduction import load_data, load_transform, plot_slice, reciprocal_lattice_params, Scissors, rotate_data
7
+ from .datareduction import load_data, load_transform, plot_slice,\
8
+ reciprocal_lattice_params, Scissors, rotate_data
8
9
  from .chess import TempDependence
9
10
 
10
11
  # What to import when running "from nxs_analysis_tools import *"
@@ -4,11 +4,12 @@ This module provides classes and functions for analyzing scattering datasets col
4
4
  plotting linecuts.
5
5
  """
6
6
  import os
7
+ import re
8
+
7
9
  import matplotlib.pyplot as plt
8
10
  import matplotlib as mpl
9
11
  import numpy as np
10
12
  from IPython.display import display, Markdown
11
- from nexusformat.nexus import nxload
12
13
  from nxs_analysis_tools import load_data, Scissors
13
14
  from nxs_analysis_tools.fitting import LinecutModel
14
15
  from nxs_analysis_tools.datareduction import load_transform, reciprocal_lattice_params
@@ -16,14 +17,93 @@ from nxs_analysis_tools.datareduction import load_transform, reciprocal_lattice_
16
17
 
17
18
  class TempDependence:
18
19
  """
19
- Class for analyzing scattering datasets collected at CHESS (ID4B) with temperature dependence.
20
+ A class for analyzing temperature-dependent scattering datasets collected at CHESS (ID4B).
21
+
22
+ The `TempDependence` class facilitates the loading, processing, and analysis of scattering
23
+ data across different temperatures. It includes methods for handling datasets, setting
24
+ lattice parameters, performing linecuts, modeling the data, and visualizing the results.
25
+
26
+ Attributes
27
+ ----------
28
+ sample_directory : str
29
+ Path to the directory containing the datasets.
30
+ xlabel : str
31
+ Label for the x-axis of plots, determined by the axis of the linecuts.
32
+ datasets : dict
33
+ Dictionary storing datasets keyed by temperature.
34
+ temperatures : list of str
35
+ List of temperatures for which data is available.
36
+ scissors : dict
37
+ Dictionary of Scissors objects, one for each temperature, used for data manipulation and
38
+ linecut operations.
39
+ linecuts : dict
40
+ Dictionary storing the linecut data for each temperature.
41
+ linecutmodels : dict
42
+ Dictionary of LinecutModel objects, one for each temperature, used for fitting the linecuts.
43
+ a, b, c, al, be, ga : float or None
44
+ Lattice parameters (a, b, c, alpha, beta, gamma) of the crystal.
45
+ a_star, b_star, c_star, al_star, be_star, ga_star : float or None
46
+ Reciprocal lattice parameters (a*, b*, c*, alpha*, beta*, gamma*).
47
+
48
+ Methods
49
+ -------
50
+ set_temperatures(temperatures):
51
+ Set the list of temperatures for the datasets.
52
+ set_sample_directory(path):
53
+ Set the directory path where the datasets are located.
54
+ initialize():
55
+ Initialize Scissors and LinecutModel objects for each temperature.
56
+ set_data(temperature, data):
57
+ Set the dataset for a specific temperature.
58
+ load_transforms(temperatures_list=None):
59
+ Load transform datasets (from nxrefine) based on temperature.
60
+ load_datasets(file_ending='hkli.nxs', temperatures_list=None):
61
+ Load datasets (CHESS format) from the specified folder.
62
+ get_sample_directory():
63
+ Get the folder path where the datasets are located.
64
+ clear_datasets():
65
+ Clear the datasets stored in the TempDependence instance.
66
+ set_Lattice_params(lattice_params):
67
+ Set lattice parameters and calculate reciprocal lattice parameters.
68
+ set_window(window, verbose=False):
69
+ Set the extents of the integration window for each temperature.
70
+ set_center(center):
71
+ Set the central coordinate for the linecut for each temperature.
72
+ cut_data(center=None, window=None, axis=None, verbose=False):
73
+ Perform data cutting for each temperature dataset.
74
+ plot_linecuts(vertical_offset=0, **kwargs):
75
+ Plot the linecuts obtained from data cutting.
76
+ plot_linecuts_heatmap(ax=None, **kwargs):
77
+ Plot a heatmap of the linecuts obtained from data cutting.
78
+ highlight_integration_window(temperature=None, **kwargs):
79
+ Display the integration window plot for a specific temperature.
80
+ plot_integration_window(temperature=None, **kwargs):
81
+ Plot the integration window cross-sections for a specific temperature.
82
+ set_model_components(model_components):
83
+ Set the model components for all line cut models.
84
+ set_param_hint(*args, **kwargs):
85
+ Set parameter hints for all line cut models.
86
+ make_params():
87
+ Create parameters for all line cut models.
88
+ guess():
89
+ Make initial parameter guesses for all line cut models.
90
+ print_initial_params():
91
+ Print the initial parameter values for all line cut models.
92
+ plot_initial_guess():
93
+ Plot the initial guess for all line cut models.
94
+ fit(verbose=False):
95
+ Fit the line cut models for each temperature.
96
+ plot_fit(mdheadings=False, **kwargs):
97
+ Plot the fit results for each temperature.
98
+ print_fit_report():
99
+ Print the fit report for each temperature.
20
100
  """
21
101
 
22
102
  def __init__(self):
23
103
  """
24
- Initialize TempDependence class.
104
+ Initialize the TempDependence class with default values.
25
105
  """
26
- self.nxs_files_list = []
106
+
27
107
  self.sample_directory = ''
28
108
  self.xlabel = ''
29
109
  self.datasets = {}
@@ -31,63 +111,107 @@ class TempDependence:
31
111
  self.scissors = {}
32
112
  self.linecuts = {}
33
113
  self.linecutmodels = {}
114
+ self.a, self.b, self.c, self.al, self.be, self.ga, \
115
+ self.a_star, self.b_star, self.c_star, self.al_star, self.be_star, self.ga_star \
116
+ = [None] * 12
34
117
 
35
118
  def set_temperatures(self, temperatures):
119
+ """
120
+ Set the list of temperatures for the datasets.
121
+
122
+ Parameters
123
+ ----------
124
+ temperatures : list
125
+ List of temperatures to set.
126
+ """
36
127
  self.temperatures = temperatures
37
128
 
38
129
  def set_sample_directory(self, path):
130
+ """
131
+ Set the directory path where the datasets are located.
132
+
133
+ Parameters
134
+ ----------
135
+ path : str
136
+ Path to the sample directory.
137
+ """
39
138
  self.sample_directory = os.path.normpath(path)
40
139
 
41
140
  def initialize(self):
141
+ """
142
+ Initialize Scissors and LinecutModel objects for each temperature.
143
+ """
42
144
  for temperature in self.temperatures:
43
145
  self.scissors[temperature] = Scissors()
44
146
  self.scissors[temperature] = LinecutModel()
45
147
 
46
148
  def set_data(self, temperature, data):
149
+ """
150
+ Set the dataset for a specific temperature.
151
+
152
+ Parameters
153
+ ----------
154
+ temperature : str
155
+ Temperature for which to set the data.
156
+ data : object
157
+ The dataset to be set.
158
+ """
47
159
  self.datasets[temperature] = data
48
160
 
49
161
  def load_transforms(self, temperatures_list=None):
162
+ """
163
+ Load transform datasets (from nxrefine) based on temperature.
164
+
165
+ Parameters
166
+ ----------
167
+ temperatures_list : list of int or None, optional
168
+ List of temperatures to load. If None, all available temperatures are loaded.
169
+ """
50
170
  # Convert all temperatures to strings
51
171
  if temperatures_list:
52
172
  temperatures_list = [str(t) for t in temperatures_list]
53
173
 
54
- # Search all files in the sample directory
174
+ # Identify files to load
175
+ items_to_load = []
176
+ # Search for nxrefine .nxs files
55
177
  for item in os.listdir(self.sample_directory):
56
-
57
- # Find the nxs files
58
- if item.endswith('.nxs'):
59
-
60
- # Load the nxs files
61
- path = os.path.join(self.sample_directory, item)
62
- g = nxload(path)
63
-
64
- # Extract the sample temperature
65
- temperature = str(int(np.round(g.entry.sample.temperature.nxdata)))
66
-
67
- # Load all samples (or a subset if temperatures_list is provided)
178
+ pattern = r'_(\d+)\.nxs'
179
+ match = re.search(pattern, item)
180
+ if match:
181
+ print(f'Found {item}')
182
+ # Identify temperature
183
+ temperature = match.group(1)
184
+ # print(f'Temperature = {temperature}')
68
185
  if (temperatures_list is None) or (temperature in temperatures_list):
186
+ # Prepare file to be loaded
69
187
  self.temperatures.append(temperature)
70
- self.datasets[temperature] = load_transform(path)
71
- # Initialize scissors object at each temperature
72
- self.scissors[temperature] = Scissors()
73
- self.scissors[temperature].set_data(self.datasets[temperature])
188
+ items_to_load.append(item)
189
+ # print(f'Preparing to load {temperature} K data: {item}')
74
190
 
75
- # Initialize linecutmodel object at each temperature
76
- self.linecutmodels[temperature] = LinecutModel()
77
-
78
- # Convert to int temporarily to sort temperatures list
191
+ # Convert all temperatures to int temporarily to sort temperatures list before loading
79
192
  self.temperatures = [int(t) for t in self.temperatures]
80
193
  self.temperatures.sort()
81
194
  self.temperatures = [str(t) for t in self.temperatures]
82
195
 
196
+ for i, item in enumerate(items_to_load):
197
+ path = os.path.join(self.sample_directory, item)
198
+
199
+ # Save dataset
200
+ self.datasets[self.temperatures[i]] = load_transform(path)
201
+
202
+ # Initialize scissors object
203
+ self.scissors[self.temperatures[i]] = Scissors()
204
+ self.scissors[self.temperatures[i]].set_data(self.datasets[self.temperatures[i]])
205
+
206
+ # Initialize linecutmodel object
207
+ self.linecutmodels[self.temperatures[i]] = LinecutModel()
208
+
83
209
  def load_datasets(self, file_ending='hkli.nxs', temperatures_list=None):
84
210
  """
85
- Load scattering datasets from the specified folder.
211
+ Load datasets (CHESS format) from the specified folder.
86
212
 
87
213
  Parameters
88
214
  ----------
89
- folder : str
90
- The path to the folder where the datasets are located.
91
215
  file_ending : str, optional
92
216
  The file extension of the datasets to be loaded. The default is 'hkli.nxs'.
93
217
  temperatures_list : list of int or None, optional
@@ -136,8 +260,8 @@ class TempDependence:
136
260
 
137
261
  Returns
138
262
  -------
139
- str:
140
- The folder path.
263
+ str
264
+ The folder path.
141
265
  """
142
266
  return self.sample_directory
143
267
 
@@ -147,26 +271,39 @@ class TempDependence:
147
271
  """
148
272
  self.datasets = {}
149
273
 
150
- def set_Lattice_params(self, lattice_params):
274
+ def set_lattice_params(self, lattice_params):
275
+ """
276
+ Set lattice parameters and calculate reciprocal lattice parameters.
277
+
278
+ Parameters
279
+ ----------
280
+ lattice_params : tuple
281
+ Tuple containing lattice parameters (a, b, c, al, be, ga).
282
+ """
151
283
  self.a, self.b, self.c, self.al, self.be, self.ga = lattice_params
152
- self.a_star, self.b_star, self.c_star, self.al_star, self.be_star, self.ga_star = reciprocal_lattice_params(lattice_params)
153
- def set_window(self, window):
284
+ self.a_star, self.b_star, self.c_star, \
285
+ self.al_star, self.be_star, self.ga_star = reciprocal_lattice_params(lattice_params)
286
+
287
+ def set_window(self, window, verbose=False):
154
288
  """
155
- Set the extents of the integration window.
289
+ Set the extents of the integration window for each temperature.
156
290
 
157
291
  Parameters
158
292
  ----------
159
293
  window : tuple
160
294
  Extents of the window for integration along each axis.
295
+ verbose : bool, optional
296
+ Enables printout of linecut axis and integrated axes. Default is False.
161
297
  """
162
298
  for T in self.temperatures:
163
- print("----------------------------------")
164
- print("T = " + T + " K")
165
- self.scissors[T].set_window(window)
299
+ if verbose:
300
+ print("----------------------------------")
301
+ print("T = " + T + " K")
302
+ self.scissors[T].set_window(window, verbose)
166
303
 
167
304
  def set_center(self, center):
168
305
  """
169
- Set the central coordinate for the linecut.
306
+ Set the central coordinate for the linecut for each temperature.
170
307
 
171
308
  Parameters
172
309
  ----------
@@ -176,39 +313,46 @@ class TempDependence:
176
313
  for T in self.temperatures:
177
314
  self.scissors[T].set_center(center)
178
315
 
179
- def cut_data(self, center=None, window=None, axis=None):
316
+ def cut_data(self, center=None, window=None, axis=None, verbose=False):
180
317
  """
181
318
  Perform data cutting for each temperature dataset.
182
319
 
183
320
  Parameters
184
321
  ----------
185
- center : tuple
322
+ center : tuple, optional
186
323
  The center point for cutting the data.
187
- window : tuple
324
+ Defaults to the first temperature's center if None.
325
+ window : tuple, optional
188
326
  The window size for cutting the data.
327
+ Defaults to the first temperature's window if None.
189
328
  axis : int or None, optional
190
- The axis along which to perform the cutting. If None, cutting is performed along the
191
- longest axis in `window`. The default is None.
329
+ The axis along which to perform the cutting.
330
+ Defaults to the longest axis in `window` if None.
331
+ verbose : bool, optional
332
+ Enables printout of linecut progress. Default is False.
192
333
 
193
334
  Returns
194
335
  -------
195
- list
196
- A list of linecuts obtained from the cutting operation.
336
+ dict
337
+ A dictionary of linecuts obtained from the cutting operation.
197
338
  """
198
339
 
199
340
  center = center if center is not None else self.scissors[self.temperatures[0]].center
200
341
  window = window if window is not None else self.scissors[self.temperatures[0]].window
342
+ axis = axis if axis is not None else self.scissors[self.temperatures[0]].axis
201
343
 
202
344
  for T in self.temperatures:
203
- print("-------------------------------")
204
- print("Cutting T = " + T + " K data...")
205
- self.scissors[T].cut_data(center, window, axis)
345
+ if verbose:
346
+ print("-------------------------------")
347
+ print("Cutting T = " + T + " K data...")
348
+ self.scissors[T].cut_data(center, window, axis, verbose)
206
349
  self.linecuts[T] = self.scissors[T].linecut
207
350
  self.linecutmodels[T].set_data(self.linecuts[T])
208
351
 
209
352
  xlabel_components = [self.linecuts[self.temperatures[0]].axes
210
353
  if i == self.scissors[self.temperatures[0]].axis
211
- else str(c) for i, c in enumerate(self.scissors[self.temperatures[0]].center)]
354
+ else str(c) for i, c in
355
+ enumerate(self.scissors[self.temperatures[0]].center)]
212
356
  self.xlabel = ' '.join(xlabel_components)
213
357
 
214
358
  return self.linecuts
@@ -250,10 +394,53 @@ class TempDependence:
250
394
 
251
395
  return fig, ax
252
396
 
397
+ def plot_linecuts_heatmap(self, ax=None, **kwargs):
398
+ """
399
+ Plot the linecuts obtained from data cutting.
400
+
401
+ Parameters
402
+ ----------
403
+ ax : matplotlib.axes.Axes, optional
404
+ The axes on which to plot the heatmap. If None, a new figure and axes
405
+ are created. The default is None.
406
+ **kwargs
407
+ Additional keyword arguments to be passed to the `pcolormesh` function.
408
+
409
+ Returns
410
+ -------
411
+ QuadMesh
412
+ The plotted heatmap object.
413
+ """
414
+
415
+ # Retrieve linecut data for the first temperature and extract x-axis data
416
+ cut = self.linecuts[self.temperatures[0]]
417
+ x = cut[cut.axes].nxdata
418
+
419
+ # Convert the list of temperatures to a NumPy array for the y-axis
420
+ y = np.array([int(t) for t in self.temperatures])
421
+
422
+ # Collect counts from each temperature and ensure they are numpy arrays
423
+ v = [self.linecuts[T].counts.nxdata for T in self.temperatures]
424
+
425
+ # Convert list of arrays to a 2D array for the heatmap
426
+ v_2d = np.array(v)
427
+
428
+ # Create the grid for the heatmap
429
+ X, Y = np.meshgrid(x, y)
430
+
431
+ # Plot using pcolormesh
432
+ if ax is None:
433
+ _, ax = plt.subplots()
434
+ p = ax.pcolormesh(X, Y, v_2d, **kwargs)
435
+ plt.colorbar(p, label='counts')
436
+ ax.set(xlabel=cut.axes, ylabel=r'$T$ (K)')
437
+
438
+ return p
439
+
253
440
  def highlight_integration_window(self, temperature=None, **kwargs):
254
441
  """
255
- Displays the integration window plot for a specific temperature, or for the first temperature if
256
- none is provided.
442
+ Displays the integration window plot for a specific temperature,
443
+ or for the first temperature if none is provided.
257
444
 
258
445
  Parameters
259
446
  ----------
@@ -266,8 +453,10 @@ class TempDependence:
266
453
  """
267
454
 
268
455
  if temperature is not None:
269
- p = self.scissors[self.temperatures[0]].highlight_integration_window(data=self.datasets[temperature],
270
- **kwargs)
456
+ p = self.scissors[
457
+ self.temperatures[0]].highlight_integration_window(
458
+ data=self.datasets[temperature], **kwargs
459
+ )
271
460
  else:
272
461
  p = self.scissors[self.temperatures[0]].highlight_integration_window(
273
462
  data=self.datasets[self.temperatures[0]], **kwargs
@@ -277,15 +466,18 @@ class TempDependence:
277
466
 
278
467
  def plot_integration_window(self, temperature=None, **kwargs):
279
468
  """
280
- Plots the three principal cross-sections of the integration volume on a single figure for a specific
281
- temperature, or for the first temperature if none is provided.
469
+ Plots the three principal cross-sections of the integration volume on
470
+ a single figure for a specific temperature, or for the first temperature
471
+ if none is provided.
282
472
 
283
473
  Parameters
284
474
  ----------
285
475
  temperature : str, optional
286
- The temperature at which to plot the integration volume. If provided, the plot
287
- will be generated using the dataset corresponding to the specified temperature. If not
288
- provided, the integration window plots will be generated for the first temperature.
476
+ The temperature at which to plot the integration volume. If provided,
477
+ the plot will be generated using the dataset corresponding to the
478
+ specified temperature. If not provided, the integration window plots
479
+ will be generated for the first temperature.
480
+
289
481
  **kwargs : keyword arguments, optional
290
482
  Additional keyword arguments to customize the plot.
291
483
  """
@@ -301,9 +493,9 @@ class TempDependence:
301
493
  """
302
494
  Set the model components for all line cut models.
303
495
 
304
- This method sets the same model components for all line cut models in the analysis.
305
- It iterates over each line cut model and calls their respective `set_model_components` method
306
- with the provided `model_components`.
496
+ This method sets the same model components for all line cut models in the
497
+ analysis. It iterates over each line cut model and calls their respective
498
+ `set_model_components` method with the provided `model_components`.
307
499
 
308
500
  Parameters
309
501
  ----------
@@ -311,7 +503,8 @@ class TempDependence:
311
503
  The model components to set for all line cut models.
312
504
 
313
505
  """
314
- [linecutmodel.set_model_components(model_components) for linecutmodel in self.linecutmodels.values()]
506
+ [linecutmodel.set_model_components(model_components) for
507
+ linecutmodel in self.linecutmodels.values()]
315
508
 
316
509
  def set_param_hint(self, *args, **kwargs):
317
510
  """
@@ -329,7 +522,8 @@ class TempDependence:
329
522
  Arbitrary keyword arguments.
330
523
 
331
524
  """
332
- [linecutmodel.set_param_hint(*args, **kwargs) for linecutmodel in self.linecutmodels.values()]
525
+ [linecutmodel.set_param_hint(*args, **kwargs)
526
+ for linecutmodel in self.linecutmodels.values()]
333
527
 
334
528
  def make_params(self):
335
529
  """
@@ -354,8 +548,9 @@ class TempDependence:
354
548
  """
355
549
  Print the initial parameter values for all line cut models.
356
550
 
357
- This method prints the initial parameter values for all line cut models in the analysis.
358
- It iterates over each line cut model and calls their respective `print_initial_params` method.
551
+ This method prints the initial parameter values for all line cut models
552
+ in the analysis. It iterates over each line cut model and calls their
553
+ respective `print_initial_params` method.
359
554
 
360
555
  """
361
556
  [linecutmodel.print_initial_params() for linecutmodel in self.linecutmodels.values()]
@@ -369,22 +564,29 @@ class TempDependence:
369
564
 
370
565
  """
371
566
  for T, linecutmodel in self.linecutmodels.items():
372
- fig, ax = plt.subplots()
567
+ _, ax = plt.subplots()
373
568
  ax.set(title=T + ' K')
374
569
  linecutmodel.plot_initial_guess()
375
570
 
376
- def fit(self):
571
+ def fit(self, verbose=False):
377
572
  """
378
573
  Fit the line cut models.
379
574
 
380
575
  This method fits the line cut models for each temperature in the analysis.
381
576
  It iterates over each line cut model, performs the fit, and prints the fitting progress.
382
577
 
578
+ Parameters
579
+ ----------
580
+ verbose : bool, optional
581
+ Enables printout of fitting progress. Default False.
582
+
383
583
  """
384
584
  for T, linecutmodel in self.linecutmodels.items():
385
- print(f"Fitting {T} K data...")
585
+ if verbose:
586
+ print(f"Fitting {T} K data...")
386
587
  linecutmodel.fit()
387
- print("Done.")
588
+ if verbose:
589
+ print("Done.")
388
590
  print("Fits completed.")
389
591
 
390
592
  def plot_fit(self, mdheadings=False, **kwargs):
@@ -401,7 +603,9 @@ class TempDependence:
401
603
  if mdheadings:
402
604
  display(Markdown(f"### {T} K Fit Results"))
403
605
  # Plot fit
404
- linecutmodel.plot_fit(xlabel=self.xlabel, ylabel=self.datasets[self.temperatures[0]].signal, title=f"{T} K",
606
+ linecutmodel.plot_fit(xlabel=self.xlabel,
607
+ ylabel=self.datasets[self.temperatures[0]].signal,
608
+ title=f"{T} K",
405
609
  **kwargs)
406
610
 
407
611
  def print_fit_report(self):