Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ragged array #2842

Merged
merged 12 commits into from Nov 16, 2021
Merged

Conversation

ericpre
Copy link
Member

@ericpre ericpre commented Oct 24, 2021

The changes in #2773 break the pyxem test suite. In my opinion, using the use of ragged array is not clear enough and there is an inconsistency in slicing array. This has been been discussed and introduced in #1336, in particular starting from #1336 (comment).

Before this PR

import hyperspy.api as hs
import numpy as np

data = np.empty((3, 4), dtype=object)
data.fill(np.array([10, 20]))
s = hs.signals.BaseSignal(data).T

print(s)
# <BaseSignal, title: , dimensions: (4, 3|)>

s2 = s.inav[0]
print(s2)
# <BaseSignal, title: , dimensions: (3|)>
assert s2.axes_manager.signal_dimension == 0
assert s2.axes_manager.signal_shape == ()
assert s2.axes_manager.navigation_shape == (3,)

s3 = s.inav[0, 0]
print(s3)
# <BaseSignal, title: , dimensions: (|1)>
assert s3.axes_manager.signal_dimension == 0
assert s3.axes_manager.signal_shape == (1,)
assert s3.axes_manager.navigation_shape == ()

np.testing.assert_allclose(s()[0], s2()[0])
np.testing.assert_allclose(s()[0], s3()[0]) # fail

With this PR

data = np.empty((3, 4), dtype=object)
data.fill(np.array([10, 20]))
s = hs.signals.BaseSignal(data, ragged=True)

print(s)
# <BaseSignal, title: , dimensions: (4, 3|ragged)>

print(s.axes_manager)
# <Axes manager, axes: (4, 3|ragged)>
#             Name |   size |  index |  offset |   scale |  units 
# ================ | ====== | ====== | ======= | ======= | ====== 
#      <undefined> |      4 |      0 |       0 |       1 | <undefined> 
#      <undefined> |      3 |      0 |       0 |       1 | <undefined> 
# ---------------- | ------ | ------ | ------- | ------- | ------ 
#      Ragged axis |               Variable length

s2 = s.inav[0]
print(s2)
# <BaseSignal, title: , dimensions: (3|ragged)>
assert s2.axes_manager.signal_dimension == 0
assert s2.axes_manager.signal_shape == ()
assert s2.axes_manager.navigation_shape == (3,)

s3 = s.inav[0, 0]
print(s3)
# <BaseSignal, title: , dimensions: (|ragged)>
assert s3.axes_manager.signal_dimension == 0
assert s3.axes_manager.signal_shape == ()
assert s3.axes_manager.navigation_shape == ()

np.testing.assert_allclose(s()[0], s2()[0])
np.testing.assert_allclose(s()[0], s3()[0])

Progress of the PR

  • Make AxesManager ragged aware and show corresponding axis, this should be more transparent to the users
  • Remove special slicing for ragged array containing a single array
  • update docstring,
  • update user guide,
  • add an changelog entry in the upcoming_changes folder (see upcoming_changes/README.rst),
  • Check formatting changelog entry in the readthedocs doc build of this PR (link in github checks)
  • add tests,
  • ready for review.

@ericpre ericpre added this to the v1.7 milestone Oct 24, 2021
@ericpre ericpre marked this pull request as draft October 24, 2021 19:34
@codecov
Copy link

codecov bot commented Oct 24, 2021

Codecov Report

Merging #2842 (e8f0523) into RELEASE_next_minor (4a7e59c) will increase coverage by 0.00%.
The diff coverage is 92.92%.

Impacted file tree graph

@@                 Coverage Diff                 @@
##           RELEASE_next_minor    #2842   +/-   ##
===================================================
  Coverage               77.11%   77.11%           
===================================================
  Files                     206      206           
  Lines                   31600    31656   +56     
  Branches                 6918     6934   +16     
===================================================
+ Hits                    24367    24412   +45     
- Misses                   5482     5489    +7     
- Partials                 1751     1755    +4     
Impacted Files Coverage Δ
hyperspy/drawing/utils.py 74.81% <ø> (ø)
hyperspy/axes.py 90.52% <80.00%> (+0.02%) ⬆️
hyperspy/misc/slicing.py 85.20% <81.81%> (+1.18%) ⬆️
hyperspy/signal.py 73.94% <96.42%> (+0.13%) ⬆️
hyperspy/_signals/lazy.py 89.19% <100.00%> (-0.11%) ⬇️
hyperspy/_signals/signal1d.py 77.86% <100.00%> (+0.08%) ⬆️
hyperspy/_signals/signal2d.py 80.53% <100.00%> (+0.13%) ⬆️
hyperspy/misc/utils.py 85.51% <100.00%> (-0.05%) ⬇️
hyperspy/io_plugins/zspy.py 93.02% <0.00%> (-2.33%) ⬇️
hyperspy/io_plugins/hspy.py 91.42% <0.00%> (-1.91%) ⬇️
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4a7e59c...e8f0523. Read the comment docs.

