hyperspy / exspy

EDS and EELS data analysis with HyperSpy
https://hyperspy.org/exspy
GNU General Public License v3.0
10 stars 10 forks source link

Reading an EELS spectrum image from a tiff file #53

Closed HamishGBrown closed 3 months ago

HamishGBrown commented 3 months ago

Describe the bug

I recorded some EELS data using GMS 2 yesterday and didn't notice that the previous user had been saving in tiff and not dm4 format so saved all my spectra in this format. Thankfully the spectra are saved as tiff stacks with the all relevant axes information in the tiff header, however when I try and read these files with exspy the conversion to an EELS datatype within hyperspy is not occuring properly

To Reproduce

Steps to reproduce the behavior:

In [1]: import hyperspy.api as hs

In [2]: gfp = hs.load("/home/hbrown/Mount/F30data/Hamish/20240603_GFP_BSA_comparison/{0}/{1}_EELS Spectrum Image.tif".format('1_5mgmlGFP','AF'))

In [3]: gfp.set_signal_type('EELS')

In [4]: gfp
Out[4]: <Signal2D, title: , dimensions: (2048|28, 30)>

In [5]: gfp.align_zero_loss_peak(subpixel=True)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[5], line 1
----> 1 gfp.align_zero_loss_peak(subpixel=True)

AttributeError: 'Signal2D' object has no attribute 'align_zero_loss_peak'

Using the exspy.signals.EELSSpectrum object more explicity:

In [41]: exspy.signals.EELSSpectrum(gfp.data,axes=gfp.axes_manager.as_dictionary())
---------------------------------------------------------------------------
TraitError                                Traceback (most recent call last)
Cell In[41], line 1
----> 1 exspy.signals.EELSSpectrum(gfp.data,axes=gfp.axes_manager.as_dictionary())

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/exspy/signals/eels.py:81, in EELSSpectrum.__init__(self, *args, **kwargs)
     80 def __init__(self, *args, **kwargs):
---> 81     super().__init__(*args, **kwargs)
     82     # Attributes defaults
     83     self.subshells = set()

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/hyperspy/_signals/signal1d.py:293, in Signal1D.__init__(self, *args, **kwargs)
    291 if kwargs.get("ragged", False):
    292     raise ValueError("Signal1D can't be ragged.")
--> 293 super().__init__(*args, **kwargs)

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/hyperspy/signal.py:2483, in BaseSignal.__init__(self, data, **kwds)
   2465     self.events = Events()
   2466     self.events.data_changed = Event(
   2467         """
   2468         Event that triggers when the data has changed
   (...)
   2481         arguments=["obj"],
   2482     )
-> 2483     self._load_dictionary(kwds)
   2485 if self._signal_dimension >= 0:
   2486     # We don't explicitly set the signal_dimension of ragged because
   2487     # we can't predict it in advance
   2488     self.axes_manager._set_signal_dimension(self._signal_dimension)

Trying to pass the gpf.axes_manager object as the axes kwarg yielded:

In [26]: exspy.signals.EELSSpectrum(gfp.data,axes=gfp.axes_manager)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[26], line 1
----> 1 exspy.signals.EELSSpectrum(gfp.data,axes=gfp.axes_manager)

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/exspy/signals/eels.py:81, in EELSSpectrum.__init__(self, *args, **kwargs)
     80 def __init__(self, *args, **kwargs):
---> 81     super().__init__(*args, **kwargs)
     82     # Attributes defaults
     83     self.subshells = set()

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/hyperspy/_signals/signal1d.py:293, in Signal1D.__init__(self, *args, **kwargs)
    291 if kwargs.get("ragged", False):
    292     raise ValueError("Signal1D can't be ragged.")
--> 293 super().__init__(*args, **kwargs)

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/hyperspy/signal.py:2483, in BaseSignal.__init__(self, data, **kwds)
   2465     self.events = Events()
   2466     self.events.data_changed = Event(
   2467         """
   2468         Event that triggers when the data has changed
   (...)
   2481         arguments=["obj"],
   2482     )
-> 2483     self._load_dictionary(kwds)
   2485 if self._signal_dimension >= 0:
   2486     # We don't explicitly set the signal_dimension of ragged because
   2487     # we can't predict it in advance
   2488     self.axes_manager._set_signal_dimension(self._signal_dimension)

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/hyperspy/signal.py:2816, in BaseSignal._load_dictionary(self, file_data_dict)
   2812 if "axes" not in file_data_dict:
   2813     file_data_dict["axes"] = self._get_undefined_axes_list(
   2814         attributes.get("ragged", False)
   2815     )
-> 2816 self.axes_manager = AxesManager(file_data_dict["axes"])
   2817 # Setting `ragged` attributes requires the `axes_manager`
   2818 for key, value in attributes.items():

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/hyperspy/axes.py:1542, in AxesManager.__init__(self, axes_list)
   1540 if self._axes:
   1541     self.remove(self._axes)
-> 1542 self.create_axes(axes_list)
   1544 self._update_attributes()
   1545 self._update_trait_handlers()

File ~/anaconda3/envs/hspy_environment/lib/python3.12/site-packages/hyperspy/axes.py:1755, in AxesManager.create_axes(self, axes_list)
   1746 # Reorder axes_list using index_in_array if it is defined
   1747 # for all axes and the indices are not repeated.
   1748 indices = set(
   1749     [
   1750         axis["index_in_array"]
   (...)
   1753     ]
   1754 )
-> 1755 if len(indices) == len(axes_list):
   1756     axes_list.sort(key=lambda x: x["index_in_array"])
   1757 for axis_dict in axes_list:

TypeError: object of type 'AxesManager' has no len()

Expected behavior

There might be a better way to do this conversion, if so please let me know.

Python environment:

Name Version Build Channel

hyperspy 2.1.0 pyhd8ed1ab_0 conda-forge exspy 0.2 pyhd8ed1ab_0 conda-forge python 3.12.3 hab00c5b_0_cpython conda-forge

Additional context

I can't upload the raw spectrum as it is too large but can share a onedrive link if requested.

CSSFrancis commented 3 months ago

@HamishGBrown it looks like the data is 28,30|2048 channels? Is that correct. Tiffs only support 3 dimension stacks with 2 of the dimensions for each "image" so likely the data is saved transposed which is why it is loaded in as a signal 2D or 2048|30,28.

Try this:

gfp = hs.load("/home/hbrown/Mount/F30data/Hamish/20240603_GFP_BSA_comparison/{0}/{1}_EELS Spectrum Image.tif".format('1_5mgmlGFP','AF'))

gfp.T #the signal is now a Signal1D
gfp.set_signal_type("EELSSpectrum") 

You might need to manually adjust the axes manager based on the header and set things like gfp.axes_manager.signal_axes[0].scale

Let me know if you need more help and if you share the data I can look at it further. If there is enough metadata in the header this might be some thing we could support

HamishGBrown commented 3 months ago

@CSSFrancis, this seems to have fixed the problem so thank you for your help. I had to make one edit:

gfp = gfp.T #the signal is now a Signal1D

To the code you suggested since the transpose method seems to return a copy of the object but doesn't affect the original object.