diff --git a/pygsp/graphs/nngraphs/bunny.py b/pygsp/graphs/nngraphs/bunny.py index cc269c10..e63caacd 100644 --- a/pygsp/graphs/nngraphs/bunny.py +++ b/pygsp/graphs/nngraphs/bunny.py @@ -19,7 +19,7 @@ class Bunny(NNGraph): >>> ax1 = fig.add_subplot(121) >>> ax2 = fig.add_subplot(122, projection='3d') >>> _ = ax1.spy(G.W, markersize=0.1) - >>> _ = _ = G.plot(ax=ax2) + >>> _ = G.plot(ax=ax2) """ diff --git a/pygsp/graphs/nngraphs/sphere.py b/pygsp/graphs/nngraphs/sphere.py index 61ee3fc4..d472d536 100644 --- a/pygsp/graphs/nngraphs/sphere.py +++ b/pygsp/graphs/nngraphs/sphere.py @@ -7,26 +7,50 @@ class Sphere(NNGraph): - r"""Randomly sampled hypersphere. + r"""Random uniform sampling of an hypersphere. Parameters ---------- N : int Number of vertices (default = 300). dim : int - Dimensionality of the space the hypersphere is embedded in (default = 3). + Dimension of the space the hypersphere is embedded in. seed : int Seed for the random number generator (for reproducible graphs). + kwargs : dict + Additional keyword parameters are passed to :class:`NNGraph`. + + Attributes + ---------- + signals : dict + Vertex position as latitude ``'lat'`` in [-π/2,π/2] and longitude + ``'lon'`` in [0,2π[. + + See Also + -------- + SphereEquiangular, SphereGaussLegendre : based on quadrature theorems + SphereIcosahedron, SphereHealpix : based on subdivided polyhedra + CubeRandom : randomly sampled cube + + References + ---------- + .. [1] http://mathworld.wolfram.com/HyperspherePointPicking.html + .. [2] J. S. Hicks and R. F. Wheeling, An Efficient Method for Generating + Uniformly Distributed Points on the Surface of an n-Dimensional Sphere, + 1959. Examples -------- >>> import matplotlib.pyplot as plt >>> G = graphs.Sphere(100, seed=42) >>> fig = plt.figure() - >>> ax1 = fig.add_subplot(121) - >>> ax2 = fig.add_subplot(122, projection='3d') + >>> ax1 = fig.add_subplot(131) + >>> ax2 = fig.add_subplot(132, projection='3d') + >>> ax3 = fig.add_subplot(133) >>> _ = ax1.spy(G.W, markersize=1.5) - >>> _ = _ = G.plot(ax=ax2) + >>> _ = G.plot(ax=ax2) + >>> G.set_coordinates('sphere', dim=2) + >>> _ = G.plot(ax=ax3, indices=True) """ diff --git a/pygsp/graphs/nngraphs/spheregausslegendre.py b/pygsp/graphs/nngraphs/spheregausslegendre.py index bc1e17ec..9f699297 100644 --- a/pygsp/graphs/nngraphs/spheregausslegendre.py +++ b/pygsp/graphs/nngraphs/spheregausslegendre.py @@ -7,7 +7,71 @@ class SphereGaussLegendre(NNGraph): - r"""Sphere sampled with a Gauss-Legendre scheme. + r"""Sphere sampled with a Gauss–Legendre scheme. + + Background information is found at :doc:`/background/spherical_samplings`. + + Parameters + ---------- + nlat : int + Number of isolatitude (longitudinal) rings. + reduced : {False, 'ecmwf-octahedral'} + If ``False``, there are ``2*nlat`` pixels per ring. + If ``'ecmwf-octahedral'``, there are ``4*i+16`` pixels per ring, where + ``i`` is the ring number from 1 (nearest to the poles) to ``nlat/2`` + (nearest to the equator). + kwargs : dict + Additional keyword parameters are passed to :class:`NNGraph`. + + Attributes + ---------- + signals : dict + Vertex position as latitude ``'lat'`` in [-π/2,π/2] and longitude + ``'lon'`` in [0,2π[. + + See Also + -------- + SphereEquiangular : based on quadrature theorems + SphereIcosahedron, SphereHealpix : based on subdivided polyhedra + SphereRandom : random uniform sampling + + Notes + ----- + Edge weights are computed by :class:`NNGraph`. Gaussian kernel widths have + however not been optimized for convolutions on the resulting graph to be + maximally equivariant to rotation [8]_. + + The ECMWF's Integrated Forecast System (IFS) O320 grid is instantiated as + ``SphereGaussLegendre(640, reduced='ecmwf-octahedral')`` [6]_ [7]_. + + References + ---------- + .. [1] M. H. Payne, Truncation effects in geopotential modelling, 1971. + .. [2] A. G. Doroshkevich et al., Gauss–Legendre sky pixelization (GLESP) + for CMB maps, 2005. + .. [3] N Schaeffer, Efficient spherical harmonic transforms aimed at + pseudospectral numerical simulations, 2013. + .. [4] J. Keiner and D. Potts, Fast evaluation of quadrature formulae on + the sphere, 2008. + .. [5] M. Hortal and A. J. Simmons, Use of reduced Gaussian grids in + spectral models, 1991. + .. [6] https://confluence.ecmwf.int/display/FCST/Introducing+the+octahedral+reduced+Gaussian+grid + .. [7] https://confluence.ecmwf.int/display/OIFS/4.2+OpenIFS%3A+Octahedral+grid + .. [8] M. Defferrard et al., DeepSphere: a graph-based spherical CNN, 2019. + + Examples + -------- + >>> import matplotlib.pyplot as plt + >>> G = graphs.SphereGaussLegendre() + >>> fig = plt.figure() + >>> ax1 = fig.add_subplot(131) + >>> ax2 = fig.add_subplot(132, projection='3d') + >>> ax3 = fig.add_subplot(133) + >>> _ = ax1.spy(G.W, markersize=1.5) + >>> _ = G.plot(ax=ax2) + >>> G.set_coordinates('sphere', dim=2) + >>> _ = G.plot(ax=ax3, indices=True) + """ def __init__(self, nlat=4, reduced=False, **kwargs): diff --git a/pygsp/graphs/nngraphs/spherehealpix.py b/pygsp/graphs/nngraphs/spherehealpix.py index ce2da419..803afb0c 100644 --- a/pygsp/graphs/nngraphs/spherehealpix.py +++ b/pygsp/graphs/nngraphs/spherehealpix.py @@ -17,21 +17,15 @@ def _import_hp(): class SphereHealpix(NNGraph): - r"""Sphere sampled with an HEALPix scheme. + r"""Sphere sampled with the HEALPix scheme. - The Hierarchical Equal Area isoLatitude Pixelisation (HEALPix) [1]_ is a - sampling scheme for the sphere whose pixels - (1) have equal area, for white noise to remain white, - (2) are arranged on isolatitude rings, for an FFT to be computed per ring, - (3) is hierarchical, where each pixel is sub-divided into four pixels. - HEALPix is used in cosmology for cosmic microwave background (CMB) maps. + Background information is found at :doc:`/background/spherical_samplings`. Parameters ---------- - nside : int - Controls the resolution of the sampling. It must be a power of 2. - The number of pixels is ``12*npix**2``, and the number of pixels around - the equator is ``4*nside``. + subdivisions : int + Number of recursive subdivisions. Known as ``order`` in HEALPix + terminology, with ``nside=2**order`` and ``npix=12*4**order``. indexes : array_like of int Indexes of the pixels from which to build a graph. Useful to build a graph from a subset of the pixels, e.g., for partial sky observations. @@ -40,49 +34,59 @@ class SphereHealpix(NNGraph): kwargs : dict Additional keyword parameters are passed to :class:`NNGraph`. + Attributes + ---------- + signals : dict + Vertex position as latitude ``'lat'`` in [-π/2,π/2] and longitude + ``'lon'`` in [0,2π[. + See Also -------- - SphereEquiangular, SphereIcosahedron + SphereEquiangular, SphereGaussLegendre : based on quadrature theorems + SphereIcosahedron : based on subdivided polyhedra + SphereRandom : random uniform sampling Notes ----- Edge weights are computed by :class:`NNGraph`. Gaussian kernel widths have - been optimized for some combinations of resolutions `nside` and number of + been optimized for some combinations of resolutions `nlat` and number of neighbors `k` for the convolutions on the resulting graph to be maximally equivariant to rotation [2]_. References ---------- - .. [1] Gorski K. M., et al., "HEALPix: a Framework for High Resolution - Discretization and Fast Analysis of Data Distributed on the Sphere", The - Astrophysical Journal, 2005. - .. [2] Defferrard, Michaël, et al., "DeepSphere: a graph-based spherical - CNN", International Conference on Learning Representations (ICLR), 2019. + .. [1] K. M. Gorski et al., HEALPix: a Framework for High Resolution + Discretization and Fast Analysis of Data Distributed on the Sphere, + 2005. + .. [2] M. Defferrard et al., DeepSphere: a graph-based spherical CNN, 2019. Examples -------- >>> import matplotlib.pyplot as plt - >>> G = graphs.SphereHealpix() + >>> G = graphs.SphereHealpix(1, k=8) >>> fig = plt.figure() - >>> ax1 = fig.add_subplot(121) - >>> ax2 = fig.add_subplot(122, projection='3d') + >>> ax1 = fig.add_subplot(131) + >>> ax2 = fig.add_subplot(132, projection='3d') + >>> ax3 = fig.add_subplot(133) >>> _ = ax1.spy(G.W, markersize=1.5) - >>> _ = _ = G.plot(ax=ax2) + >>> _ = G.plot(ax=ax2) + >>> G.set_coordinates('sphere', dim=2) + >>> _ = G.plot(ax=ax3, indices=True) Vertex orderings: >>> import matplotlib.pyplot as plt >>> fig, axes = plt.subplots(1, 2) - >>> graph = graphs.SphereHealpix(nside=2, nest=False, k=8) - >>> graph.coords = np.stack([graph.signals['lon'], graph.signals['lat']]).T - >>> graph.plot(indices=True, ax=axes[0], title='RING ordering') - >>> graph = graphs.SphereHealpix(nside=2, nest=True, k=8) - >>> graph.coords = np.stack([graph.signals['lon'], graph.signals['lat']]).T - >>> graph.plot(indices=True, ax=axes[1], title='NESTED ordering') + >>> graph = graphs.SphereHealpix(1, nest=False, k=8) + >>> graph.set_coordinates('sphere', dim=2) + >>> _ = graph.plot(indices=True, ax=axes[0], title='RING ordering') + >>> graph = graphs.SphereHealpix(1, nest=True, k=8) + >>> graph.set_coordinates('sphere', dim=2) + >>> _ = graph.plot(indices=True, ax=axes[1], title='NESTED ordering') """ - def __init__(self, subdivisions=2, indexes=None, nest=False, **kwargs): + def __init__(self, subdivisions=1, indexes=None, nest=False, **kwargs): hp = _import_hp() nside = hp.order2nside(subdivisions) diff --git a/pygsp/graphs/nngraphs/sphereicosahedron.py b/pygsp/graphs/nngraphs/sphereicosahedron.py index 471e0553..6aff6465 100644 --- a/pygsp/graphs/nngraphs/sphereicosahedron.py +++ b/pygsp/graphs/nngraphs/sphereicosahedron.py @@ -19,38 +19,71 @@ def _import_trimesh(): class SphereIcosahedron(NNGraph): - r"""Spherical-shaped graph based on the projection of the icosahedron (NN-graph). - Code inspired by Max Jiang [https://github.com/maxjiang93/ugscnn/blob/master/meshcnn/mesh.py] + r"""Sphere sampled as a subdivided icosahedron. + + Background information is found at :doc:`/background/spherical_samplings`. Parameters ---------- - level : int - Resolution of the sampling scheme, or how many times the faces are divided (default = 5) - sampling : string - What the pixels represent. Either a vertex or a face (default = 'vertex') + subdivisions : int + Number of recursive subdivisions. + dual : bool + Whether the graph vertices correspond to the vertices (``dual=False``) + or the triangular faces (``dual=True``) of the subdivided icosahedron. + kwargs : dict + Additional keyword parameters are passed to :class:`NNGraph`. + + Attributes + ---------- + signals : dict + Vertex position as latitude ``'lat'`` in [-π/2,π/2] and longitude + ``'lon'`` in [0,2π[. See Also -------- - SphereDodecahedron, SphereHealpix, SphereEquiangular + SphereEquiangular, SphereGaussLegendre : based on quadrature theorems + SphereHealpix : based on subdivided polyhedra + SphereRandom : random uniform sampling Notes - ------ - The icosahedron is the dual of the dodecahedron. Thus the pixels in this graph represent either the vertices \ - of the icosahedron, or the faces of the dodecahedron. + ----- + Edge weights are computed by :class:`NNGraph`. Gaussian kernel widths have + however not been optimized for convolutions on the resulting graph to be + maximally equivariant to rotation [3]_. + + References + ---------- + .. [1] https://sinestesia.co/blog/tutorials/python-icospheres/ + .. [2] M. Tegmark, An icosahedron-based method for pixelizing the celestial + sphere, 1996. + .. [3] M. Defferrard et al., DeepSphere: a graph-based spherical CNN, 2019. Examples -------- >>> import matplotlib.pyplot as plt - >>> from mpl_toolkits.mplot3d import Axes3D - >>> G = graphs.SphereIcosahedron(level=1) + >>> G = graphs.SphereIcosahedron() >>> fig = plt.figure() - >>> ax1 = fig.add_subplot(121) - >>> ax2 = fig.add_subplot(122, projection='3d') + >>> ax1 = fig.add_subplot(131) + >>> ax2 = fig.add_subplot(132, projection='3d') + >>> ax3 = fig.add_subplot(133) >>> _ = ax1.spy(G.W, markersize=1.5) - >>> _ = _ = G.plot(ax=ax2) + >>> _ = G.plot(ax=ax2) + >>> G.set_coordinates('sphere', dim=2) + >>> _ = G.plot(ax=ax3, indices=True) + + Primal and dual polyhedrons: + + >>> import matplotlib.pyplot as plt + >>> fig, axes = plt.subplots(1, 2) + >>> graph = graphs.SphereIcosahedron(0, dual=False, k=5) + >>> graph.set_coordinates('sphere', dim=2) + >>> _ = graph.plot(indices=True, ax=axes[0], title='Icosahedron') + >>> graph = graphs.SphereIcosahedron(0, dual=True, k=3) + >>> graph.set_coordinates('sphere', dim=2) + >>> _ = graph.plot(indices=True, ax=axes[1], title='Dodecahedron') """ - def __init__(self, subdivisions=2, dual=False, **kwargs): + def __init__(self, subdivisions=1, dual=False, **kwargs): self.subdivisions = subdivisions self.dual = dual diff --git a/pygsp/graphs/sphereequiangular.py b/pygsp/graphs/sphereequiangular.py index 9206814f..46dbbb2e 100644 --- a/pygsp/graphs/sphereequiangular.py +++ b/pygsp/graphs/sphereequiangular.py @@ -8,78 +8,79 @@ class SphereEquiangular(Graph): - r"""Spherical-shaped graph using equirectangular sampling scheme. + r"""Sphere sampled with an equiangular scheme. + + Background information is found at :doc:`/background/spherical_samplings`. Parameters ---------- - bandwidth : int or list or tuple - Resolution of the sampling scheme, corresponding to the bandwidth (latitude, latitude). - Use a list or tuple to have a different resolution for latitude and longitude (default = 64) - sampling : {'Driscoll-Heally', 'SOFT', 'Clenshaw-Curtis', 'Gauss-Legendre'} - sampling scheme (default = 'SOFT') - * Driscoll-Healy is the original sampling scheme of the sphere - * SOFT is an upgraded version without the poles - * Clenshaw-Curtis use the quadrature of its name to find the position of the latitude rings - * Gauss-Legendre use the quadrature of its name to find the position of the latitude rings - * Optimal Dimensionality guarranty the use of a minimum number of pixels, different for each latitude ring + size : int or (int, int) + Size of the discretization in latitude and longitude ``(nlat, nlon)``. + ``nlat`` is the number of isolatitude (longitudinal) rings. + ``nlon`` is the number of vertices (pixels) per ring. + The total number of vertices is ``nlat*nlon``. + ``nlat=nlon`` if only one number is given. + poles : {0, 1, 2} + Whether to sample 0, 1, or the 2 poles: + 0: nearest rings at ``dlat/2`` from both poles (``dlat=π/nlat``), + 1: ring at a pole, ring at ``dlat`` from other pole (``dlat=π/nlat``), + 2: rings at both poles (``dlat=π/(nlat-1)``). + + Attributes + ---------- + signals : dict + Vertex position as latitude ``'lat'`` in [-π/2,π/2] and longitude + ``'lon'`` in [0,2π[. See Also -------- - SphereHealpix, SphereIcosahedron + SphereGaussLegendre : based on quadrature theorems + SphereIcosahedron, SphereHealpix : based on subdivided polyhedra + SphereRandom : random uniform sampling Notes ------ - Driscoll-Heally is the original sampling scheme of the sphere [1] - SOFT is an updated sampling scheme, without the poles[2] - Clenshaw-Curtis is [3] - Gauss-Legendre is [4] - The weight matrix is designed following [5]_ + Edge weights are computed as the reciprocal of the distances between + vertices [6]_. This yields a graph convolution that is equivariant to + longitudinal and latitudinal rotations, but not general rotations [7]_. References ---------- - [1] J. R. Driscoll et D. M. Healy, « Computing Fourier Transforms and Convolutions on the 2-Sphere », - Advances in Applied Mathematics, vol. 15, no. 2, pp. 202‑250, June 1994. - [2] D. M. Healy, D. N. Rockmore, P. J. Kostelec, et S. Moore, « FFTs for the 2-Sphere-Improvements - and Variations », Journal of Fourier Analysis and Applications, vol. 9, no. 4, pp. 341‑385, Jul. 2003. - [3] D. Hotta and M. Ujiie, ‘A nestable, multigrid-friendly grid on a sphere for global spectral models - based on Clenshaw-Curtis quadrature’, Q J R Meteorol Soc, vol. 144, no. 714, pp. 1382–1397, Jul. 2018. - [4] J. Keiner et D. Potts, « Fast evaluation of quadrature formulae on the sphere », - Math. Comp., vol. 77, no. 261, pp. 397‑419, Jan. 2008. - [5] P. Frossard and R. Khasanova, ‘Graph-Based Classification of Omnidirectional Images’, - in 2017 IEEE International Conference on Computer Vision Workshops (ICCVW), Venice, Italy, 2017, pp. 860–869. - [6] Z. Khalid, R. A. Kennedy, et J. D. McEwen, « An Optimal-Dimensionality Sampling Scheme - on the Sphere with Fast Spherical Harmonic Transforms », IEEE Transactions on Signal Processing, - vol. 62, no. 17, pp. 4597‑4610, Sept. 2014. + .. [1] J. R. Driscoll et D. M. Healy, Computing Fourier Transforms and + Convolutions on the 2-Sphere, 1994. + .. [2] D. M. Healy et al., FFTs for the 2-Sphere-Improvements and + Variations, 2003. + .. [3] W. Skukowsky, A quadrature formula over the sphere with application + to high resolution spherical harmonic analysis, 1986. + .. [4] J. Keiner and D. Potts, Fast evaluation of quadrature formulae on + the sphere, 2008. + .. [5] J. D. McEwen and Y. Wiaux, A novel sampling theorem on the sphere, + 2011. + .. [6] R. Khasanova and P. Frossard, Graph-Based Classification of + Omnidirectional Images, 2017. + .. [7] M. Defferrard et al., DeepSphere: a graph-based spherical CNN, 2019. Examples -------- >>> import matplotlib.pyplot as plt - >>> G1 = graphs.SphereEquiangular(bandwidth=6, sampling='Driscoll-Healy') - >>> G2 = graphs.SphereEquiangular(bandwidth=6, sampling='SOFT') - >>> G3 = graphs.SphereEquiangular(bandwidth=6, sampling='Clenshaw-Curtis') - >>> G4 = graphs.SphereEquiangular(bandwidth=6, sampling='Gauss-Legendre') + >>> G = graphs.SphereEquiangular() + >>> fig = plt.figure() + >>> ax1 = fig.add_subplot(131) + >>> ax2 = fig.add_subplot(132, projection='3d') + >>> ax3 = fig.add_subplot(133) + >>> _ = ax1.spy(G.W, markersize=1.5) + >>> _ = G.plot(ax=ax2) + >>> G.set_coordinates('sphere', dim=2) + >>> _ = G.plot(ax=ax3, indices=True) + + Sampling of the poles: + + >>> import matplotlib.pyplot as plt >>> fig = plt.figure() - >>> plt.subplots_adjust(wspace=1.) - >>> ax1 = fig.add_subplot(221, projection='3d') - >>> ax2 = fig.add_subplot(222, projection='3d') - >>> ax3 = fig.add_subplot(223, projection='3d') - >>> ax4 = fig.add_subplot(224, projection='3d') - >>> _ = G1.plot(ax=ax1, title='Driscoll-Healy', vertex_size=10) - >>> _ = G2.plot(ax=ax2, title='SOFT', vertex_size=10) - >>> _ = G3.plot(ax=ax3, title='Clenshaw-Curtis', vertex_size=10) - >>> _ = G4.plot(ax=ax4, title='Gauss-Legendre', vertex_size=10) - >>> ax1.set_xlim([0, 1]) - >>> ax1.set_ylim([-1, 0.]) - >>> ax1.set_zlim([0.5, 1.]) - >>> ax2.set_xlim([0, 1]) - >>> ax2.set_ylim([-1, 0.]) - >>> ax2.set_zlim([0.5, 1.]) - >>> ax3.set_xlim([0, 1]) - >>> ax3.set_ylim([-1, 0.]) - >>> ax3.set_zlim([0.5, 1.]) - >>> ax4.set_xlim([0, 1]) - >>> ax4.set_ylim([-1, 0.]) - >>> ax4.set_zlim([0.5, 1.]) + >>> for i in range(3): + ... graph = graphs.SphereEquiangular(poles=i) + ... ax = fig.add_subplot(1, 3, i+1, projection='3d') + ... _ = graph.plot(title=f'poles={i}', ax=ax) """ def __init__(self, size=(4, 8), poles=0, **kwargs):