This was referenced Oct 26, 2021
@magnunor
Copy link
Contributor

I very much agree with this improvement. Better handling of this type of "variable length" data will be useful for things like peak finding, where we typically get very different amounts of peaks per navigation position.

@magnunor
Copy link
Contributor

I spent a bit of time trying to sort out failing pyxem functions caused by the improvements in HyperSpy s.map #2703. Some of these are failing due to s.inav[...] not working for the lazy BaseSignal: LazySignal.

Minimal working examples

In the current RELEASE_next_minor

import numpy as np
import hyperspy.api as hs

array_ragged = np.empty((2, 3), dtype=object)
for iy, ix in np.ndindex(array_ragged.shape):
    array_ragged[iy, ix] = np.random.randint(0, 20, size=np.random.randint(2, 10))

s = hs.signals.BaseSignal(array_ragged).T
s_lazy = s.as_lazy()

s1 = s.inav[2, 1] # Works fine
s1_lazy = s_lazy.inav[2, 1] # Does not work

In this pull request

This pull request changes this, but trying to compute s1_lazy does not work

import numpy as np
import dask.array as da
import hyperspy.api as hs

array_ragged = np.empty((2, 3), dtype=object)
for iy, ix in np.ndindex(array_ragged.shape):
    array_ragged[iy, ix] = np.random.randint(0, 20, size=np.random.randint(2, 10))

s = hs.signals.BaseSignal(array_ragged, ragged=True)
s_lazy = s.as_lazy()

s1 = s.inav[2, 1] # Works fine
s1_lazy = s_lazy.inav[2, 1] # Works fine

s1_lazy.compute() # Does not work

hyperspy/signal.py Outdated Show resolved Hide resolved
@ericpre
Copy link
Member Author

ericpre commented Oct 29, 2021

In this pull request

This pull request changes this, but trying to compute s1_lazy does not work

import numpy as np
import dask.array as da
import hyperspy.api as hs

array_ragged = np.empty((2, 3), dtype=object)
for iy, ix in np.ndindex(array_ragged.shape):
    array_ragged[iy, ix] = np.random.randint(0, 20, size=np.random.randint(2, 10))

s = hs.signals.BaseSignal(array_ragged, ragged=True)
s_lazy = s.as_lazy()

s1 = s.inav[2, 1] # Works fine
s1_lazy = s_lazy.inav[2, 1] # Works fine

s1_lazy.compute() # Does not work

This is different bug, coming from a special case for dask array when creating lazy signal. This should be fixed in b7502ea.

@ericpre ericpre added run-extension-tests Run extension test suites type: bug-fix labels Oct 29, 2021
@ericpre
Copy link
Member Author

ericpre commented Oct 29, 2021

pyxem failure are:

2021-10-29T18:16:33.2919492Z FAILED tests/generators/test_indexation_generator.py::test_vector_indexation_generator_index_vectors
2021-10-29T18:16:33.2920772Z FAILED tests/generators/test_subpixelrefinement_generator.py::Test_subpixelpeakfinders::test_assertioned_xc[diffraction_vectors0]
2021-10-29T18:16:33.2922257Z FAILED tests/generators/test_subpixelrefinement_generator.py::Test_subpixelpeakfinders::test_assertioned_com[diffraction_vectors0]
2021-10-29T18:16:33.2924365Z FAILED tests/generators/test_subpixelrefinement_generator.py::Test_subpixelpeakfinders::test_log[diffraction_vectors0]
2021-10-29T18:16:33.2925478Z = 4 failed, 1217 passed, 195 skipped, 6 xfailed, 399 warnings in 134.83s (0:02:14) =
details of the failures

