nxs-analysis-tools 0.0.47__py3-none-any.whl → 0.1.2__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.

@@ -1,697 +1,703 @@
1
- """
2
- This module provides classes and functions for analyzing scattering datasets collected at CHESS
3
- (ID4B) with temperature dependence. It includes functions for loading data, cutting data, and
4
- plotting linecuts.
5
- """
6
- import os
7
- import re
8
-
9
- import matplotlib.pyplot as plt
10
- import matplotlib as mpl
11
- import pandas as pd
12
- import numpy as np
13
- from IPython.display import display, Markdown
14
- from nxs_analysis_tools import load_data, Scissors
15
- from nxs_analysis_tools.fitting import LinecutModel
16
- from nxs_analysis_tools.datareduction import load_transform, reciprocal_lattice_params
17
-
18
-
19
- class TempDependence:
20
- """
21
- A class for analyzing temperature-dependent scattering datasets collected at CHESS (ID4B).
22
-
23
- The `TempDependence` class facilitates the loading, processing, and analysis of scattering
24
- data across different temperatures. It includes methods for handling datasets, setting
25
- lattice parameters, performing linecuts, modeling the data, and visualizing the results.
26
-
27
- Attributes
28
- ----------
29
- sample_directory : str
30
- Path to the directory containing the datasets.
31
- xlabel : str
32
- Label for the x-axis of plots, determined by the axis of the linecuts.
33
- datasets : dict
34
- Dictionary storing datasets keyed by temperature.
35
- temperatures : list of str
36
- List of temperatures for which data is available.
37
- scissors : dict
38
- Dictionary of Scissors objects, one for each temperature, used for data manipulation and
39
- linecut operations.
40
- linecuts : dict
41
- Dictionary storing the linecut data for each temperature.
42
- linecutmodels : dict
43
- Dictionary of LinecutModel objects, one for each temperature, used for fitting the linecuts.
44
- a, b, c, al, be, ga : float or None
45
- Lattice parameters (a, b, c, alpha, beta, gamma) of the crystal.
46
- a_star, b_star, c_star, al_star, be_star, ga_star : float or None
47
- Reciprocal lattice parameters (a*, b*, c*, alpha*, beta*, gamma*).
48
-
49
- Methods
50
- -------
51
- set_temperatures(temperatures):
52
- Set the list of temperatures for the datasets.
53
- find_temperatures():
54
- Set the list of temperatures by automatically scanning the sample directory.
55
- set_sample_directory(path):
56
- Set the directory path where the datasets are located.
57
- initialize():
58
- Initialize Scissors and LinecutModel objects for each temperature.
59
- set_data(temperature, data):
60
- Set the dataset for a specific temperature.
61
- load_transforms(temperatures_list=None, print_tree=True):
62
- Load transform datasets (from nxrefine) based on temperature.
63
- load_datasets(file_ending='hkli.nxs', temperatures_list=None, print_tree=True):
64
- Load datasets (CHESS format) from the specified folder.
65
- get_sample_directory():
66
- Get the folder path where the datasets are located.
67
- clear_datasets():
68
- Clear the datasets stored in the TempDependence instance.
69
- set_Lattice_params(lattice_params):
70
- Set lattice parameters and calculate reciprocal lattice parameters.
71
- set_window(window, verbose=False):
72
- Set the extents of the integration window for each temperature.
73
- set_center(center):
74
- Set the central coordinate for the linecut for each temperature.
75
- cut_data(center=None, window=None, axis=None, verbose=False):
76
- Perform data cutting for each temperature dataset.
77
- plot_linecuts(vertical_offset=0, **kwargs):
78
- Plot the linecuts obtained from data cutting.
79
- plot_linecuts_heatmap(ax=None, **kwargs):
80
- Plot a heatmap of the linecuts obtained from data cutting.
81
- highlight_integration_window(temperature=None, **kwargs):
82
- Display the integration window plot for a specific temperature.
83
- plot_integration_window(temperature=None, **kwargs):
84
- Plot the integration window cross-sections for a specific temperature.
85
- set_model_components(model_components):
86
- Set the model components for all line cut models.
87
- set_param_hint(*args, **kwargs):
88
- Set parameter hints for all line cut models.
89
- make_params():
90
- Create parameters for all line cut models.
91
- guess():
92
- Make initial parameter guesses for all line cut models.
93
- print_initial_params():
94
- Print the initial parameter values for all line cut models.
95
- plot_initial_guess():
96
- Plot the initial guess for all line cut models.
97
- fit(verbose=False):
98
- Fit the line cut models for each temperature.
99
- plot_fit(mdheadings=False, **kwargs):
100
- Plot the fit results for each temperature.
101
- plot_order_parameter(self):
102
- Plot the temperature dependence of the peakheight parameter.
103
- print_fit_report():
104
- Print the fit report for each temperature.
105
- """
106
-
107
- def __init__(self):
108
- """
109
- Initialize the TempDependence class with default values.
110
- """
111
-
112
- self.sample_directory = None
113
- self.xlabel = ''
114
- self.datasets = {}
115
- self.temperatures = []
116
- self.scissors = {}
117
- self.linecuts = {}
118
- self.linecutmodels = {}
119
- self.a, self.b, self.c, self.al, self.be, self.ga, \
120
- self.a_star, self.b_star, self.c_star, self.al_star, self.be_star, self.ga_star \
121
- = [None] * 12
122
-
123
- def set_temperatures(self, temperatures):
124
- """
125
- Set the list of temperatures for the datasets.
126
-
127
- Parameters
128
- ----------
129
- temperatures : list
130
- List of temperatures to set.
131
- """
132
- self.temperatures = temperatures
133
-
134
- def find_temperatures(self):
135
- """
136
- Set the list of temperatures by automatically scanning the sample directory.
137
- """
138
-
139
- # Assert that self.sample_directory must exist
140
- if self.sample_directory is None:
141
- raise ValueError("Sample directory is not set. Use set_sample_directory(path) first.")
142
-
143
- # Clear existing temperatures
144
- self.temperatures = []
145
-
146
- # Search for nxrefine .nxs files
147
- for item in os.listdir(self.sample_directory):
148
- pattern = r'_(\d+)\.nxs'
149
- match = re.search(pattern, item)
150
- if match:
151
- # Identify temperature
152
- temperature = match.group(1)
153
- self.temperatures.append(temperature)
154
- # Convert all temperatures to int temporarily to sort temperatures list
155
- self.temperatures = [int(t) for t in self.temperatures]
156
- self.temperatures.sort()
157
- self.temperatures = [str(t) for t in self.temperatures]
158
-
159
- def set_sample_directory(self, path):
160
- """
161
- Set the directory path where the datasets are located.
162
-
163
- Parameters
164
- ----------
165
- path : str
166
- Path to the sample directory.
167
- """
168
- self.sample_directory = os.path.normpath(path)
169
-
170
- def initialize(self):
171
- """
172
- Initialize Scissors and LinecutModel objects for each temperature.
173
- """
174
- for temperature in self.temperatures:
175
- self.scissors[temperature] = Scissors()
176
- self.scissors[temperature] = LinecutModel()
177
-
178
- def set_data(self, temperature, data):
179
- """
180
- Set the dataset for a specific temperature.
181
-
182
- Parameters
183
- ----------
184
- temperature : str
185
- Temperature for which to set the data.
186
- data : object
187
- The dataset to be set.
188
- """
189
- self.datasets[temperature] = data
190
-
191
- def load_transforms(self, temperatures_list=None, print_tree=True):
192
- """
193
- Load transform datasets (from nxrefine) based on temperature.
194
-
195
- Parameters
196
- ----------
197
- temperatures_list : list of int or None, optional
198
- List of temperatures to load. If None, all available temperatures are loaded.
199
- print_tree : bool, optional
200
- Whether to print the data tree upon loading. Default True.
201
- """
202
- # Convert all temperatures to strings
203
- if temperatures_list:
204
- temperatures_list = [str(t) for t in temperatures_list]
205
-
206
- # Clear existing temperatures before loading files
207
- self.temperatures = []
208
-
209
- # Identify files to load
210
- items_to_load = []
211
- # Search for nxrefine .nxs files
212
- for item in os.listdir(self.sample_directory):
213
- pattern = r'_(\d+)\.nxs'
214
- match = re.search(pattern, item)
215
- if match:
216
- # Identify temperature
217
- temperature = match.group(1)
218
- # print(f'Temperature = {temperature}')
219
- if (temperatures_list is None) or (temperature in temperatures_list):
220
- # Prepare file to be loaded
221
- self.temperatures.append(temperature)
222
- items_to_load.append(item)
223
- # print(f'Preparing to load {temperature} K data: {item}')
224
- # Convert all temperatures to int temporarily to sort temperatures list before loading
225
- self.temperatures = [int(t) for t in self.temperatures]
226
-
227
- loading_template = pd.DataFrame({'temperature': self.temperatures, 'filename': items_to_load})
228
- loading_template = loading_template.sort_values(by='temperature')
229
- self.temperatures = loading_template['temperature']
230
- self.temperatures = [str(t) for t in self.temperatures]
231
- items_to_load = loading_template['filename'].to_list()
232
-
233
- for i, item in enumerate(items_to_load):
234
- path = os.path.join(self.sample_directory, item)
235
-
236
- # Save dataset
237
- try:
238
- self.datasets[self.temperatures[i]] = load_transform(path, print_tree)
239
- except Exception as e:
240
- # Report temperature that was unable to load, then raise exception.
241
- temp_failed = self.temperatures[i]
242
- print(f"Failed to load data for temperature {temp_failed} K from file {item}. Error: {e}")
243
- raise # Re-raise the exception
244
-
245
- # Initialize scissors object
246
- self.scissors[self.temperatures[i]] = Scissors()
247
- self.scissors[self.temperatures[i]].set_data(self.datasets[self.temperatures[i]])
248
-
249
- # Initialize linecutmodel object
250
- self.linecutmodels[self.temperatures[i]] = LinecutModel()
251
-
252
- def load_datasets(self, file_ending='hkli.nxs', temperatures_list=None, print_tree=True):
253
- """
254
- Load datasets (CHESS format) from the specified folder.
255
-
256
- Parameters
257
- ----------
258
- file_ending : str, optional
259
- The file extension of the datasets to be loaded. The default is 'hkli.nxs'.
260
- temperatures_list : list of int or None, optional
261
- The list of specific temperatures to load. If None, all available temperatures are
262
- loaded. The default is None.
263
- print_tree : bool, optional
264
- Whether to print the data tree upon loading. Default True.
265
- """
266
- temperature_folders = [] # Empty list to store temperature folder names
267
- for item in os.listdir(self.sample_directory):
268
- try:
269
- temperature_folders.append(int(item)) # If folder name can be int, add it
270
- except ValueError:
271
- pass # Otherwise don't add it
272
- temperature_folders.sort() # Sort from low to high T
273
- temperature_folders = [str(i) for i in temperature_folders] # Convert to strings
274
-
275
- self.temperatures = temperature_folders
276
-
277
- if temperatures_list is not None:
278
- self.temperatures = [str(t) for t in temperatures_list]
279
-
280
- # Load .nxs files
281
- for T in self.temperatures:
282
- for file in os.listdir(os.path.join(self.sample_directory, T)):
283
- if file.endswith(file_ending):
284
- filepath = os.path.join(self.sample_directory, T, file)
285
-
286
- # Load dataset at each temperature
287
- self.datasets[T] = load_data(filepath, print_tree)
288
-
289
- # Initialize scissors object at each temperature
290
- self.scissors[T] = Scissors()
291
- self.scissors[T].set_data(self.datasets[T])
292
-
293
- # Initialize linecutmodel object at each temperature
294
- self.linecutmodels[T] = LinecutModel()
295
-
296
- def get_sample_directory(self):
297
- """
298
- Get the folder path where the datasets are located.
299
-
300
- Returns
301
- -------
302
- str
303
- The folder path.
304
- """
305
- return self.sample_directory
306
-
307
- def clear_datasets(self):
308
- """
309
- Clear the datasets stored in the TempDependence instance.
310
- """
311
- self.datasets = {}
312
-
313
- def set_lattice_params(self, lattice_params):
314
- """
315
- Set lattice parameters and calculate reciprocal lattice parameters.
316
-
317
- Parameters
318
- ----------
319
- lattice_params : tuple
320
- Tuple containing lattice parameters (a, b, c, al, be, ga).
321
- """
322
- self.a, self.b, self.c, self.al, self.be, self.ga = lattice_params
323
- self.a_star, self.b_star, self.c_star, \
324
- self.al_star, self.be_star, self.ga_star = reciprocal_lattice_params(lattice_params)
325
-
326
- def set_window(self, window, verbose=False):
327
- """
328
- Set the extents of the integration window for each temperature.
329
-
330
- Parameters
331
- ----------
332
- window : tuple
333
- Extents of the window for integration along each axis.
334
- verbose : bool, optional
335
- Enables printout of linecut axis and integrated axes. Default is False.
336
- """
337
- for T in self.temperatures:
338
- if verbose:
339
- print("----------------------------------")
340
- print("T = " + T + " K")
341
- self.scissors[T].set_window(window, verbose)
342
-
343
- def set_center(self, center):
344
- """
345
- Set the central coordinate for the linecut for each temperature.
346
-
347
- Parameters
348
- ----------
349
- center : tuple
350
- Central coordinate around which to perform the linecut.
351
- """
352
- for T in self.temperatures:
353
- self.scissors[T].set_center(center)
354
-
355
- def cut_data(self, center=None, window=None, axis=None, verbose=False):
356
- """
357
- Perform data cutting for each temperature dataset.
358
-
359
- Parameters
360
- ----------
361
- center : tuple, optional
362
- The center point for cutting the data.
363
- Defaults to the first temperature's center if None.
364
- window : tuple, optional
365
- The window size for cutting the data.
366
- Defaults to the first temperature's window if None.
367
- axis : int or None, optional
368
- The axis along which to perform the cutting.
369
- Defaults to the longest axis in `window` if None.
370
- verbose : bool, optional
371
- Enables printout of linecut progress. Default is False.
372
-
373
- Returns
374
- -------
375
- dict
376
- A dictionary of linecuts obtained from the cutting operation.
377
- """
378
-
379
- for T in self.temperatures:
380
- if verbose:
381
- print("-------------------------------")
382
- print("Cutting T = " + T + " K data...")
383
- self.scissors[T].set_center(center)
384
- self.scissors[T].set_window(window)
385
- self.scissors[T].cut_data(axis=axis, verbose=verbose)
386
- self.linecuts[T] = self.scissors[T].linecut
387
- self.linecutmodels[T].set_data(self.linecuts[T])
388
-
389
- xlabel_components = [self.linecuts[self.temperatures[0]].axes
390
- if i == self.scissors[self.temperatures[0]].axis
391
- else str(c) for i, c in
392
- enumerate(self.scissors[self.temperatures[0]].center)]
393
- self.xlabel = ' '.join(xlabel_components)
394
-
395
- return self.linecuts
396
-
397
- def plot_linecuts(self, vertical_offset=0, **kwargs):
398
- """
399
- Plot the linecuts obtained from data cutting.
400
-
401
- Parameters
402
- ----------
403
- vertical_offset : float, optional
404
- The vertical offset between linecuts on the plot. The default is 0.
405
- **kwargs
406
- Additional keyword arguments to be passed to the plot function.
407
- """
408
- fig, ax = plt.subplots()
409
-
410
- # Get the Viridis colormap
411
- cmap = mpl.colormaps.get_cmap('viridis')
412
-
413
- for i, linecut in enumerate(self.linecuts.values()):
414
- x_data = linecut[linecut.axes].nxdata
415
- y_data = linecut[linecut.signal].nxdata + i * vertical_offset
416
- ax.plot(x_data, y_data, color=cmap(i / len(self.linecuts)), label=self.temperatures[i],
417
- **kwargs)
418
-
419
- ax.set(xlabel=self.xlabel,
420
- ylabel=self.linecuts[self.temperatures[0]].signal)
421
-
422
- # Get the current legend handles and labels
423
- handles, labels = plt.gca().get_legend_handles_labels()
424
-
425
- # Reverse the order of handles and labels
426
- handles = handles[::-1]
427
- labels = labels[::-1]
428
-
429
- # Create a new legend with reversed order
430
- plt.legend(handles, labels)
431
-
432
- return fig, ax
433
-
434
- def plot_linecuts_heatmap(self, ax=None, **kwargs):
435
- """
436
- Plot the linecuts obtained from data cutting.
437
-
438
- Parameters
439
- ----------
440
- ax : matplotlib.axes.Axes, optional
441
- The axes on which to plot the heatmap. If None, a new figure and axes
442
- are created. The default is None.
443
- **kwargs
444
- Additional keyword arguments to be passed to the `pcolormesh` function.
445
-
446
- Returns
447
- -------
448
- QuadMesh
449
- The plotted heatmap object.
450
- """
451
-
452
- # Retrieve linecut data for the first temperature and extract x-axis data
453
- cut = self.linecuts[self.temperatures[0]]
454
- x = cut[cut.axes].nxdata
455
-
456
- # Convert the list of temperatures to a NumPy array for the y-axis
457
- y = np.array([int(t) for t in self.temperatures])
458
-
459
- # Collect counts from each temperature and ensure they are numpy arrays
460
- v = [self.linecuts[T].counts.nxdata for T in self.temperatures]
461
-
462
- # Convert list of arrays to a 2D array for the heatmap
463
- v_2d = np.array(v)
464
-
465
- # Create the grid for the heatmap
466
- X, Y = np.meshgrid(x, y)
467
-
468
- # Plot using pcolormesh
469
- if ax is None:
470
- _, ax = plt.subplots()
471
- p = ax.pcolormesh(X, Y, v_2d, **kwargs)
472
- plt.colorbar(p, label='counts')
473
- ax.set(xlabel=self.xlabel, ylabel=r'$T$ (K)')
474
-
475
- return p
476
-
477
- def highlight_integration_window(self, temperature=None, **kwargs):
478
- """
479
- Displays the integration window plot for a specific temperature,
480
- or for the first temperature if none is provided.
481
-
482
- Parameters
483
- ----------
484
- temperature : str, optional
485
- The temperature at which to display the integration window plot. If provided, the plot
486
- will be generated using the dataset corresponding to the specified temperature. If not
487
- provided, the integration window plots will be generated for the first temperature.
488
- **kwargs : keyword arguments, optional
489
- Additional keyword arguments to customize the plot.
490
- """
491
-
492
- if temperature is not None:
493
- p = self.scissors[
494
- self.temperatures[0]].highlight_integration_window(
495
- data=self.datasets[temperature], **kwargs
496
- )
497
- else:
498
- p = self.scissors[self.temperatures[0]].highlight_integration_window(
499
- data=self.datasets[self.temperatures[0]], **kwargs
500
- )
501
-
502
- return p
503
-
504
- def plot_integration_window(self, temperature=None, **kwargs):
505
- """
506
- Plots the three principal cross-sections of the integration volume on
507
- a single figure for a specific temperature, or for the first temperature
508
- if none is provided.
509
-
510
- Parameters
511
- ----------
512
- temperature : str, optional
513
- The temperature at which to plot the integration volume. If provided,
514
- the plot will be generated using the dataset corresponding to the
515
- specified temperature. If not provided, the integration window plots
516
- will be generated for the first temperature.
517
-
518
- **kwargs : keyword arguments, optional
519
- Additional keyword arguments to customize the plot.
520
- """
521
-
522
- if temperature is not None:
523
- p = self.scissors[self.temperatures[0]].plot_integration_window(**kwargs)
524
- else:
525
- p = self.scissors[self.temperatures[0]].plot_integration_window(**kwargs)
526
-
527
- return p
528
-
529
- def set_model_components(self, model_components):
530
- """
531
- Set the model components for all line cut models.
532
-
533
- This method sets the same model components for all line cut models in the
534
- analysis. It iterates over each line cut model and calls their respective
535
- `set_model_components` method with the provided `model_components`.
536
-
537
- Parameters
538
- ----------
539
- model_components : Model or iterable of Model
540
- The model components to set for all line cut models.
541
-
542
- """
543
- [linecutmodel.set_model_components(model_components) for
544
- linecutmodel in self.linecutmodels.values()]
545
-
546
- def set_param_hint(self, *args, **kwargs):
547
- """
548
- Set parameter hints for all line cut models.
549
-
550
- This method sets the parameter hints for all line cut models in the analysis.
551
- It iterates over each line cut model and calls their respective `set_param_hint` method
552
- with the provided arguments and keyword arguments.
553
-
554
- Parameters
555
- ----------
556
- *args
557
- Variable length argument list.
558
- **kwargs
559
- Arbitrary keyword arguments.
560
-
561
- """
562
- [linecutmodel.set_param_hint(*args, **kwargs)
563
- for linecutmodel in self.linecutmodels.values()]
564
-
565
- def make_params(self):
566
- """
567
- Make parameters for all line cut models.
568
-
569
- This method creates the parameters for all line cut models in the analysis.
570
- It iterates over each line cut model and calls their respective `make_params` method.
571
- """
572
- [linecutmodel.make_params() for linecutmodel in self.linecutmodels.values()]
573
-
574
- def guess(self):
575
- """
576
- Make initial parameter guesses for all line cut models.
577
-
578
- This method generates initial parameter guesses for all line cut models in the analysis.
579
- It iterates over each line cut model and calls their respective `guess` method.
580
-
581
- """
582
- [linecutmodel.guess() for linecutmodel in self.linecutmodels.values()]
583
-
584
- def print_initial_params(self):
585
- """
586
- Print the initial parameter values for all line cut models.
587
-
588
- This method prints the initial parameter values for all line cut models
589
- in the analysis. It iterates over each line cut model and calls their
590
- respective `print_initial_params` method.
591
-
592
- """
593
- [linecutmodel.print_initial_params() for linecutmodel in self.linecutmodels.values()]
594
-
595
- def plot_initial_guess(self):
596
- """
597
- Plot the initial guess for all line cut models.
598
-
599
- This method plots the initial guess for all line cut models in the analysis.
600
- It iterates over each line cut model and calls their respective `plot_initial_guess` method.
601
-
602
- """
603
- for T, linecutmodel in self.linecutmodels.items():
604
- _, ax = plt.subplots()
605
- ax.set(title=T + ' K')
606
- linecutmodel.plot_initial_guess()
607
-
608
- def fit(self, verbose=False):
609
- """
610
- Fit the line cut models.
611
-
612
- This method fits the line cut models for each temperature in the analysis.
613
- It iterates over each line cut model, performs the fit, and prints the fitting progress.
614
-
615
- Parameters
616
- ----------
617
- verbose : bool, optional
618
- Enables printout of fitting progress. Default False.
619
-
620
- """
621
- for T, linecutmodel in self.linecutmodels.items():
622
- if verbose:
623
- print(f"Fitting {T} K data...")
624
- linecutmodel.fit()
625
- if verbose:
626
- print("Done.")
627
- print("Fits completed.")
628
-
629
- def plot_fit(self, mdheadings=False, **kwargs):
630
- """
631
- Plot the fit results.
632
-
633
- This method plots the fit results for each temperature in the analysis.
634
- It iterates over each line cut model, calls their respective `plot_fit` method,
635
- and sets the xlabel, ylabel, and title for the plot.
636
-
637
- """
638
- for T, linecutmodel in self.linecutmodels.items():
639
- # Create a markdown heading for the plot
640
- if mdheadings:
641
- display(Markdown(f"### {T} K Fit Results"))
642
- # Plot fit
643
- linecutmodel.plot_fit(xlabel=self.xlabel,
644
- ylabel=self.datasets[self.temperatures[0]].signal,
645
- title=f"{T} K",
646
- **kwargs)
647
-
648
- def plot_order_parameter(self):
649
- """
650
- Plot the temperature dependence of the peak height (order parameter).
651
-
652
- This method extracts the peak height from each temperature-dependent
653
- line cut fit stored in `linecutmodels` and plots it as a function
654
- of temperature using matplotlib.
655
-
656
- Returns
657
- -------
658
- Figure
659
- Matplotlib Figure object containing the peak height vs. temperature plot.
660
- Axes
661
- Matplotlib Axes object associated with the figure.
662
-
663
- Notes
664
- -----
665
- - Temperature values are converted to integers for plotting.
666
- - Peak heights are extracted from the 'peakheight' parameter in the model results.
667
- - The plot uses standard axes labels with temperature in Kelvin.
668
- """
669
-
670
- # Create an array of temperature values
671
- temperatures = [int(T) for T in self.temperatures]
672
-
673
- # Create an empty list for the peak heights
674
- peakheights = []
675
-
676
- # Extract the peakheight at every temperature
677
- for T in self.temperatures:
678
- peakheights.append(self.linecutmodels[T].modelresult.params['peakheight'].value)
679
-
680
- # Plot the peakheights vs. temperature
681
- fig, ax = plt.subplots()
682
- ax.plot(temperatures, peakheights)
683
- ax.set(xlabel='$T$ (K)', ylabel='peakheight')
684
- return fig, ax
685
-
686
- def print_fit_report(self):
687
- """
688
- Plot the fit results.
689
-
690
- This method plots the fit results for each temperature in the analysis.
691
- It iterates over each line cut model, calls their respective `plot_fit` method,
692
- and sets the xlabel, ylabel, and title for the plot.
693
-
694
- """
695
- for T, linecutmodel in self.linecutmodels.items():
696
- print(f"[[[{T} K Fit Report]]]")
697
- linecutmodel.print_fit_report()
1
+ """
2
+ This module provides classes and functions for analyzing scattering datasets collected at CHESS
3
+ (ID4B) with temperature dependence. It includes functions for loading data, cutting data, and
4
+ plotting linecuts.
5
+ """
6
+ import os
7
+ import re
8
+
9
+ import matplotlib.pyplot as plt
10
+ import matplotlib as mpl
11
+ import pandas as pd
12
+ import numpy as np
13
+ from IPython.display import display, Markdown
14
+ from nxs_analysis_tools import load_data, Scissors
15
+ from nxs_analysis_tools.fitting import LinecutModel
16
+ from nxs_analysis_tools.datareduction import load_transform, reciprocal_lattice_params
17
+
18
+
19
+ class TempDependence:
20
+ """
21
+ A class for analyzing temperature-dependent scattering datasets collected at CHESS (ID4B).
22
+
23
+ The `TempDependence` class facilitates the loading, processing, and analysis of scattering
24
+ data across different temperatures. It includes methods for handling datasets, setting
25
+ lattice parameters, performing linecuts, modeling the data, and visualizing the results.
26
+
27
+ Attributes
28
+ ----------
29
+ sample_directory : str
30
+ Path to the directory containing the datasets.
31
+ xlabel : str
32
+ Label for the x-axis of plots, determined by the axis of the linecuts.
33
+ datasets : dict
34
+ Dictionary storing datasets keyed by temperature.
35
+ temperatures : list of str
36
+ List of temperatures for which data is available.
37
+ scissors : dict
38
+ Dictionary of Scissors objects, one for each temperature, used for data manipulation and
39
+ linecut operations.
40
+ linecuts : dict
41
+ Dictionary storing the linecut data for each temperature.
42
+ linecutmodels : dict
43
+ Dictionary of LinecutModel objects, one for each temperature, used for fitting the linecuts.
44
+ a, b, c, al, be, ga : float or None
45
+ Lattice parameters (a, b, c, alpha, beta, gamma) of the crystal.
46
+ a_star, b_star, c_star, al_star, be_star, ga_star : float or None
47
+ Reciprocal lattice parameters (a*, b*, c*, alpha*, beta*, gamma*).
48
+
49
+ Methods
50
+ -------
51
+ set_temperatures(temperatures):
52
+ Set the list of temperatures for the datasets.
53
+ find_temperatures():
54
+ Set the list of temperatures by automatically scanning the sample directory.
55
+ set_sample_directory(path):
56
+ Set the directory path where the datasets are located.
57
+ initialize():
58
+ Initialize Scissors and LinecutModel objects for each temperature.
59
+ set_data(temperature, data):
60
+ Set the dataset for a specific temperature.
61
+ load_transforms(temperatures_list=None, print_tree=True):
62
+ Load transform datasets (from nxrefine) based on temperature.
63
+ load_datasets(file_ending='hkli.nxs', temperatures_list=None, print_tree=True):
64
+ Load datasets (CHESS format) from the specified folder.
65
+ get_sample_directory():
66
+ Get the folder path where the datasets are located.
67
+ clear_datasets():
68
+ Clear the datasets stored in the TempDependence instance.
69
+ set_Lattice_params(lattice_params):
70
+ Set lattice parameters and calculate reciprocal lattice parameters.
71
+ set_window(window, verbose=False):
72
+ Set the extents of the integration window for each temperature.
73
+ set_center(center):
74
+ Set the central coordinate for the linecut for each temperature.
75
+ cut_data(center=None, window=None, axis=None, verbose=False):
76
+ Perform data cutting for each temperature dataset.
77
+ plot_linecuts(vertical_offset=0, **kwargs):
78
+ Plot the linecuts obtained from data cutting.
79
+ plot_linecuts_heatmap(ax=None, **kwargs):
80
+ Plot a heatmap of the linecuts obtained from data cutting.
81
+ highlight_integration_window(temperature=None, **kwargs):
82
+ Display the integration window plot for a specific temperature.
83
+ plot_integration_window(temperature=None, **kwargs):
84
+ Plot the integration window cross-sections for a specific temperature.
85
+ set_model_components(model_components):
86
+ Set the model components for all line cut models.
87
+ set_param_hint(*args, **kwargs):
88
+ Set parameter hints for all line cut models.
89
+ make_params():
90
+ Create parameters for all line cut models.
91
+ guess():
92
+ Make initial parameter guesses for all line cut models.
93
+ print_initial_params():
94
+ Print the initial parameter values for all line cut models.
95
+ plot_initial_guess():
96
+ Plot the initial guess for all line cut models.
97
+ fit(verbose=False):
98
+ Fit the line cut models for each temperature.
99
+ plot_fit(mdheadings=False, **kwargs):
100
+ Plot the fit results for each temperature.
101
+ plot_order_parameter(self):
102
+ Plot the temperature dependence of the peakheight parameter.
103
+ print_fit_report():
104
+ Print the fit report for each temperature.
105
+ """
106
+
107
+ def __init__(self):
108
+ """
109
+ Initialize the TempDependence class with default values.
110
+ """
111
+
112
+ self.sample_directory = None
113
+ self.xlabel = ''
114
+ self.datasets = {}
115
+ self.temperatures = []
116
+ self.scissors = {}
117
+ self.linecuts = {}
118
+ self.linecutmodels = {}
119
+ self.a, self.b, self.c, self.al, self.be, self.ga, \
120
+ self.a_star, self.b_star, self.c_star, self.al_star, self.be_star, self.ga_star \
121
+ = [None] * 12
122
+
123
+ def set_temperatures(self, temperatures):
124
+ """
125
+ Set the list of temperatures for the datasets.
126
+
127
+ Parameters
128
+ ----------
129
+ temperatures : list
130
+ List of temperatures to set.
131
+ """
132
+ self.temperatures = temperatures
133
+
134
+ def find_temperatures(self):
135
+ """
136
+ Set the list of temperatures by automatically scanning the sample directory.
137
+ """
138
+
139
+ # Assert that self.sample_directory must exist
140
+ if self.sample_directory is None:
141
+ raise ValueError("Sample directory is not set. Use set_sample_directory(path) first.")
142
+
143
+ # Clear existing temperatures
144
+ self.temperatures = []
145
+
146
+ # Search for nxrefine .nxs files
147
+ for item in os.listdir(self.sample_directory):
148
+ pattern = r'_(\d+)\.nxs'
149
+ match = re.search(pattern, item)
150
+ if match:
151
+ # Identify temperature
152
+ temperature = match.group(1)
153
+ self.temperatures.append(temperature)
154
+ # Convert all temperatures to int temporarily to sort temperatures list
155
+ self.temperatures = [int(t) for t in self.temperatures]
156
+ self.temperatures.sort()
157
+ self.temperatures = [str(t) for t in self.temperatures]
158
+
159
+ def set_sample_directory(self, path):
160
+ """
161
+ Set the directory path where the datasets are located.
162
+
163
+ Parameters
164
+ ----------
165
+ path : str
166
+ Path to the sample directory.
167
+ """
168
+ self.sample_directory = os.path.normpath(path)
169
+
170
+ def initialize(self):
171
+ """
172
+ Initialize Scissors and LinecutModel objects for each temperature.
173
+ """
174
+ for temperature in self.temperatures:
175
+ self.scissors[temperature] = Scissors()
176
+ self.scissors[temperature] = LinecutModel()
177
+
178
+ def set_data(self, temperature, data):
179
+ """
180
+ Set the dataset for a specific temperature.
181
+
182
+ Parameters
183
+ ----------
184
+ temperature : str
185
+ Temperature for which to set the data.
186
+ data : object
187
+ The dataset to be set.
188
+ """
189
+ self.datasets[temperature] = data
190
+
191
+ def load_transforms(self, temperatures_list=None, print_tree=True):
192
+ """
193
+ Load transform datasets (from nxrefine) based on temperature.
194
+
195
+ Parameters
196
+ ----------
197
+ temperatures_list : list of int or None, optional
198
+ List of temperatures to load. If None, all available temperatures are loaded.
199
+ print_tree : bool, optional
200
+ Whether to print the data tree upon loading. Default True.
201
+ """
202
+ # Convert all temperatures to strings
203
+ if temperatures_list:
204
+ temperatures_list = [str(t) for t in temperatures_list]
205
+
206
+ # Clear existing temperatures before loading files
207
+ self.temperatures = []
208
+
209
+ # Identify files to load
210
+ items_to_load = []
211
+ # Search for nxrefine .nxs files
212
+ for item in os.listdir(self.sample_directory):
213
+ pattern = r'_(\d+)\.nxs'
214
+ match = re.search(pattern, item)
215
+ if match:
216
+ # Identify temperature
217
+ temperature = match.group(1)
218
+ # print(f'Temperature = {temperature}')
219
+ if (temperatures_list is None) or (temperature in temperatures_list):
220
+ # Prepare file to be loaded
221
+ self.temperatures.append(temperature)
222
+ items_to_load.append(item)
223
+ # print(f'Preparing to load {temperature} K data: {item}')
224
+
225
+ # Convert all temperatures to int temporarily to sort temperatures list before loading
226
+ self.temperatures = [int(t) for t in self.temperatures]
227
+
228
+ loading_template = pd.DataFrame({'temperature': self.temperatures,
229
+ 'filename': items_to_load})
230
+ loading_template = loading_template.sort_values(by='temperature')
231
+ self.temperatures = loading_template['temperature']
232
+ self.temperatures = [str(t) for t in self.temperatures]
233
+ items_to_load = loading_template['filename'].to_list()
234
+
235
+ for i, item in enumerate(items_to_load):
236
+ path = os.path.join(self.sample_directory, item)
237
+
238
+ # Ensure path is a string before using it
239
+ path = str(path)
240
+
241
+ # Save dataset
242
+ try:
243
+ self.datasets[self.temperatures[i]] = load_transform(path, print_tree)
244
+ except Exception as e:
245
+ # Report temperature that was unable to load, then raise exception.
246
+ temp_failed = self.temperatures[i]
247
+ print(f"Failed to load data for temperature {temp_failed} K from file {item}."
248
+ f" Error: {e}")
249
+ raise # Re-raise the exception
250
+
251
+ # Initialize scissors object
252
+ self.scissors[self.temperatures[i]] = Scissors()
253
+ self.scissors[self.temperatures[i]].set_data(self.datasets[self.temperatures[i]])
254
+
255
+ # Initialize linecutmodel object
256
+ self.linecutmodels[self.temperatures[i]] = LinecutModel()
257
+
258
+ def load_datasets(self, file_ending='hkli.nxs', temperatures_list=None, print_tree=True):
259
+ """
260
+ Load datasets (CHESS format) from the specified folder.
261
+
262
+ Parameters
263
+ ----------
264
+ file_ending : str, optional
265
+ The file extension of the datasets to be loaded. The default is 'hkli.nxs'.
266
+ temperatures_list : list of int or None, optional
267
+ The list of specific temperatures to load. If None, all available temperatures are
268
+ loaded. The default is None.
269
+ print_tree : bool, optional
270
+ Whether to print the data tree upon loading. Default True.
271
+ """
272
+ temperature_folders = [] # Empty list to store temperature folder names
273
+ for item in os.listdir(self.sample_directory):
274
+ try:
275
+ temperature_folders.append(int(item)) # If folder name can be int, add it
276
+ except ValueError:
277
+ pass # Otherwise don't add it
278
+ temperature_folders.sort() # Sort from low to high T
279
+ temperature_folders = [str(i) for i in temperature_folders] # Convert to strings
280
+
281
+ self.temperatures = temperature_folders
282
+
283
+ if temperatures_list is not None:
284
+ self.temperatures = [str(t) for t in temperatures_list]
285
+
286
+ # Load .nxs files
287
+ for T in self.temperatures:
288
+ for file in os.listdir(os.path.join(self.sample_directory, T)):
289
+ if file.endswith(file_ending):
290
+ filepath = os.path.join(self.sample_directory, T, file)
291
+
292
+ # Load dataset at each temperature
293
+ self.datasets[T] = load_data(filepath, print_tree)
294
+
295
+ # Initialize scissors object at each temperature
296
+ self.scissors[T] = Scissors()
297
+ self.scissors[T].set_data(self.datasets[T])
298
+
299
+ # Initialize linecutmodel object at each temperature
300
+ self.linecutmodels[T] = LinecutModel()
301
+
302
+ def get_sample_directory(self):
303
+ """
304
+ Get the folder path where the datasets are located.
305
+
306
+ Returns
307
+ -------
308
+ str
309
+ The folder path.
310
+ """
311
+ return self.sample_directory
312
+
313
+ def clear_datasets(self):
314
+ """
315
+ Clear the datasets stored in the TempDependence instance.
316
+ """
317
+ self.datasets = {}
318
+
319
+ def set_lattice_params(self, lattice_params):
320
+ """
321
+ Set lattice parameters and calculate reciprocal lattice parameters.
322
+
323
+ Parameters
324
+ ----------
325
+ lattice_params : tuple
326
+ Tuple containing lattice parameters (a, b, c, al, be, ga).
327
+ """
328
+ self.a, self.b, self.c, self.al, self.be, self.ga = lattice_params
329
+ self.a_star, self.b_star, self.c_star, \
330
+ self.al_star, self.be_star, self.ga_star = reciprocal_lattice_params(lattice_params)
331
+
332
+ def set_window(self, window, verbose=False):
333
+ """
334
+ Set the extents of the integration window for each temperature.
335
+
336
+ Parameters
337
+ ----------
338
+ window : tuple
339
+ Extents of the window for integration along each axis.
340
+ verbose : bool, optional
341
+ Enables printout of linecut axis and integrated axes. Default is False.
342
+ """
343
+ for T in self.temperatures:
344
+ if verbose:
345
+ print("----------------------------------")
346
+ print("T = " + T + " K")
347
+ self.scissors[T].set_window(window, verbose)
348
+
349
+ def set_center(self, center):
350
+ """
351
+ Set the central coordinate for the linecut for each temperature.
352
+
353
+ Parameters
354
+ ----------
355
+ center : tuple
356
+ Central coordinate around which to perform the linecut.
357
+ """
358
+ for T in self.temperatures:
359
+ self.scissors[T].set_center(center)
360
+
361
+ def cut_data(self, center=None, window=None, axis=None, verbose=False):
362
+ """
363
+ Perform data cutting for each temperature dataset.
364
+
365
+ Parameters
366
+ ----------
367
+ center : tuple, optional
368
+ The center point for cutting the data.
369
+ Defaults to the first temperature's center if None.
370
+ window : tuple, optional
371
+ The window size for cutting the data.
372
+ Defaults to the first temperature's window if None.
373
+ axis : int or None, optional
374
+ The axis along which to perform the cutting.
375
+ Defaults to the longest axis in `window` if None.
376
+ verbose : bool, optional
377
+ Enables printout of linecut progress. Default is False.
378
+
379
+ Returns
380
+ -------
381
+ dict
382
+ A dictionary of linecuts obtained from the cutting operation.
383
+ """
384
+
385
+ for T in self.temperatures:
386
+ if verbose:
387
+ print("-------------------------------")
388
+ print("Cutting T = " + T + " K data...")
389
+ self.scissors[T].set_center(center)
390
+ self.scissors[T].set_window(window)
391
+ self.scissors[T].cut_data(axis=axis, verbose=verbose)
392
+ self.linecuts[T] = self.scissors[T].linecut
393
+ self.linecutmodels[T].set_data(self.linecuts[T])
394
+
395
+ xlabel_components = [self.linecuts[self.temperatures[0]].axes
396
+ if i == self.scissors[self.temperatures[0]].axis
397
+ else str(c) for i, c in
398
+ enumerate(self.scissors[self.temperatures[0]].center)]
399
+ self.xlabel = ' '.join(xlabel_components)
400
+
401
+ return self.linecuts
402
+
403
+ def plot_linecuts(self, vertical_offset=0, **kwargs):
404
+ """
405
+ Plot the linecuts obtained from data cutting.
406
+
407
+ Parameters
408
+ ----------
409
+ vertical_offset : float, optional
410
+ The vertical offset between linecuts on the plot. The default is 0.
411
+ **kwargs
412
+ Additional keyword arguments to be passed to the plot function.
413
+ """
414
+ fig, ax = plt.subplots()
415
+
416
+ # Get the Viridis colormap
417
+ cmap = mpl.colormaps.get_cmap('viridis')
418
+
419
+ for i, linecut in enumerate(self.linecuts.values()):
420
+ x_data = linecut[linecut.axes].nxdata
421
+ y_data = linecut[linecut.signal].nxdata + i * vertical_offset
422
+ ax.plot(x_data, y_data, color=cmap(i / len(self.linecuts)), label=self.temperatures[i],
423
+ **kwargs)
424
+
425
+ ax.set(xlabel=self.xlabel,
426
+ ylabel=self.linecuts[self.temperatures[0]].signal)
427
+
428
+ # Get the current legend handles and labels
429
+ handles, labels = plt.gca().get_legend_handles_labels()
430
+
431
+ # Reverse the order of handles and labels
432
+ handles = handles[::-1]
433
+ labels = labels[::-1]
434
+
435
+ # Create a new legend with reversed order
436
+ plt.legend(handles, labels)
437
+
438
+ return fig, ax
439
+
440
+ def plot_linecuts_heatmap(self, ax=None, **kwargs):
441
+ """
442
+ Plot the linecuts obtained from data cutting.
443
+
444
+ Parameters
445
+ ----------
446
+ ax : matplotlib.axes.Axes, optional
447
+ The axes on which to plot the heatmap. If None, a new figure and axes
448
+ are created. The default is None.
449
+ **kwargs
450
+ Additional keyword arguments to be passed to the `pcolormesh` function.
451
+
452
+ Returns
453
+ -------
454
+ QuadMesh
455
+ The plotted heatmap object.
456
+ """
457
+
458
+ # Retrieve linecut data for the first temperature and extract x-axis data
459
+ cut = self.linecuts[self.temperatures[0]]
460
+ x = cut[cut.axes].nxdata
461
+
462
+ # Convert the list of temperatures to a NumPy array for the y-axis
463
+ y = np.array([int(t) for t in self.temperatures])
464
+
465
+ # Collect counts from each temperature and ensure they are numpy arrays
466
+ v = [self.linecuts[T].counts.nxdata for T in self.temperatures]
467
+
468
+ # Convert list of arrays to a 2D array for the heatmap
469
+ v_2d = np.array(v)
470
+
471
+ # Create the grid for the heatmap
472
+ X, Y = np.meshgrid(x, y)
473
+
474
+ # Plot using pcolormesh
475
+ if ax is None:
476
+ _, ax = plt.subplots()
477
+ p = ax.pcolormesh(X, Y, v_2d, **kwargs)
478
+ plt.colorbar(p, label='counts')
479
+ ax.set(xlabel=self.xlabel, ylabel=r'$T$ (K)')
480
+
481
+ return p
482
+
483
+ def highlight_integration_window(self, temperature=None, **kwargs):
484
+ """
485
+ Displays the integration window plot for a specific temperature,
486
+ or for the first temperature if none is provided.
487
+
488
+ Parameters
489
+ ----------
490
+ temperature : str, optional
491
+ The temperature at which to display the integration window plot. If provided, the plot
492
+ will be generated using the dataset corresponding to the specified temperature. If not
493
+ provided, the integration window plots will be generated for the first temperature.
494
+ **kwargs : keyword arguments, optional
495
+ Additional keyword arguments to customize the plot.
496
+ """
497
+
498
+ if temperature is not None:
499
+ p = self.scissors[
500
+ self.temperatures[0]].highlight_integration_window(
501
+ data=self.datasets[temperature], **kwargs
502
+ )
503
+ else:
504
+ p = self.scissors[self.temperatures[0]].highlight_integration_window(
505
+ data=self.datasets[self.temperatures[0]], **kwargs
506
+ )
507
+
508
+ return p
509
+
510
+ def plot_integration_window(self, temperature=None, **kwargs):
511
+ """
512
+ Plots the three principal cross-sections of the integration volume on
513
+ a single figure for a specific temperature, or for the first temperature
514
+ if none is provided.
515
+
516
+ Parameters
517
+ ----------
518
+ temperature : str, optional
519
+ The temperature at which to plot the integration volume. If provided,
520
+ the plot will be generated using the dataset corresponding to the
521
+ specified temperature. If not provided, the integration window plots
522
+ will be generated for the first temperature.
523
+
524
+ **kwargs : keyword arguments, optional
525
+ Additional keyword arguments to customize the plot.
526
+ """
527
+
528
+ if temperature is not None:
529
+ p = self.scissors[self.temperatures[0]].plot_integration_window(**kwargs)
530
+ else:
531
+ p = self.scissors[self.temperatures[0]].plot_integration_window(**kwargs)
532
+
533
+ return p
534
+
535
+ def set_model_components(self, model_components):
536
+ """
537
+ Set the model components for all line cut models.
538
+
539
+ This method sets the same model components for all line cut models in the
540
+ analysis. It iterates over each line cut model and calls their respective
541
+ `set_model_components` method with the provided `model_components`.
542
+
543
+ Parameters
544
+ ----------
545
+ model_components : Model or iterable of Model
546
+ The model components to set for all line cut models.
547
+
548
+ """
549
+ [linecutmodel.set_model_components(model_components) for
550
+ linecutmodel in self.linecutmodels.values()]
551
+
552
+ def set_param_hint(self, *args, **kwargs):
553
+ """
554
+ Set parameter hints for all line cut models.
555
+
556
+ This method sets the parameter hints for all line cut models in the analysis.
557
+ It iterates over each line cut model and calls their respective `set_param_hint` method
558
+ with the provided arguments and keyword arguments.
559
+
560
+ Parameters
561
+ ----------
562
+ *args
563
+ Variable length argument list.
564
+ **kwargs
565
+ Arbitrary keyword arguments.
566
+
567
+ """
568
+ [linecutmodel.set_param_hint(*args, **kwargs)
569
+ for linecutmodel in self.linecutmodels.values()]
570
+
571
+ def make_params(self):
572
+ """
573
+ Make parameters for all line cut models.
574
+
575
+ This method creates the parameters for all line cut models in the analysis.
576
+ It iterates over each line cut model and calls their respective `make_params` method.
577
+ """
578
+ [linecutmodel.make_params() for linecutmodel in self.linecutmodels.values()]
579
+
580
+ def guess(self):
581
+ """
582
+ Make initial parameter guesses for all line cut models.
583
+
584
+ This method generates initial parameter guesses for all line cut models in the analysis.
585
+ It iterates over each line cut model and calls their respective `guess` method.
586
+
587
+ """
588
+ [linecutmodel.guess() for linecutmodel in self.linecutmodels.values()]
589
+
590
+ def print_initial_params(self):
591
+ """
592
+ Print the initial parameter values for all line cut models.
593
+
594
+ This method prints the initial parameter values for all line cut models
595
+ in the analysis. It iterates over each line cut model and calls their
596
+ respective `print_initial_params` method.
597
+
598
+ """
599
+ [linecutmodel.print_initial_params() for linecutmodel in self.linecutmodels.values()]
600
+
601
+ def plot_initial_guess(self):
602
+ """
603
+ Plot the initial guess for all line cut models.
604
+
605
+ This method plots the initial guess for all line cut models in the analysis.
606
+ It iterates over each line cut model and calls their respective `plot_initial_guess` method.
607
+
608
+ """
609
+ for T, linecutmodel in self.linecutmodels.items():
610
+ _, ax = plt.subplots()
611
+ ax.set(title=T + ' K')
612
+ linecutmodel.plot_initial_guess()
613
+
614
+ def fit(self, verbose=False):
615
+ """
616
+ Fit the line cut models.
617
+
618
+ This method fits the line cut models for each temperature in the analysis.
619
+ It iterates over each line cut model, performs the fit, and prints the fitting progress.
620
+
621
+ Parameters
622
+ ----------
623
+ verbose : bool, optional
624
+ Enables printout of fitting progress. Default False.
625
+
626
+ """
627
+ for T, linecutmodel in self.linecutmodels.items():
628
+ if verbose:
629
+ print(f"Fitting {T} K data...")
630
+ linecutmodel.fit()
631
+ if verbose:
632
+ print("Done.")
633
+ print("Fits completed.")
634
+
635
+ def plot_fit(self, mdheadings=False, **kwargs):
636
+ """
637
+ Plot the fit results.
638
+
639
+ This method plots the fit results for each temperature in the analysis.
640
+ It iterates over each line cut model, calls their respective `plot_fit` method,
641
+ and sets the xlabel, ylabel, and title for the plot.
642
+
643
+ """
644
+ for T, linecutmodel in self.linecutmodels.items():
645
+ # Create a markdown heading for the plot
646
+ if mdheadings:
647
+ display(Markdown(f"### {T} K Fit Results"))
648
+ # Plot fit
649
+ linecutmodel.plot_fit(xlabel=self.xlabel,
650
+ ylabel=self.datasets[self.temperatures[0]].signal,
651
+ title=f"{T} K",
652
+ **kwargs)
653
+
654
+ def plot_order_parameter(self):
655
+ """
656
+ Plot the temperature dependence of the peak height (order parameter).
657
+
658
+ This method extracts the peak height from each temperature-dependent
659
+ line cut fit stored in `linecutmodels` and plots it as a function
660
+ of temperature using matplotlib.
661
+
662
+ Returns
663
+ -------
664
+ Figure
665
+ Matplotlib Figure object containing the peak height vs. temperature plot.
666
+ Axes
667
+ Matplotlib Axes object associated with the figure.
668
+
669
+ Notes
670
+ -----
671
+ - Temperature values are converted to integers for plotting.
672
+ - Peak heights are extracted from the 'peakheight' parameter in the model results.
673
+ - The plot uses standard axes labels with temperature in Kelvin.
674
+ """
675
+
676
+ # Create an array of temperature values
677
+ temperatures = [int(T) for T in self.temperatures]
678
+
679
+ # Create an empty list for the peak heights
680
+ peakheights = []
681
+
682
+ # Extract the peakheight at every temperature
683
+ for T in self.temperatures:
684
+ peakheights.append(self.linecutmodels[T].modelresult.params['peakheight'].value)
685
+
686
+ # Plot the peakheights vs. temperature
687
+ fig, ax = plt.subplots()
688
+ ax.plot(temperatures, peakheights)
689
+ ax.set(xlabel='$T$ (K)', ylabel='peakheight')
690
+ return fig, ax
691
+
692
+ def print_fit_report(self):
693
+ """
694
+ Plot the fit results.
695
+
696
+ This method plots the fit results for each temperature in the analysis.
697
+ It iterates over each line cut model, calls their respective `plot_fit` method,
698
+ and sets the xlabel, ylabel, and title for the plot.
699
+
700
+ """
701
+ for T, linecutmodel in self.linecutmodels.items():
702
+ print(f"[[[{T} K Fit Report]]]")
703
+ linecutmodel.print_fit_report()