nxs-analysis-tools 0.1.6__py3-none-any.whl → 0.1.8__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-2025, {__author__}"
8
8
  __license__ = 'MIT'
9
- __version__ = '0.1.6'
9
+ __version__ = '0.1.8'
10
10
  __repo_url__ = 'https://github.com/stevenjgomez/nxs_analysis_tools'
@@ -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'] + ['TempDependence']
14
+ 'rebin_nxdata', 'rebin_3d', 'rebin_1d', 'TempDependence',
15
+ 'animate_slice_temp', 'animate_slice_axis']
@@ -103,18 +103,28 @@ class TempDependence:
103
103
  Plot raw data and fitted models for each temperature.
104
104
  fit_peak_simple():
105
105
  Perform a basic fit using a pseudo-Voigt peak shape, linear background, and no constraints.
106
- plot_order_parameter():
106
+ plot_order_parameter(ax, **kwargs):
107
107
  Plot the temperature dependence of the peakheight parameter.
108
108
  print_fit_report():
109
109
  Print the fit report for each temperature.
110
110
  """
111
111
 
112
- def __init__(self):
112
+ def __init__(self, sample_directory=None):
113
113
  """
114
- Initialize the TempDependence class with default values.
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.
115
121
  """
116
122
 
117
- self.sample_directory = None
123
+ if sample_directory is None:
124
+ self.sample_directory = None
125
+ else:
126
+ self.set_sample_directory(sample_directory)
127
+
118
128
  self.xlabel = ''
119
129
  self.datasets = {}
120
130
  self.temperatures = []
@@ -138,7 +148,7 @@ class TempDependence:
138
148
 
139
149
  def find_temperatures(self):
140
150
  """
141
- 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.
142
152
  """
143
153
 
144
154
  # Assert that self.sample_directory must exist
@@ -454,7 +464,7 @@ class TempDependence:
454
464
 
455
465
  Parameters
456
466
  ----------
457
- ax : matplotlib.axes.Axes, optional
467
+ ax : :class:`matplotlib.axes.Axes`, optional
458
468
  The axes on which to plot the heatmap. If None, a new figure and axes
459
469
  are created. The default is None.
460
470
  **kwargs
@@ -707,7 +717,7 @@ class TempDependence:
707
717
  Amount to vertically offset each linecut for clarity.
708
718
  cmap : str, default='viridis'
709
719
  Name of the matplotlib colormap used to distinguish different temperatures.
710
- ax : matplotlib.axes.Axes or None, default=None
720
+ ax : :class:`matplotlib.axes.Axes` or None, default=None
711
721
  Axis object to plot on. If None, a new figure and axis are created.
712
722
 
713
723
  The function:
@@ -754,9 +764,10 @@ class TempDependence:
754
764
  LinearModel(prefix='background')])
755
765
  linecutmodel.make_params()
756
766
  linecutmodel.guess()
767
+ linecutmodel.params['peakamplitude'].set(min=0)
757
768
  linecutmodel.fit()
758
769
 
759
- def plot_order_parameter(self):
770
+ def plot_order_parameter(self, ax=None, **kwargs):
760
771
  """
761
772
  Plot the temperature dependence of the peak height (order parameter).
762
773
 
@@ -764,6 +775,14 @@ class TempDependence:
764
775
  line cut fit stored in `linecutmodels` and plots it as a function
765
776
  of temperature using matplotlib.
766
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
+
767
786
  Returns
768
787
  -------
769
788
  Figure
@@ -786,11 +805,19 @@ class TempDependence:
786
805
 
787
806
  # Extract the peakheight at every temperature
788
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
+
789
813
  peakheights.append(self.linecutmodels[T].modelresult.params['peakheight'].value)
790
814
 
791
815
  # Plot the peakheights vs. temperature
792
- fig, ax = plt.subplots()
793
- ax.plot(temperatures, peakheights)
816
+ if ax is None:
817
+ fig, ax = plt.subplots()
818
+ else:
819
+ fig = ax.figure
820
+ ax.plot(temperatures, peakheights, **kwargs)
794
821
  ax.set(xlabel='$T$ (K)', ylabel='peakheight')
795
822
  return fig, ax
796
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
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nxs-analysis-tools
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: Reduce and transform nexus format (.nxs) scattering data.
5
5
  Author-email: "Steven J. Gomez Alvarado" <stevenjgomez@ucsb.edu>
6
6
  License-Expression: MIT
@@ -0,0 +1,11 @@
1
+ _meta/__init__.py,sha256=xKvd1LMYceNF4Vd0KeOyKWVnK1CBhGM6F7TmQ0rHPlg,346
2
+ nxs_analysis_tools/__init__.py,sha256=lutfLk7oBaMpKq2G2hf6V59SNqAhzSUyKLXGwTI_iDg,622
3
+ nxs_analysis_tools/chess.py,sha256=mTl3-hfKG6xUtuzqWJa63t1qg975Iv8ND6C6B1dxPio,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.8.dist-info/licenses/LICENSE,sha256=bE6FnYixueAGAnEfUuumbkSeMgdBguAAkheVgjv47Jo,1086
8
+ nxs_analysis_tools-0.1.8.dist-info/METADATA,sha256=9O06hZJSXUurPLACZtDQ422KmEqtFo0I45KV3Pb4Z94,3180
9
+ nxs_analysis_tools-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ nxs_analysis_tools-0.1.8.dist-info/top_level.txt,sha256=8U000GNPzo6T6pOMjRdgOSO5heMzLMGjkxa1CDtyMHM,25
11
+ nxs_analysis_tools-0.1.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,11 +0,0 @@
1
- _meta/__init__.py,sha256=KHPI9g5HZm4SkX7sGgxiwCtGNi1EE_Wxga18Nnq74T4,346
2
- nxs_analysis_tools/__init__.py,sha256=3UFf4nxseTCfsPDSluxupmpd0Es55F9_du5T8-z4CsE,570
3
- nxs_analysis_tools/chess.py,sha256=Wq5mbLOOdVJPz7e9v9Ao4G2nvpvfZRd_so4ePnrGDOQ,31668
4
- nxs_analysis_tools/datareduction.py,sha256=yb0L6Y48z72JUpYg5GOjmJxvDfbOYus0WKhjZNMY4rA,52110
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.6.dist-info/licenses/LICENSE,sha256=bE6FnYixueAGAnEfUuumbkSeMgdBguAAkheVgjv47Jo,1086
8
- nxs_analysis_tools-0.1.6.dist-info/METADATA,sha256=e_zGdrs-JbBrUDr6CtjCJpQT5aFt2sVZybxDhKXraDc,3180
9
- nxs_analysis_tools-0.1.6.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
10
- nxs_analysis_tools-0.1.6.dist-info/top_level.txt,sha256=8U000GNPzo6T6pOMjRdgOSO5heMzLMGjkxa1CDtyMHM,25
11
- nxs_analysis_tools-0.1.6.dist-info/RECORD,,