2021-10-29T18:16:33.2259230Z =================================== FAILURES ===================================
2021-10-29T18:16:33.2259931Z ________________ test_vector_indexation_generator_index_vectors ________________
2021-10-29T18:16:33.2261987Z [gw0] linux -- Python 3.9.7 /usr/share/miniconda3/bin/python
2021-10-29T18:16:33.2262705Z 
2021-10-29T18:16:33.2263270Z vector_match_peaks = array([[1. , 0.1, 0. ],
2021-10-29T18:16:33.2263808Z        [0. , 2. , 0. ],
2021-10-29T18:16:33.2264716Z        [1. , 2. , 3. ]])
2021-10-29T18:16:33.2265638Z vector_library = {'A': {'indices': array([[[0, 2, 0],
2021-10-29T18:16:33.2266185Z         [1, 0, 0]],
2021-10-29T18:16:33.2266500Z 
2021-10-29T18:16:33.2266898Z        [[1, 2, 3],
2021-10-29T18:16:33.2267490Z         [0, 2, 0]],
2021-10-29T18:16:33.2267870Z 
2021-10-29T18:16:33.2268269Z        [[1, 2, 3],
2021-10-29T18:16:33.2268669Z  ..., 1.        , 1.57079633],
2021-10-29T18:16:33.2269102Z        [3.74165739, 2.        , 1.00685369],
2021-10-29T18:16:33.2269554Z        [3.74165739, 1.        , 1.30024656]])}}
2021-10-29T18:16:33.2269871Z 
2021-10-29T18:16:33.2270559Z     def test_vector_indexation_generator_index_vectors(vector_match_peaks, vector_library):
2021-10-29T18:16:33.2271336Z         # vectors not used directly
2021-10-29T18:16:33.2272032Z         vectors = DiffractionVectors(np.array(vector_match_peaks[:, :2]))
2021-10-29T18:16:33.2273119Z         vectors.cartesian = DiffractionVectors(np.array(vector_match_peaks))
2021-10-29T18:16:33.2274152Z         gen = VectorIndexationGenerator(vectors, vector_library)
2021-10-29T18:16:33.2274993Z         indexation = gen.index_vectors(
2021-10-29T18:16:33.2275813Z             mag_tol=0.1, angle_tol=6, index_error_tol=0.3, n_peaks_to_index=2, n_best=5
2021-10-29T18:16:33.2276487Z         )
2021-10-29T18:16:33.2276897Z     
2021-10-29T18:16:33.2277518Z         # Values are tested directly on the match_vector in the util tests
2021-10-29T18:16:33.2278434Z         assert isinstance(indexation.vectors, DiffractionVectors)
2021-10-29T18:16:33.2279147Z     
2021-10-29T18:16:33.2279667Z         # (n_best=1, 5 result values from each)
2021-10-29T18:16:33.2280550Z >       np.testing.assert_equal(indexation.data.shape, (5,))
2021-10-29T18:16:33.2282795Z E       AssertionError: 
2021-10-29T18:16:33.2283235Z E       Items are not equal:
2021-10-29T18:16:33.2283612Z E       item=0
2021-10-29T18:16:33.2283909Z E       
2021-10-29T18:16:33.2284234Z E        ACTUAL: 1
2021-10-29T18:16:33.2284574Z E        DESIRED: 5
2021-10-29T18:16:33.2284814Z 
2021-10-29T18:16:33.2286491Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/tests/generators/test_indexation_generator.py:245: AssertionError
2021-10-29T18:16:33.2287775Z ------------------------------ Captured log call -------------------------------
2021-10-29T18:16:33.2288689Z WARNING  hyperspy.signal:signal.py:3589 The default iterpath will change in HyperSpy 2.0.
2021-10-29T18:16:33.2289827Z ______ Test_subpixelpeakfinders.test_assertioned_xc[diffraction_vectors0] ______
2021-10-29T18:16:33.2290852Z [gw0] linux -- Python 3.9.7 /usr/share/miniconda3/bin/python
2021-10-29T18:16:33.2291248Z 
2021-10-29T18:16:33.2292340Z self = <pyxem.tests.generators.test_subpixelrefinement_generator.Test_subpixelpeakfinders object at 0x7f350afbf880>
2021-10-29T18:16:33.2293619Z diffraction_vectors = <DiffractionVectors, title: , dimensions: (2, 2|)>
2021-10-29T18:16:33.2294194Z 
2021-10-29T18:16:33.2294832Z     def test_assertioned_xc(self, diffraction_vectors):
2021-10-29T18:16:33.2295917Z >       subpixelsfound = self.get_spr(diffraction_vectors).conventional_xc(12, 4, 8)
2021-10-29T18:16:33.2296419Z 
2021-10-29T18:16:33.2297478Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/tests/generators/test_subpixelrefinement_generator.py:161: 
2021-10-29T18:16:33.2298329Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2021-10-29T18:16:33.2299452Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/tests/generators/test_subpixelrefinement_generator.py:148: in get_spr
2021-10-29T18:16:33.2300645Z     return SubpixelrefinementGenerator(dp, diffraction_vectors)
2021-10-29T18:16:33.2302088Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/generators/subpixelrefinement_generator.py:201: in __init__
2021-10-29T18:16:33.2303424Z     self.vector_pixels = _get_pixel_vectors(
2021-10-29T18:16:33.2303899Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2021-10-29T18:16:33.2304327Z 
2021-10-29T18:16:33.2304874Z dp = <ElectronDiffraction2D, title: , dimensions: (2, 2|128, 128)>
2021-10-29T18:16:33.2305724Z vectors = <DiffractionVectors, title: , dimensions: (2, 2|)>
2021-10-29T18:16:33.2306302Z calibration = [1.0, 1.0], center = [64.0, 64.0]
2021-10-29T18:16:33.2306582Z 
2021-10-29T18:16:33.2307138Z     def _get_pixel_vectors(dp, vectors, calibration, center):
2021-10-29T18:16:33.2307795Z         """Get the pixel coordinates for the given diffraction
2021-10-29T18:16:33.2308302Z         pattern and vectors.
2021-10-29T18:16:33.2308644Z     
2021-10-29T18:16:33.2308958Z         Parameters
2021-10-29T18:16:33.2309483Z         ----------
2021-10-29T18:16:33.2310117Z         dp: :obj:`pyxem.signals.ElectronDiffraction2D`
2021-10-29T18:16:33.2311016Z             Instance of ElectronDiffraction2D
2021-10-29T18:16:33.2312064Z         vectors : :obj:`pyxem.signals.diffraction_vectors.DiffractionVectors`
2021-10-29T18:16:33.2313028Z             List of diffraction vectors
2021-10-29T18:16:33.2313547Z         calibration : [float, float]
2021-10-29T18:16:33.2314038Z             Calibration values
2021-10-29T18:16:33.2314697Z         center : float, float
2021-10-29T18:16:33.2315170Z             Image origin in pixel coordinates
2021-10-29T18:16:33.2315658Z     
2021-10-29T18:16:33.2315953Z         Returns
2021-10-29T18:16:33.2316387Z         -------
2021-10-29T18:16:33.2317102Z         vector_pixels : :obj:`pyxem.signals.diffraction_vectors.DiffractionVectors`
2021-10-29T18:16:33.2318016Z             Pixel coordinates for given diffraction pattern and vectors.
2021-10-29T18:16:33.2318494Z         """
2021-10-29T18:16:33.2318766Z     
2021-10-29T18:16:33.2319163Z         def _floor(vectors, calibration, center):
2021-10-29T18:16:33.2319767Z             if vectors.shape == (1,) and vectors.dtype == object:
2021-10-29T18:16:33.2320270Z                 vectors = vectors[0]
2021-10-29T18:16:33.2320885Z             return np.floor((vectors.astype(np.float64) / calibration) + center).astype(
2021-10-29T18:16:33.2321447Z                 np.int
2021-10-29T18:16:33.2321734Z             )
2021-10-29T18:16:33.2322008Z     
2021-10-29T18:16:33.2322580Z         if isinstance(vectors, DiffractionVectors):
2021-10-29T18:16:33.2323437Z             if vectors.axes_manager.navigation_shape != dp.axes_manager.navigation_shape:
2021-10-29T18:16:33.2324160Z                 raise ValueError(
2021-10-29T18:16:33.2324719Z                     "Vectors with shape {} must have the same navigation shape "
2021-10-29T18:16:33.2325507Z                     "as the diffraction patterns which has shape {}.".format(
2021-10-29T18:16:33.2326140Z                         vectors.axes_manager.navigation_shape,
2021-10-29T18:16:33.2326760Z                         dp.axes_manager.navigation_shape,
2021-10-29T18:16:33.2327188Z                     )
2021-10-29T18:16:33.2327472Z                 )
2021-10-29T18:16:33.2327852Z             vector_pixels = vectors.map(
2021-10-29T18:16:33.2328446Z                 _floor, calibration=calibration, center=center, inplace=False
2021-10-29T18:16:33.2328947Z             )
2021-10-29T18:16:33.2329228Z         else:
2021-10-29T18:16:33.2329697Z             vector_pixels = _floor(vectors, calibration, center)
2021-10-29T18:16:33.2330131Z     
2021-10-29T18:16:33.2330613Z         if isinstance(vector_pixels, DiffractionVectors):
2021-10-29T18:16:33.2331506Z >           if np.any(vector_pixels.data > (np.max(dp.data.shape) - 1)) or (
2021-10-29T18:16:33.2332073Z                 np.any(vector_pixels.data < 0)
2021-10-29T18:16:33.2332573Z             ):
2021-10-29T18:16:33.2333181Z E           ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
2021-10-29T18:16:33.2333706Z 
2021-10-29T18:16:33.2334888Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/generators/subpixelrefinement_generator.py:128: ValueError
2021-10-29T18:16:33.2336435Z ------------------------------ Captured log call -------------------------------
2021-10-29T18:16:33.2337730Z WARNING  hyperspy.signal:signal.py:4745 The function you applied does not take into account the difference of units and of scales in-between axes.
2021-10-29T18:16:33.2339097Z WARNING  hyperspy.signal:signal.py:3589 The default iterpath will change in HyperSpy 2.0.
2021-10-29T18:16:33.2340204Z _____ Test_subpixelpeakfinders.test_assertioned_com[diffraction_vectors0] ______
2021-10-29T18:16:33.2341253Z [gw0] linux -- Python 3.9.7 /usr/share/miniconda3/bin/python
2021-10-29T18:16:33.2341634Z 
2021-10-29T18:16:33.2342652Z self = <pyxem.tests.generators.test_subpixelrefinement_generator.Test_subpixelpeakfinders object at 0x7f35181d02e0>
2021-10-29T18:16:33.2344333Z diffraction_vectors = <DiffractionVectors, title: , dimensions: (2, 2|)>
2021-10-29T18:16:33.2344899Z 
2021-10-29T18:16:33.2345405Z     def test_assertioned_com(self, diffraction_vectors):
2021-10-29T18:16:33.2346354Z >       subpixelsfound = self.get_spr(diffraction_vectors).center_of_mass_method(12)
2021-10-29T18:16:33.2361317Z 
2021-10-29T18:16:33.2362703Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/tests/generators/test_subpixelrefinement_generator.py:166: 
2021-10-29T18:16:33.2363615Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2021-10-29T18:16:33.2364812Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/tests/generators/test_subpixelrefinement_generator.py:148: in get_spr
2021-10-29T18:16:33.2366040Z     return SubpixelrefinementGenerator(dp, diffraction_vectors)
2021-10-29T18:16:33.2367686Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/generators/subpixelrefinement_generator.py:201: in __init__
2021-10-29T18:16:33.2368584Z     self.vector_pixels = _get_pixel_vectors(
2021-10-29T18:16:33.2369072Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2021-10-29T18:16:33.2369433Z 
2021-10-29T18:16:33.2369924Z dp = <ElectronDiffraction2D, title: , dimensions: (2, 2|128, 128)>
2021-10-29T18:16:33.2370644Z vectors = <DiffractionVectors, title: , dimensions: (2, 2|)>
2021-10-29T18:16:33.2371221Z calibration = [1.0, 1.0], center = [64.0, 64.0]
2021-10-29T18:16:33.2371490Z 
2021-10-29T18:16:33.2371948Z     def _get_pixel_vectors(dp, vectors, calibration, center):
2021-10-29T18:16:33.2372718Z         """Get the pixel coordinates for the given diffraction
2021-10-29T18:16:33.2373392Z         pattern and vectors.
2021-10-29T18:16:33.2373765Z     
2021-10-29T18:16:33.2374095Z         Parameters
2021-10-29T18:16:33.2374620Z         ----------
2021-10-29T18:16:33.2375307Z         dp: :obj:`pyxem.signals.ElectronDiffraction2D`
2021-10-29T18:16:33.2376358Z             Instance of ElectronDiffraction2D
2021-10-29T18:16:33.2377207Z         vectors : :obj:`pyxem.signals.diffraction_vectors.DiffractionVectors`
2021-10-29T18:16:33.2377964Z             List of diffraction vectors
2021-10-29T18:16:33.2378419Z         calibration : [float, float]
2021-10-29T18:16:33.2378837Z             Calibration values
2021-10-29T18:16:33.2379244Z         center : float, float
2021-10-29T18:16:33.2379679Z             Image origin in pixel coordinates
2021-10-29T18:16:33.2380066Z     
2021-10-29T18:16:33.2380359Z         Returns
2021-10-29T18:16:33.2380793Z         -------
2021-10-29T18:16:33.2381511Z         vector_pixels : :obj:`pyxem.signals.diffraction_vectors.DiffractionVectors`
2021-10-29T18:16:33.2382417Z             Pixel coordinates for given diffraction pattern and vectors.
2021-10-29T18:16:33.2382913Z         """
2021-10-29T18:16:33.2383171Z     
2021-10-29T18:16:33.2383581Z         def _floor(vectors, calibration, center):
2021-10-29T18:16:33.2384449Z             if vectors.shape == (1,) and vectors.dtype == object:
2021-10-29T18:16:33.2384964Z                 vectors = vectors[0]
2021-10-29T18:16:33.2385732Z             return np.floor((vectors.astype(np.float64) / calibration) + center).astype(
2021-10-29T18:16:33.2386559Z                 np.int
2021-10-29T18:16:33.2386878Z             )
2021-10-29T18:16:33.2387162Z     
2021-10-29T18:16:33.2387654Z         if isinstance(vectors, DiffractionVectors):
2021-10-29T18:16:33.2388584Z             if vectors.axes_manager.navigation_shape != dp.axes_manager.navigation_shape:
2021-10-29T18:16:33.2403262Z                 raise ValueError(
2021-10-29T18:16:33.2403853Z                     "Vectors with shape {} must have the same navigation shape "
2021-10-29T18:16:33.2404761Z                     "as the diffraction patterns which has shape {}.".format(
2021-10-29T18:16:33.2405420Z                         vectors.axes_manager.navigation_shape,
2021-10-29T18:16:33.2406047Z                         dp.axes_manager.navigation_shape,
2021-10-29T18:16:33.2406472Z                     )
2021-10-29T18:16:33.2406761Z                 )
2021-10-29T18:16:33.2407145Z             vector_pixels = vectors.map(
2021-10-29T18:16:33.2407748Z                 _floor, calibration=calibration, center=center, inplace=False
2021-10-29T18:16:33.2408251Z             )
2021-10-29T18:16:33.2408534Z         else:
2021-10-29T18:16:33.2409006Z             vector_pixels = _floor(vectors, calibration, center)
2021-10-29T18:16:33.2409445Z     
2021-10-29T18:16:33.2409926Z         if isinstance(vector_pixels, DiffractionVectors):
2021-10-29T18:16:33.2411057Z >           if np.any(vector_pixels.data > (np.max(dp.data.shape) - 1)) or (
2021-10-29T18:16:33.2411808Z                 np.any(vector_pixels.data < 0)
2021-10-29T18:16:33.2412228Z             ):
2021-10-29T18:16:33.2412860Z E           ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
2021-10-29T18:16:33.2413400Z 
2021-10-29T18:16:33.2414542Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/generators/subpixelrefinement_generator.py:128: ValueError
2021-10-29T18:16:33.2415853Z ------------------------------ Captured log call -------------------------------
2021-10-29T18:16:33.2417162Z WARNING  hyperspy.signal:signal.py:4745 The function you applied does not take into account the difference of units and of scales in-between axes.
2021-10-29T18:16:33.2418496Z WARNING  hyperspy.signal:signal.py:3589 The default iterpath will change in HyperSpy 2.0.
2021-10-29T18:16:33.2419352Z ___________ Test_subpixelpeakfinders.test_log[diffraction_vectors0] ____________
2021-10-29T18:16:33.2420248Z [gw0] linux -- Python 3.9.7 /usr/share/miniconda3/bin/python
2021-10-29T18:16:33.2420607Z 
2021-10-29T18:16:33.2421444Z self = <pyxem.tests.generators.test_subpixelrefinement_generator.Test_subpixelpeakfinders object at 0x7f3518306bb0>
2021-10-29T18:16:33.2422551Z diffraction_vectors = <DiffractionVectors, title: , dimensions: (2, 2|)>
2021-10-29T18:16:33.2423007Z 
2021-10-29T18:16:33.2423389Z     def test_log(self, diffraction_vectors):
2021-10-29T18:16:33.2423867Z         with pytest.raises(
2021-10-29T18:16:33.2424638Z             NotImplementedError,
2021-10-29T18:16:33.2425268Z             match="This functionality was removed in v.0.13.0",
2021-10-29T18:16:33.2425740Z         ):
2021-10-29T18:16:33.2426251Z >           _ = self.get_spr(diffraction_vectors).local_gaussian_method(12)
2021-10-29T18:16:33.2426682Z 
2021-10-29T18:16:33.2427941Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/tests/generators/test_subpixelrefinement_generator.py:175: 
2021-10-29T18:16:33.2428708Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2021-10-29T18:16:33.2429732Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/tests/generators/test_subpixelrefinement_generator.py:148: in get_spr
2021-10-29T18:16:33.2430796Z     return SubpixelrefinementGenerator(dp, diffraction_vectors)
2021-10-29T18:16:33.2432032Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/generators/subpixelrefinement_generator.py:201: in __init__
2021-10-29T18:16:33.2432886Z     self.vector_pixels = _get_pixel_vectors(
2021-10-29T18:16:33.2433487Z _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
2021-10-29T18:16:33.2433728Z 
2021-10-29T18:16:33.2434230Z dp = <ElectronDiffraction2D, title: , dimensions: (2, 2|128, 128)>
2021-10-29T18:16:33.2435189Z vectors = <DiffractionVectors, title: , dimensions: (2, 2|)>
2021-10-29T18:16:33.2435965Z calibration = [1.0, 1.0], center = [64.0, 64.0]
2021-10-29T18:16:33.2436285Z 
2021-10-29T18:16:33.2436797Z     def _get_pixel_vectors(dp, vectors, calibration, center):
2021-10-29T18:16:33.2437611Z         """Get the pixel coordinates for the given diffraction
2021-10-29T18:16:33.2438203Z         pattern and vectors.
2021-10-29T18:16:33.2438712Z     
2021-10-29T18:16:33.2439241Z         Parameters
2021-10-29T18:16:33.2439754Z         ----------
2021-10-29T18:16:33.2440428Z         dp: :obj:`pyxem.signals.ElectronDiffraction2D`
2021-10-29T18:16:33.2441195Z             Instance of ElectronDiffraction2D
2021-10-29T18:16:33.2442045Z         vectors : :obj:`pyxem.signals.diffraction_vectors.DiffractionVectors`
2021-10-29T18:16:33.2442809Z             List of diffraction vectors
2021-10-29T18:16:33.2443264Z         calibration : [float, float]
2021-10-29T18:16:33.2443683Z             Calibration values
2021-10-29T18:16:33.2444087Z         center : float, float
2021-10-29T18:16:33.2444533Z             Image origin in pixel coordinates
2021-10-29T18:16:33.2445035Z     
2021-10-29T18:16:33.2445474Z         Returns
2021-10-29T18:16:33.2445954Z         -------
2021-10-29T18:16:33.2446751Z         vector_pixels : :obj:`pyxem.signals.diffraction_vectors.DiffractionVectors`
2021-10-29T18:16:33.2447748Z             Pixel coordinates for given diffraction pattern and vectors.
2021-10-29T18:16:33.2448497Z         """
2021-10-29T18:16:33.2448772Z     
2021-10-29T18:16:33.2449778Z         def _floor(vectors, calibration, center):
2021-10-29T18:16:33.2451822Z             if vectors.shape == (1,) and vectors.dtype == object:
2021-10-29T18:16:33.2452558Z                 vectors = vectors[0]
2021-10-29T18:16:33.2453297Z             return np.floor((vectors.astype(np.float64) / calibration) + center).astype(
2021-10-29T18:16:33.2453868Z                 np.int
2021-10-29T18:16:33.2454155Z             )
2021-10-29T18:16:33.2454428Z     
2021-10-29T18:16:33.2455002Z         if isinstance(vectors, DiffractionVectors):
2021-10-29T18:16:33.2456000Z             if vectors.axes_manager.navigation_shape != dp.axes_manager.navigation_shape:
2021-10-29T18:16:33.2456736Z                 raise ValueError(
2021-10-29T18:16:33.2457326Z                     "Vectors with shape {} must have the same navigation shape "
2021-10-29T18:16:33.2458037Z                     "as the diffraction patterns which has shape {}.".format(
2021-10-29T18:16:33.2458900Z                         vectors.axes_manager.navigation_shape,
2021-10-29T18:16:33.2459520Z                         dp.axes_manager.navigation_shape,
2021-10-29T18:16:33.2459945Z                     )
2021-10-29T18:16:33.2460231Z                 )
2021-10-29T18:16:33.2460601Z             vector_pixels = vectors.map(
2021-10-29T18:16:33.2461490Z                 _floor, calibration=calibration, center=center, inplace=False
2021-10-29T18:16:33.2462045Z             )
2021-10-29T18:16:33.2462356Z         else:
2021-10-29T18:16:33.2463117Z             vector_pixels = _floor(vectors, calibration, center)
2021-10-29T18:16:33.2463603Z     
2021-10-29T18:16:33.2464506Z         if isinstance(vector_pixels, DiffractionVectors):
2021-10-29T18:16:33.2466053Z >           if np.any(vector_pixels.data > (np.max(dp.data.shape) - 1)) or (
2021-10-29T18:16:33.2466714Z                 np.any(vector_pixels.data < 0)
2021-10-29T18:16:33.2467131Z             ):
2021-10-29T18:16:33.2471114Z E           ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
2021-10-29T18:16:33.2471647Z 
2021-10-29T18:16:33.2472806Z /usr/share/miniconda3/lib/python3.9/site-packages/pyxem/generators/subpixelrefinement_generator.py:128: ValueError

From a quick look at the failure, it seems that there are most likely related and shouldn't be difficult to fix.

@ericpre
Copy link
Member Author

ericpre commented Oct 29, 2021

@pc494, @CSSFrancis, @din14970, I don't know if you are using much ragged array (for sure, some of you use it much more than me!) and it you find working with ragged array annoying, this is a good moment to have a look at it to improve the situation. Thanks!

One thing that I have left on this PR is to add a section on creating ragged signal in the user guide. Other than that it is ready for discussion.

@magnunor
Copy link
Contributor

I think the most common use for ragged signals at the moment is peak finding, I'll try it for that use case.

@ericpre
Copy link
Member Author

ericpre commented Oct 29, 2021

Yes, most likely. When tasting the water with pyxem on this, I have seen it been used for the indexation too.

@ericpre ericpre mentioned this pull request Oct 30, 2021
9 tasks
@pc494
Copy link
Contributor

pc494 commented Oct 30, 2021

I agree with the above, by far the most prevalent use of ragged signals in pyxem at the moment is peak finding. I think having:

<x,y| ragged>

is a far clearer than what we have at present. I'm not sure I have much more to add, it looks good!

@ericpre ericpre force-pushed the fix_ragged_array branch 2 times, most recently from c63bbb2 to 89ebab5 Compare October 30, 2021 19:58
@ericpre ericpre marked this pull request as ready for review October 30, 2021 19:59
Copy link
Contributor

@magnunor magnunor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing the functionality:


Signal1D and Signal2D

import numpy as np
import hyperspy.api as hs
data = np.zeros((2, 10), dtype=object)
s = hs.signals.Signal1D(data, ragged=True)
print(s) # <Signal1D, title: , dimensions: (2|10ragged)>
s = hs.signals.Signal2D(data, ragged=True)
print(s) # <Signal2D, title: , dimensions: (|10, 2ragged)>

Should Signal1D and Signal2D allowed to be ragged?


.map

import numpy as np
import hyperspy.api as hs
def afunction(image):
    rand = np.random.randint(2, 50)
    return image[0, :rand]
data = np.random.random((4, 6, 100, 100))
s = hs.signals.Signal2D(data)
s1 = s.map(afunction, ragged=True, inplace=False)
print(s1) # <BaseSignal, title: , dimensions: (6, 4|ragged)>
#########################
s_lazy = s.as_lazy()
s_lazy.data = s_lazy.data.rechunk((2, 3, 100, 100))
s1_lazy = s_lazy.map(afunction, ragged=True, inplace=False)
print(s1_lazy) # <LazySignal, title: , dimensions: (6, 4|)>

.map(..., ragged=True) for non-lazy signal returns a ragged signal. While a lazy signal does not. Not sure if this should be fixed in this pull request or not, since this will most likely be fixed in #2703 (non-lazy map improvements).

doc/user_guide/signal.rst Outdated Show resolved Hide resolved
doc/user_guide/signal.rst Outdated Show resolved Hide resolved
hyperspy/axes.py Show resolved Hide resolved
hyperspy/signal.py Outdated Show resolved Hide resolved
hyperspy/signal.py Outdated Show resolved Hide resolved
hyperspy/tests/signals/test_transpose.py Outdated Show resolved Hide resolved
@ericpre
Copy link
Member Author

ericpre commented Oct 31, 2021

Thanks @magnunor for the review, this should all good now, except the suggestion regarding american versus british english, which I have left as it is.

@magnunor
Copy link
Contributor

magnunor commented Nov 1, 2021

Some more points:

The error messages when using s.as_signal2D and s.as_signal1D could be improved a bit. Example:

import numpy as np
import hyperspy.api as hs
data = np.zeros((10, 15), dtype=object)
s = hs.signals.BaseSignal(data, ragged=True)
s.as_signal2D(image_axes=(0, 1)) # RuntimeError: Signal with ragged dimension can't be transposed.

This also extends to s.as_signal1D. The error should maybe say something like, "Ragged BaseSignals can not be converted to Signal2D"


Plotting of lazy ragged signals gives bad error:

import numpy as np
import hyperspy.api as hs
data = np.zeros((10, 15), dtype=object)
s = hs.signals.BaseSignal(data, ragged=True).as_lazy()
s.plot() # IndexError: list index out of range

As opposed to non-lazy signals: RuntimeError: Plotting ragged signal is not supported.

@ericpre
Copy link
Member Author

ericpre commented Nov 1, 2021

Some more points:

The error messages when using s.as_signal2D and s.as_signal1D could be improved a bit. Example:

import numpy as np
import hyperspy.api as hs
data = np.zeros((10, 15), dtype=object)
s = hs.signals.BaseSignal(data, ragged=True)
s.as_signal2D(image_axes=(0, 1)) # RuntimeError: Signal with ragged dimension can't be transposed.

This also extends to s.as_signal1D. The error should maybe say something like, "Ragged BaseSignals can not be converted to Signal2D"

I thought about it and I think that the current situation is better:

  • more useful for the user:
    • it is more accurate: it says where the actual issue is
    • it give a better explanation: it is not possible to do the conversion because it is not possible to transpose
  • in term in maintenance, it avoids introducing error message, which are not necessary - from the points above the error message is good enough.

Plotting of lazy ragged signals gives bad error:

import numpy as np
import hyperspy.api as hs
data = np.zeros((10, 15), dtype=object)
s = hs.signals.BaseSignal(data, ragged=True).as_lazy()
s.plot() # IndexError: list index out of range

As opposed to non-lazy signals: RuntimeError: Plotting ragged signal is not supported.

Done

@ericpre ericpre added run-extension-tests Run extension test suites and removed run-extension-tests Run extension test suites labels Nov 3, 2021
@ericpre
Copy link
Member Author

ericpre commented Nov 10, 2021

@magnunor, do you want to do another review?

@magnunor
Copy link
Contributor

I'll try to do another review during the weekend.

Copy link
Contributor

@magnunor magnunor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some minor comments. After those are addressed, the pull request should be good for merging!

doc/user_guide/signal.rst Outdated Show resolved Hide resolved
hyperspy/axes.py Show resolved Hide resolved
doc/user_guide/signal.rst Outdated Show resolved Hide resolved
@magnunor
Copy link
Contributor

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants