nxs-analysis-tools 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nxs-analysis-tools might be problematic. Click here for more details.
- _meta/__init__.py +1 -1
- nxs_analysis_tools/__init__.py +2 -1
- nxs_analysis_tools/chess.py +140 -14
- nxs_analysis_tools/datareduction.py +179 -2
- nxs_analysis_tools/fitting.py +51 -9
- {nxs_analysis_tools-0.1.5.dist-info → nxs_analysis_tools-0.1.7.dist-info}/METADATA +1 -1
- nxs_analysis_tools-0.1.7.dist-info/RECORD +11 -0
- {nxs_analysis_tools-0.1.5.dist-info → nxs_analysis_tools-0.1.7.dist-info}/WHEEL +1 -1
- nxs_analysis_tools-0.1.5.dist-info/RECORD +0 -11
- {nxs_analysis_tools-0.1.5.dist-info → nxs_analysis_tools-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {nxs_analysis_tools-0.1.5.dist-info → nxs_analysis_tools-0.1.7.dist-info}/top_level.txt +0 -0
_meta/__init__.py
CHANGED
nxs_analysis_tools/__init__.py
CHANGED
|
@@ -11,4 +11,5 @@ from .chess import TempDependence
|
|
|
11
11
|
__all__ = ['load_data', 'load_transform', 'plot_slice', 'Scissors',
|
|
12
12
|
'reciprocal_lattice_params', 'rotate_data', 'rotate_data_2D',
|
|
13
13
|
'convert_to_inverse_angstroms', 'array_to_nxdata', 'Padder',
|
|
14
|
-
'rebin_nxdata', 'rebin_3d', 'rebin_1d'
|
|
14
|
+
'rebin_nxdata', 'rebin_3d', 'rebin_1d', 'TempDependence',
|
|
15
|
+
'animate_slice_temp', 'animate_slice_axis']
|
nxs_analysis_tools/chess.py
CHANGED
|
@@ -14,6 +14,7 @@ from IPython.display import display, Markdown
|
|
|
14
14
|
from nxs_analysis_tools import load_data, Scissors
|
|
15
15
|
from nxs_analysis_tools.fitting import LinecutModel
|
|
16
16
|
from nxs_analysis_tools.datareduction import load_transform, reciprocal_lattice_params
|
|
17
|
+
from lmfit.models import PseudoVoigtModel, LinearModel
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class TempDependence:
|
|
@@ -98,18 +99,32 @@ class TempDependence:
|
|
|
98
99
|
Fit the line cut models for each temperature.
|
|
99
100
|
plot_fit(mdheadings=False, **kwargs):
|
|
100
101
|
Plot the fit results for each temperature.
|
|
101
|
-
|
|
102
|
+
overlay_fits(numpoints=None, vertical_offset=0, cmap='viridis', ax=ax):
|
|
103
|
+
Plot raw data and fitted models for each temperature.
|
|
104
|
+
fit_peak_simple():
|
|
105
|
+
Perform a basic fit using a pseudo-Voigt peak shape, linear background, and no constraints.
|
|
106
|
+
plot_order_parameter(ax, **kwargs):
|
|
102
107
|
Plot the temperature dependence of the peakheight parameter.
|
|
103
108
|
print_fit_report():
|
|
104
109
|
Print the fit report for each temperature.
|
|
105
110
|
"""
|
|
106
111
|
|
|
107
|
-
def __init__(self):
|
|
112
|
+
def __init__(self, sample_directory=None):
|
|
108
113
|
"""
|
|
109
|
-
Initialize the TempDependence class
|
|
114
|
+
Initialize the TempDependence class.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
sample_directory : str, optional
|
|
119
|
+
Path to the directory containing the temperature folders.
|
|
120
|
+
If None, no directory is set initially.
|
|
110
121
|
"""
|
|
111
122
|
|
|
112
|
-
|
|
123
|
+
if sample_directory is None:
|
|
124
|
+
self.sample_directory = None
|
|
125
|
+
else:
|
|
126
|
+
self.set_sample_directory(sample_directory)
|
|
127
|
+
|
|
113
128
|
self.xlabel = ''
|
|
114
129
|
self.datasets = {}
|
|
115
130
|
self.temperatures = []
|
|
@@ -133,7 +148,7 @@ class TempDependence:
|
|
|
133
148
|
|
|
134
149
|
def find_temperatures(self):
|
|
135
150
|
"""
|
|
136
|
-
Set the list of temperatures by automatically scanning the sample directory.
|
|
151
|
+
Set the list of temperatures by automatically scanning the sample directory for .nxs files from nxrefine.
|
|
137
152
|
"""
|
|
138
153
|
|
|
139
154
|
# Assert that self.sample_directory must exist
|
|
@@ -449,7 +464,7 @@ class TempDependence:
|
|
|
449
464
|
|
|
450
465
|
Parameters
|
|
451
466
|
----------
|
|
452
|
-
ax : matplotlib.axes.Axes
|
|
467
|
+
ax : :class:`matplotlib.axes.Axes`, optional
|
|
453
468
|
The axes on which to plot the heatmap. If None, a new figure and axes
|
|
454
469
|
are created. The default is None.
|
|
455
470
|
**kwargs
|
|
@@ -469,7 +484,7 @@ class TempDependence:
|
|
|
469
484
|
y = np.array([int(t) for t in self.temperatures])
|
|
470
485
|
|
|
471
486
|
# Collect counts from each temperature and ensure they are numpy arrays
|
|
472
|
-
v = [self.linecuts[T].
|
|
487
|
+
v = [self.linecuts[T].nxsignal.nxdata for T in self.temperatures]
|
|
473
488
|
|
|
474
489
|
# Convert list of arrays to a 2D array for the heatmap
|
|
475
490
|
v_2d = np.array(v)
|
|
@@ -548,7 +563,7 @@ class TempDependence:
|
|
|
548
563
|
|
|
549
564
|
Parameters
|
|
550
565
|
----------
|
|
551
|
-
model_components : Model or iterable of Model
|
|
566
|
+
model_components : Model, CompositeModel, or iterable of Model
|
|
552
567
|
The model components to set for all line cut models.
|
|
553
568
|
|
|
554
569
|
"""
|
|
@@ -561,7 +576,8 @@ class TempDependence:
|
|
|
561
576
|
|
|
562
577
|
This method sets the parameter hints for all line cut models in the analysis.
|
|
563
578
|
It iterates over each line cut model and calls their respective `set_param_hint` method
|
|
564
|
-
with the provided arguments and keyword arguments.
|
|
579
|
+
with the provided arguments and keyword arguments. These are implemented when the
|
|
580
|
+
.make_params() method is called.
|
|
565
581
|
|
|
566
582
|
Parameters
|
|
567
583
|
----------
|
|
@@ -573,10 +589,40 @@ class TempDependence:
|
|
|
573
589
|
"""
|
|
574
590
|
[linecutmodel.set_param_hint(*args, **kwargs)
|
|
575
591
|
for linecutmodel in self.linecutmodels.values()]
|
|
592
|
+
|
|
593
|
+
def params_set(self, name, **kwargs):
|
|
594
|
+
"""
|
|
595
|
+
Set constraints on a parameter for all line cut models.
|
|
596
|
+
|
|
597
|
+
This method updates the specified parameter across all models in
|
|
598
|
+
`self.linecutmodels` using the keyword arguments provided. These
|
|
599
|
+
keyword arguments are passed to the `set()` method of the parameter,
|
|
600
|
+
which comes from a `lmfit.Parameters` object.
|
|
601
|
+
|
|
602
|
+
Parameters
|
|
603
|
+
----------
|
|
604
|
+
name : str
|
|
605
|
+
Name of the parameter to modify (must exist in each model).
|
|
606
|
+
**kwargs
|
|
607
|
+
Constraint arguments passed to `Parameter.set()`, such as `value`,
|
|
608
|
+
`min`, `max`, `vary`, etc.
|
|
609
|
+
|
|
610
|
+
Raises
|
|
611
|
+
------
|
|
612
|
+
KeyError
|
|
613
|
+
If the parameter `name` does not exist in one of the models.
|
|
614
|
+
|
|
615
|
+
Example
|
|
616
|
+
-------
|
|
617
|
+
>>> sample.params_set('peakamplitude', value=5, min=0, vary=True)
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
for linecutmodel in self.linecutmodels.values():
|
|
621
|
+
linecutmodel.params[name].set(**kwargs)
|
|
576
622
|
|
|
577
623
|
def make_params(self):
|
|
578
624
|
"""
|
|
579
|
-
|
|
625
|
+
Create and initialize the parameters for all models.
|
|
580
626
|
|
|
581
627
|
This method creates the parameters for all line cut models in the analysis.
|
|
582
628
|
It iterates over each line cut model and calls their respective `make_params` method.
|
|
@@ -585,7 +631,8 @@ class TempDependence:
|
|
|
585
631
|
|
|
586
632
|
def guess(self):
|
|
587
633
|
"""
|
|
588
|
-
Make initial parameter guesses for all line cut models.
|
|
634
|
+
Make initial parameter guesses for all line cut models. This overwrites any prior initial
|
|
635
|
+
values and constraints.
|
|
589
636
|
|
|
590
637
|
This method generates initial parameter guesses for all line cut models in the analysis.
|
|
591
638
|
It iterates over each line cut model and calls their respective `guess` method.
|
|
@@ -657,7 +704,70 @@ class TempDependence:
|
|
|
657
704
|
title=f"{T} K",
|
|
658
705
|
**kwargs)
|
|
659
706
|
|
|
660
|
-
def
|
|
707
|
+
def overlay_fits(self, numpoints=None, vertical_offset=0, cmap='viridis', ax=None):
|
|
708
|
+
"""
|
|
709
|
+
Plot raw data and fitted models for each temperature with optional vertical offsets.
|
|
710
|
+
|
|
711
|
+
Parameters:
|
|
712
|
+
-----------
|
|
713
|
+
numpoints : int or None, default=None
|
|
714
|
+
Number of points to evaluate for the fitted model curves.
|
|
715
|
+
If None, uses the number of raw data points for each linecut.
|
|
716
|
+
vertical_offset : float, default=0
|
|
717
|
+
Amount to vertically offset each linecut for clarity.
|
|
718
|
+
cmap : str, default='viridis'
|
|
719
|
+
Name of the matplotlib colormap used to distinguish different temperatures.
|
|
720
|
+
ax : :class:`matplotlib.axes.Axes` or None, default=None
|
|
721
|
+
Axis object to plot on. If None, a new figure and axis are created.
|
|
722
|
+
|
|
723
|
+
The function:
|
|
724
|
+
- Uses a colormap to assign unique colors to each temperature.
|
|
725
|
+
- Plots raw data alongside evaluated fit models for each linecut.
|
|
726
|
+
- Vertically offsets each trace by a constant value for visual separation.
|
|
727
|
+
- Displays a legend in reverse order to match top-to-bottom visual stacking.
|
|
728
|
+
- Automatically labels the x- and y-axes based on NeXus-style data metadata.
|
|
729
|
+
"""
|
|
730
|
+
|
|
731
|
+
# Create a figure and axes if an axis is not already provided
|
|
732
|
+
_, ax = plt.subplots() if ax is None else (None, ax)
|
|
733
|
+
|
|
734
|
+
# Generate a color palette for the various temperatures
|
|
735
|
+
cmap = plt.get_cmap(cmap)
|
|
736
|
+
colors = [cmap(i / len(self.temperatures)) for i, _ in enumerate(self.temperatures)]
|
|
737
|
+
|
|
738
|
+
for i, lm in enumerate(self.linecutmodels.values()):
|
|
739
|
+
# Plot the raw data
|
|
740
|
+
ax.plot(lm.x, lm.y + vertical_offset * i, '.', c=colors[i])
|
|
741
|
+
|
|
742
|
+
# Evaluate the fit
|
|
743
|
+
numpoints = len(lm.x) if numpoints is None else numpoints
|
|
744
|
+
x_eval = np.linspace(lm.x.min(), lm.x.max(), numpoints)
|
|
745
|
+
y_eval = lm.modelresult.eval(x=x_eval)
|
|
746
|
+
ax.plot(x_eval, y_eval + vertical_offset * i, '-', c=colors[i], label=self.temperatures[i])
|
|
747
|
+
|
|
748
|
+
# Reverse legend entries to match top-to-bottom stacking
|
|
749
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
750
|
+
ax.legend(handles[::-1], labels[::-1])
|
|
751
|
+
|
|
752
|
+
# Add axis labels
|
|
753
|
+
ax.set(xlabel=lm.data.nxaxes[0].nxname, ylabel=lm.data.nxsignal.nxname)
|
|
754
|
+
|
|
755
|
+
def fit_peak_simple(self):
|
|
756
|
+
"""
|
|
757
|
+
Fit all linecuts in the temperature series using a pseudo-Voigt peak shape and linear
|
|
758
|
+
background, with no constraints.
|
|
759
|
+
"""
|
|
760
|
+
|
|
761
|
+
for T in self.temperatures:
|
|
762
|
+
linecutmodel = self.linecutmodels[T]
|
|
763
|
+
linecutmodel.set_model_components([PseudoVoigtModel(prefix='peak'),
|
|
764
|
+
LinearModel(prefix='background')])
|
|
765
|
+
linecutmodel.make_params()
|
|
766
|
+
linecutmodel.guess()
|
|
767
|
+
linecutmodel.params.set('peakamplitude', min=0)
|
|
768
|
+
linecutmodel.fit()
|
|
769
|
+
|
|
770
|
+
def plot_order_parameter(self, ax=None, **kwargs):
|
|
661
771
|
"""
|
|
662
772
|
Plot the temperature dependence of the peak height (order parameter).
|
|
663
773
|
|
|
@@ -665,6 +775,14 @@ class TempDependence:
|
|
|
665
775
|
line cut fit stored in `linecutmodels` and plots it as a function
|
|
666
776
|
of temperature using matplotlib.
|
|
667
777
|
|
|
778
|
+
Parameters
|
|
779
|
+
----------
|
|
780
|
+
ax : :class:`matplotlib.axes.Axes`, optional
|
|
781
|
+
Axis object to plot on. If None, a new figure and axis are created.
|
|
782
|
+
**kwargs
|
|
783
|
+
Keyword arguments to be passed to the plot function.
|
|
784
|
+
|
|
785
|
+
|
|
668
786
|
Returns
|
|
669
787
|
-------
|
|
670
788
|
Figure
|
|
@@ -687,11 +805,19 @@ class TempDependence:
|
|
|
687
805
|
|
|
688
806
|
# Extract the peakheight at every temperature
|
|
689
807
|
for T in self.temperatures:
|
|
808
|
+
|
|
809
|
+
# Verify that the fit has already been completed
|
|
810
|
+
if self.linecutmodels[T].modelresult is None:
|
|
811
|
+
raise AttributeError("Model result is empty. Have you fit the data to a model?")
|
|
812
|
+
|
|
690
813
|
peakheights.append(self.linecutmodels[T].modelresult.params['peakheight'].value)
|
|
691
814
|
|
|
692
815
|
# Plot the peakheights vs. temperature
|
|
693
|
-
|
|
694
|
-
|
|
816
|
+
if ax is None:
|
|
817
|
+
fig, ax = plt.subplots()
|
|
818
|
+
else:
|
|
819
|
+
fig = ax.figure
|
|
820
|
+
ax.plot(temperatures, peakheights, **kwargs)
|
|
695
821
|
ax.set(xlabel='$T$ (K)', ylabel='peakheight')
|
|
696
822
|
return fig, ax
|
|
697
823
|
|
|
@@ -2,22 +2,27 @@
|
|
|
2
2
|
Reduces scattering data into 2D and 1D datasets.
|
|
3
3
|
"""
|
|
4
4
|
import os
|
|
5
|
+
import io
|
|
6
|
+
import warnings
|
|
5
7
|
import numpy as np
|
|
6
8
|
import matplotlib.pyplot as plt
|
|
7
9
|
from matplotlib.transforms import Affine2D
|
|
8
10
|
from matplotlib.markers import MarkerStyle
|
|
9
11
|
from matplotlib.ticker import MultipleLocator
|
|
12
|
+
import matplotlib.animation as animation
|
|
10
13
|
from matplotlib import colors
|
|
11
14
|
from matplotlib import patches
|
|
12
|
-
from IPython.display import display, Markdown
|
|
15
|
+
from IPython.display import display, Markdown, HTML, Image
|
|
13
16
|
from nexusformat.nexus import NXfield, NXdata, nxload, NeXusError, NXroot, NXentry, nxsave
|
|
14
17
|
from scipy import ndimage
|
|
15
18
|
|
|
19
|
+
|
|
16
20
|
# Specify items on which users are allowed to perform standalone imports
|
|
17
21
|
__all__ = ['load_data', 'load_transform', 'plot_slice', 'Scissors',
|
|
18
22
|
'reciprocal_lattice_params', 'rotate_data', 'rotate_data_2D',
|
|
19
23
|
'convert_to_inverse_angstroms', 'array_to_nxdata', 'Padder',
|
|
20
|
-
'rebin_nxdata', 'rebin_3d', 'rebin_1d'
|
|
24
|
+
'rebin_nxdata', 'rebin_3d', 'rebin_1d', 'animate_slice_temp',
|
|
25
|
+
'animate_slice_axis']
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
def load_data(path, print_tree=True):
|
|
@@ -407,6 +412,9 @@ def plot_slice(data, X=None, Y=None, sum_axis=None, transpose=False, vmin=None,
|
|
|
407
412
|
# Use the 2D template to create a new nxdata
|
|
408
413
|
data = array_to_nxdata(arr, data[slice_obj])
|
|
409
414
|
|
|
415
|
+
if data.ndim != 2:
|
|
416
|
+
raise ValueError("Slice data must be 2D.")
|
|
417
|
+
|
|
410
418
|
# If the data is of type ndarray, then convert to NXdata
|
|
411
419
|
if is_array:
|
|
412
420
|
# Convert X to NXfield if it is not already
|
|
@@ -600,6 +608,175 @@ def plot_slice(data, X=None, Y=None, sum_axis=None, transpose=False, vmin=None,
|
|
|
600
608
|
# Return the quadmesh object
|
|
601
609
|
return p
|
|
602
610
|
|
|
611
|
+
def animate_slice_temp(temp_dependence, slice_obj, ax=None, interval=500, save_gif=False, filename='animation',
|
|
612
|
+
title=True, title_fmt='d', plot_slice_kwargs=None, ax_kwargs=None):
|
|
613
|
+
"""
|
|
614
|
+
Animate 2D slices from a temperature-dependent dataset.
|
|
615
|
+
|
|
616
|
+
Creates a matplotlib animation by extracting 2D slices from each dataset
|
|
617
|
+
in a TempDependence object and animating them in sequence by temperature.
|
|
618
|
+
Optionally displays the animation inline and/or saves it as a GIF.
|
|
619
|
+
|
|
620
|
+
Parameters
|
|
621
|
+
----------
|
|
622
|
+
temp_dependence : nxs_analysis_tools.chess.TempDependence
|
|
623
|
+
Object holding datasets at various temperatures.
|
|
624
|
+
slice_obj : list of slice or None
|
|
625
|
+
Slice object to apply to each dataset; None entries are treated as ':'.
|
|
626
|
+
ax : matplotlib.axes.Axes, optional
|
|
627
|
+
The axes object to plot on. If None, a new figure and axes will be created.
|
|
628
|
+
interval : int, optional
|
|
629
|
+
Delay between frames in milliseconds. Default is 500.
|
|
630
|
+
save_gif : bool, optional
|
|
631
|
+
If True, saves the animation to a .gif file. Default is False.
|
|
632
|
+
filename : str, optional
|
|
633
|
+
Filename (without extension) for saved .gif. Default is 'animation'.
|
|
634
|
+
title : bool, optional
|
|
635
|
+
If True, displays the temperature in the title of each frame. Default is True.
|
|
636
|
+
title_fmt : str, optional
|
|
637
|
+
Format string for temperature values (e.g., '.2f' for 2 decimals). Default is 'd' (integer).
|
|
638
|
+
plot_slice_kwargs : dict, optional
|
|
639
|
+
Additional keyword arguments passed to `plot_slice`.
|
|
640
|
+
ax_kwargs : dict, optional
|
|
641
|
+
Keyword arguments passed to `ax.set`.
|
|
642
|
+
|
|
643
|
+
Returns
|
|
644
|
+
-------
|
|
645
|
+
ani : matplotlib.animation.FuncAnimation
|
|
646
|
+
The resulting animation object.
|
|
647
|
+
"""
|
|
648
|
+
if ax is None:
|
|
649
|
+
fig,ax = plt.subplots() # Generate a new figure and axis
|
|
650
|
+
else:
|
|
651
|
+
fig = ax.figure # Get the figure from the provided axis
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
if plot_slice_kwargs is None:
|
|
655
|
+
plot_slice_kwargs = {}
|
|
656
|
+
if ax_kwargs is None:
|
|
657
|
+
ax_kwargs = {}
|
|
658
|
+
|
|
659
|
+
# Normalize the slice object
|
|
660
|
+
normalized_slice = [slice(None) if s is None else s for s in slice_obj]
|
|
661
|
+
|
|
662
|
+
# Warn if colorbar is requested
|
|
663
|
+
if plot_slice_kwargs.get('cbar', False):
|
|
664
|
+
warnings.warn("Colorbar is not supported in animation and will be ignored.", UserWarning)
|
|
665
|
+
plot_slice_kwargs['cbar'] = False
|
|
666
|
+
elif 'cbar' not in plot_slice_kwargs.keys():
|
|
667
|
+
plot_slice_kwargs['cbar'] = False
|
|
668
|
+
|
|
669
|
+
def update(temp):
|
|
670
|
+
ax.clear()
|
|
671
|
+
dataset = temp_dependence.datasets[temp]
|
|
672
|
+
plot_slice(dataset[tuple(normalized_slice)], ax=ax, **plot_slice_kwargs)
|
|
673
|
+
ax.set(**ax_kwargs)
|
|
674
|
+
|
|
675
|
+
if title:
|
|
676
|
+
try:
|
|
677
|
+
formatted_temp = f"{int(temp):{title_fmt}}"
|
|
678
|
+
except ValueError:
|
|
679
|
+
raise ValueError(f"Invalid title_fmt '{title_fmt}' for temperature value '{temp}'")
|
|
680
|
+
ax.set(title=f'$T$={formatted_temp}')
|
|
681
|
+
|
|
682
|
+
ani = animation.FuncAnimation(fig, update,
|
|
683
|
+
frames=temp_dependence.temperatures,
|
|
684
|
+
interval=interval, repeat=False)
|
|
685
|
+
|
|
686
|
+
display(HTML(ani.to_jshtml()))
|
|
687
|
+
|
|
688
|
+
if save_gif:
|
|
689
|
+
gif_file = f'{filename}.gif'
|
|
690
|
+
writer = animation.PillowWriter(fps=1000 / interval)
|
|
691
|
+
ani.save(gif_file, writer=writer)
|
|
692
|
+
with open(gif_file, 'rb') as f:
|
|
693
|
+
display(Image(f.read(), format='gif'))
|
|
694
|
+
|
|
695
|
+
return ani
|
|
696
|
+
|
|
697
|
+
def animate_slice_axis(data, axis, axis_values, ax=None, interval=500, save_gif=False, filename='animation', title=True, title_fmt='.2f', plot_slice_kwargs={}, ax_kwargs={}):
|
|
698
|
+
"""
|
|
699
|
+
Animate 2D slices of a 3D dataset along a given axis.
|
|
700
|
+
|
|
701
|
+
Creates a matplotlib animation by sweeping through 2D slices of a 3D
|
|
702
|
+
dataset along the specified axis. Optionally displays the animation
|
|
703
|
+
inline (e.g., in Jupyter) and/or saves it as a GIF.
|
|
704
|
+
|
|
705
|
+
Parameters
|
|
706
|
+
----------
|
|
707
|
+
data : nexusformat.nexus.NXdata
|
|
708
|
+
The 3D dataset to visualize.
|
|
709
|
+
axis : int
|
|
710
|
+
The axis along which to animate (must be 0, 1, or 2).
|
|
711
|
+
axis_values : iterable
|
|
712
|
+
The values along the animation axis to use as animation frames.
|
|
713
|
+
ax : matplotlib.axes.Axes, optional
|
|
714
|
+
The axes object to plot on. If None, a new figure and axes will be created.
|
|
715
|
+
interval : int, optional
|
|
716
|
+
Delay between frames in milliseconds. Default is 500.
|
|
717
|
+
save_gif : bool, optional
|
|
718
|
+
If True, saves the animation as a .gif file. Default is False.
|
|
719
|
+
filename : str, optional
|
|
720
|
+
Filename (without extension) to use for the saved .gif. Default is 'animation'.
|
|
721
|
+
title : bool, optional
|
|
722
|
+
If True, displays the axis value as a title for each frame. Default is True.
|
|
723
|
+
title_fmt : str, optional
|
|
724
|
+
Format string for axis value in the title (e.g., '.2f' for 2 decimals). Default is '.2f'.
|
|
725
|
+
plot_slice_kwargs : dict, optional
|
|
726
|
+
Additional keyword arguments passed to `plot_slice`.
|
|
727
|
+
ax_kwargs : dict, optional
|
|
728
|
+
Keyword arguments passed to `ax.set` to update axis settings.
|
|
729
|
+
|
|
730
|
+
Returns
|
|
731
|
+
-------
|
|
732
|
+
ani : matplotlib.animation.FuncAnimation
|
|
733
|
+
The animation object.
|
|
734
|
+
"""
|
|
735
|
+
if ax is None:
|
|
736
|
+
fig,ax = plt.subplots() # Generate a new figure and axis
|
|
737
|
+
else:
|
|
738
|
+
fig = ax.figure # Get the figure from the provided axis
|
|
739
|
+
|
|
740
|
+
if axis not in [0, 1, 2]:
|
|
741
|
+
raise ValueError("axis must be either 0, 1, or 2.")
|
|
742
|
+
|
|
743
|
+
if plot_slice_kwargs.get('cbar', False):
|
|
744
|
+
warnings.warn("Colorbar is not supported in animation and will be ignored.", UserWarning)
|
|
745
|
+
plot_slice_kwargs['cbar'] = False
|
|
746
|
+
elif 'cbar' not in plot_slice_kwargs.keys():
|
|
747
|
+
plot_slice_kwargs['cbar'] = False
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def update(parameter):
|
|
752
|
+
ax.clear()
|
|
753
|
+
|
|
754
|
+
# Construct slicing object for the selected axis
|
|
755
|
+
slice_obj = [slice(None)] * 3
|
|
756
|
+
slice_obj[axis] = parameter
|
|
757
|
+
|
|
758
|
+
# Plot the 2D slice
|
|
759
|
+
plot_slice(data[tuple(slice_obj)], ax=ax, **plot_slice_kwargs)
|
|
760
|
+
ax.set(**ax_kwargs)
|
|
761
|
+
|
|
762
|
+
if title:
|
|
763
|
+
axis_label = data.axes[axis]
|
|
764
|
+
ax.set(title=f'${axis_label}$={parameter:{title_fmt}}')
|
|
765
|
+
|
|
766
|
+
ani = animation.FuncAnimation(fig, update, frames=axis_values, interval=interval, repeat=False)
|
|
767
|
+
|
|
768
|
+
display(HTML(ani.to_jshtml()))
|
|
769
|
+
|
|
770
|
+
if save_gif:
|
|
771
|
+
gif_file = f'{filename}.gif'
|
|
772
|
+
writergif = animation.PillowWriter(fps=1000/interval)
|
|
773
|
+
ani.save(gif_file, writer=writergif)
|
|
774
|
+
display(HTML(ani.to_jshtml()))
|
|
775
|
+
with open(gif_file, 'rb') as file:
|
|
776
|
+
display(Image(file.read(), format='gif'))
|
|
777
|
+
|
|
778
|
+
return ani
|
|
779
|
+
|
|
603
780
|
|
|
604
781
|
class Scissors:
|
|
605
782
|
"""
|
nxs_analysis_tools/fitting.py
CHANGED
|
@@ -3,8 +3,9 @@ Module for fitting of linecuts using the lmfit package.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import operator
|
|
6
|
-
from lmfit
|
|
7
|
-
from lmfit.model import CompositeModel
|
|
6
|
+
from lmfit import Parameters
|
|
7
|
+
from lmfit.model import Model, CompositeModel
|
|
8
|
+
from lmfit.models import PseudoVoigtModel, LinearModel
|
|
8
9
|
import matplotlib.pyplot as plt
|
|
9
10
|
import numpy as np
|
|
10
11
|
|
|
@@ -66,6 +67,8 @@ class LinecutModel:
|
|
|
66
67
|
Fit the model to the data.
|
|
67
68
|
plot_fit(self, numpoints=None, fit_report=True, **kwargs)
|
|
68
69
|
Plot the fitted model.
|
|
70
|
+
fit_peak_simple():
|
|
71
|
+
Perform a basic fit using a pseudo-Voigt peak shape, linear background, and no constraints.
|
|
69
72
|
print_fit_report(self)
|
|
70
73
|
Print the fit report.
|
|
71
74
|
"""
|
|
@@ -110,15 +113,25 @@ class LinecutModel:
|
|
|
110
113
|
|
|
111
114
|
Parameters
|
|
112
115
|
----------
|
|
113
|
-
model_components : Model or
|
|
114
|
-
The model component(s) to be used for fitting
|
|
115
|
-
which will be combined into a CompositeModel.
|
|
116
|
+
model_components : Model, CompositeModel, or iterable of Model
|
|
117
|
+
The model component(s) to be used for fitting.
|
|
116
118
|
"""
|
|
117
119
|
|
|
118
120
|
# If the model only has one component, then use it as the model
|
|
119
121
|
if isinstance(model_components, Model):
|
|
120
122
|
self.model = model_components
|
|
121
|
-
|
|
123
|
+
self.params = self.model.make_params()
|
|
124
|
+
|
|
125
|
+
# If the model is a composite model, then use it as the model
|
|
126
|
+
elif isinstance(model_components, CompositeModel):
|
|
127
|
+
self.model = model_components
|
|
128
|
+
self.model_components = self.model.components
|
|
129
|
+
# Make params for each component of the model
|
|
130
|
+
self.params = Parameters()
|
|
131
|
+
for component in self.model.components:
|
|
132
|
+
self.params.update(component.make_params())
|
|
133
|
+
|
|
134
|
+
# Else, combine the components into a composite model and use that as the model
|
|
122
135
|
else:
|
|
123
136
|
self.model_components = model_components
|
|
124
137
|
self.model = model_components[0]
|
|
@@ -127,9 +140,15 @@ class LinecutModel:
|
|
|
127
140
|
for component in model_components[1:]:
|
|
128
141
|
self.model = CompositeModel(self.model, component, operator.add)
|
|
129
142
|
|
|
143
|
+
# Make params for each component of the model
|
|
144
|
+
self.params = Parameters()
|
|
145
|
+
for component in self.model.components:
|
|
146
|
+
self.params.update(component.make_params())
|
|
147
|
+
|
|
130
148
|
def set_param_hint(self, *args, **kwargs):
|
|
131
149
|
"""
|
|
132
|
-
Set parameter hints for the model.
|
|
150
|
+
Set parameter hints for the model. These are implemented when the .make_params() method
|
|
151
|
+
is called.
|
|
133
152
|
|
|
134
153
|
Parameters
|
|
135
154
|
----------
|
|
@@ -159,10 +178,22 @@ class LinecutModel:
|
|
|
159
178
|
|
|
160
179
|
def guess(self):
|
|
161
180
|
"""
|
|
162
|
-
Perform initial guesses for each model component.
|
|
181
|
+
Perform initial guesses for each model component and update params. This overwrites any
|
|
182
|
+
prior initial values and constraints.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
components_params : list
|
|
187
|
+
A list containing params objects for each component of the model.
|
|
163
188
|
"""
|
|
164
|
-
|
|
189
|
+
|
|
190
|
+
components_params = []
|
|
191
|
+
|
|
192
|
+
for model_component in self.model.components:
|
|
165
193
|
self.params.update(model_component.guess(self.y, x=self.x))
|
|
194
|
+
components_params.append(model_component.guess(self.y, x=self.x))
|
|
195
|
+
|
|
196
|
+
return components_params
|
|
166
197
|
|
|
167
198
|
def print_initial_params(self):
|
|
168
199
|
"""
|
|
@@ -251,6 +282,17 @@ class LinecutModel:
|
|
|
251
282
|
if fit_report:
|
|
252
283
|
print(self.modelresult.fit_report())
|
|
253
284
|
return ax
|
|
285
|
+
|
|
286
|
+
def fit_peak_simple(self):
|
|
287
|
+
"""
|
|
288
|
+
Fit all linecuts in the temperature series using a pseudo-Voigt peak shape and linear
|
|
289
|
+
background, with no constraints.
|
|
290
|
+
"""
|
|
291
|
+
self.set_model_components([PseudoVoigtModel(prefix='peak'),
|
|
292
|
+
LinearModel(prefix='background')])
|
|
293
|
+
self.make_params()
|
|
294
|
+
self.guess()
|
|
295
|
+
self.fit()
|
|
254
296
|
|
|
255
297
|
def print_fit_report(self):
|
|
256
298
|
"""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
_meta/__init__.py,sha256=zpGc6jhRvO6wrmQbAkztl9cSzg7jjatDDQ2_F-Aa0os,346
|
|
2
|
+
nxs_analysis_tools/__init__.py,sha256=lutfLk7oBaMpKq2G2hf6V59SNqAhzSUyKLXGwTI_iDg,622
|
|
3
|
+
nxs_analysis_tools/chess.py,sha256=e1x9u4o38iaVfpFLoEUuqI9iXhHgcCcfuHEOMfOE5PU,32699
|
|
4
|
+
nxs_analysis_tools/datareduction.py,sha256=-Db-Fz6F-xEl3nZsooUrPdDPBLEoQGL5tcW98sv18KE,58840
|
|
5
|
+
nxs_analysis_tools/fitting.py,sha256=kRMhjObetGqmZ5-Jk1OHKGrXW4qI4D37s8VeC2ygJV8,10275
|
|
6
|
+
nxs_analysis_tools/pairdistribution.py,sha256=BDJdPiQ-XEk8vZKiFQnCotaWeS5cDDGqmSyhzC3fwrQ,65586
|
|
7
|
+
nxs_analysis_tools-0.1.7.dist-info/licenses/LICENSE,sha256=bE6FnYixueAGAnEfUuumbkSeMgdBguAAkheVgjv47Jo,1086
|
|
8
|
+
nxs_analysis_tools-0.1.7.dist-info/METADATA,sha256=m4u4yFWCWveKrj6-7j0JGRuxRmIH3XTI2ESOb9BieSk,3180
|
|
9
|
+
nxs_analysis_tools-0.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
nxs_analysis_tools-0.1.7.dist-info/top_level.txt,sha256=8U000GNPzo6T6pOMjRdgOSO5heMzLMGjkxa1CDtyMHM,25
|
|
11
|
+
nxs_analysis_tools-0.1.7.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
_meta/__init__.py,sha256=BH0JhANvvayOZjZkcKH-49j-EhsE1kZMiVlasaFPwYU,346
|
|
2
|
-
nxs_analysis_tools/__init__.py,sha256=3UFf4nxseTCfsPDSluxupmpd0Es55F9_du5T8-z4CsE,570
|
|
3
|
-
nxs_analysis_tools/chess.py,sha256=VAnEEAVIILQgavXTSUN7S-ndghjzolY6HFDr-d7YQbM,27299
|
|
4
|
-
nxs_analysis_tools/datareduction.py,sha256=yb0L6Y48z72JUpYg5GOjmJxvDfbOYus0WKhjZNMY4rA,52110
|
|
5
|
-
nxs_analysis_tools/fitting.py,sha256=xkx66AJiJJO_kC1OBa3bcSDwVV20MEowRa59K-yFlqg,8551
|
|
6
|
-
nxs_analysis_tools/pairdistribution.py,sha256=BDJdPiQ-XEk8vZKiFQnCotaWeS5cDDGqmSyhzC3fwrQ,65586
|
|
7
|
-
nxs_analysis_tools-0.1.5.dist-info/licenses/LICENSE,sha256=bE6FnYixueAGAnEfUuumbkSeMgdBguAAkheVgjv47Jo,1086
|
|
8
|
-
nxs_analysis_tools-0.1.5.dist-info/METADATA,sha256=x3dPkmlrVTaEb8TBzkny5CARFuBntUu04PduZfkCpyE,3180
|
|
9
|
-
nxs_analysis_tools-0.1.5.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
10
|
-
nxs_analysis_tools-0.1.5.dist-info/top_level.txt,sha256=8U000GNPzo6T6pOMjRdgOSO5heMzLMGjkxa1CDtyMHM,25
|
|
11
|
-
nxs_analysis_tools-0.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|