diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 00000000..95d305fe --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,39 @@ +{ + "title": "PyGSP: Graph Signal Processing in Python", + "description": "The PyGSP facilitates a wide variety of operations on graphs, like computing their Fourier basis, filtering or interpolating signals, plotting graphs, signals, and filters.", + "upload_type": "software", + "license": "BSD-3-Clause", + "access_right": "open", + "creators": [ + { + "name": "Micha\u00ebl Defferrard", + "affiliation": "EPFL", + "orcid": "0000-0002-6028-9024" + }, + { + "name": "Lionel Martin", + "affiliation": "EPFL" + }, + { + "name": "Rodrigo Pena", + "affiliation": "EPFL" + }, + { + "name": "Nathana\u00ebl Perraudin", + "affiliation": "EPFL", + "orcid": "0000-0001-8285-1308" + } + ], + "related_identifiers": [ + { + "scheme": "url", + "identifier": "https://github.com/epfl-lts2/pygsp", + "relation": "isSupplementTo" + }, + { + "scheme": "doi", + "identifier": "10.5281/zenodo.1003157", + "relation": "isPartOf" + } + ] +} diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index b112891c..63604dcf 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -57,6 +57,9 @@ Making a release Log in as the LTS2 user. #. Build and upload the distribution to the real PyPI with ``make release``. +#. Update the conda feedstock (at least the version number and sha256 in + ``recipe/meta.yaml``) by sending a PR to + `conda-forge `_. Repository organization ----------------------- diff --git a/README.rst b/README.rst index 99d27364..6b3cd061 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ PyGSP: Graph Signal Processing in Python :target: https://pygsp.readthedocs.io .. |pypi| image:: https://img.shields.io/pypi/v/pygsp.svg :target: https://pypi.python.org/pypi/PyGSP -.. |zenodo| image:: https://zenodo.org/badge/16276560.svg +.. |zenodo| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.1003157.svg :target: https://doi.org/10.5281/zenodo.1003157 .. |license| image:: https://img.shields.io/pypi/l/pygsp.svg :target: https://github.com/epfl-lts2/pygsp/blob/master/LICENSE.txt @@ -33,13 +33,11 @@ PyGSP: Graph Signal Processing in Python The PyGSP is a Python package to ease `Signal Processing on Graphs `_. -It is a free software, distributed under the BSD license, and -available on `PyPI `_. The documentation is available on `Read the Docs `_ and development takes place on `GitHub `_. -(A `Matlab counterpart `_ exists.) +A (mostly unmaintained) `Matlab version `_ exists. The PyGSP facilitates a wide variety of operations on graphs, like computing their Fourier basis, filtering or interpolating signals, plotting graphs, @@ -60,8 +58,15 @@ main objects of the package. >>> from pygsp import graphs, filters >>> G = graphs.Logo() ->>> G.estimate_lmax() ->>> g = filters.Heat(G, tau=100) +>>> G.compute_fourier_basis() # Fourier to plot the eigenvalues. +>>> # G.estimate_lmax() is otherwise sufficient. +>>> g = filters.Heat(G, tau=50) +>>> g.plot() + +.. image:: ../pygsp/data/readme_example_filter.png + :alt: +.. image:: pygsp/data/readme_example_filter.png + :alt: Let's now create a graph signal: a set of three Kronecker deltas for that example. We can now look at one step of heat diffusion by filtering the deltas @@ -73,11 +78,11 @@ structure! >>> s = np.zeros(G.N) >>> s[DELTAS] = 1 >>> s = g.filter(s) ->>> G.plot_signal(s, highlight=DELTAS, backend='matplotlib') +>>> G.plot_signal(s, highlight=DELTAS) -.. image:: ../pygsp/data/readme_example.png +.. image:: ../pygsp/data/readme_example_graph.png :alt: -.. image:: pygsp/data/readme_example.png +.. image:: pygsp/data/readme_example_graph.png :alt: You can @@ -86,7 +91,7 @@ look at the `tutorials `_ to learn how to use it, or look at the `reference guide `_ -for an exhaustive documentation of the API. Enjoy the package! +for an exhaustive documentation of the API. Enjoy! Installation ------------ @@ -115,6 +120,16 @@ research purpose at the `EPFL LTS2 laboratory `_. This project has been partly funded by the Swiss National Science Foundation under grant 200021_154350 "Towards Signal Processing on Graphs". +The code in this repository is released under the terms of the `BSD 3-Clause license `_. + If you are using the library for your research, for the sake of reproducibility, please cite the version you used as indexed by `Zenodo `_. +Or cite the generic concept as:: + + @misc{pygsp, + title = {PyGSP: Graph Signal Processing in Python}, + author = {Defferrard, Micha\"el and Martin, Lionel and Pena, Rodrigo and Perraudin, Nathana\"el}, + doi = {10.5281/zenodo.1003157}, + url = {https://github.com/epfl-lts2/pygsp/}, + } diff --git a/pygsp/data/readme_example_filter.png b/pygsp/data/readme_example_filter.png new file mode 100644 index 00000000..f97605d9 Binary files /dev/null and b/pygsp/data/readme_example_filter.png differ diff --git a/pygsp/data/readme_example.png b/pygsp/data/readme_example_graph.png similarity index 100% rename from pygsp/data/readme_example.png rename to pygsp/data/readme_example_graph.png diff --git a/pygsp/filters/abspline.py b/pygsp/filters/abspline.py index 11a81c0e..df9cd9b9 100644 --- a/pygsp/filters/abspline.py +++ b/pygsp/filters/abspline.py @@ -40,7 +40,7 @@ class Abspline(Filter): """ - def __init__(self, G, Nf=6, lpfactor=20, scales=None, **kwargs): + def __init__(self, G, Nf=6, lpfactor=20, scales=None): def kernel_abspline3(x, alpha, beta, t1, t2): M = np.array([[1, t1, t1**2, t1**3], @@ -98,4 +98,4 @@ def kernel_abspline3(x, alpha, beta, t1, t2): lminfac = .6 * G.lmin g[0] = lambda x: gamma_l * gl(x / lminfac) - super(Abspline, self).__init__(G, g, **kwargs) + super(Abspline, self).__init__(G, g) diff --git a/pygsp/filters/expwin.py b/pygsp/filters/expwin.py index 29f789a5..bff22cd0 100644 --- a/pygsp/filters/expwin.py +++ b/pygsp/filters/expwin.py @@ -33,7 +33,7 @@ class Expwin(Filter): """ - def __init__(self, G, bmax=0.2, a=1., **kwargs): + def __init__(self, G, bmax=0.2, a=1.): def fx(x, a): y = np.exp(-float(a)/x) @@ -53,4 +53,4 @@ def ffin(x, a): g = [lambda x: ffin(np.float64(x)/bmax/G.lmax, a)] - super(Expwin, self).__init__(G, g, **kwargs) + super(Expwin, self).__init__(G, g) diff --git a/pygsp/filters/filter.py b/pygsp/filters/filter.py index 472e07cb..b4d9a6e4 100644 --- a/pygsp/filters/filter.py +++ b/pygsp/filters/filter.py @@ -31,10 +31,6 @@ class Filter(object): G : Graph The graph to which the filter bank was tailored. It is a reference to the graph passed when instantiating the class. - kernels : function or list of functions - A (list of) function defining the filter bank. One function per filter. - Either passed by the user when instantiating the base class, either - constructed by the derived classes. Nf : int Number of filters in the filter bank. @@ -93,8 +89,8 @@ def evaluate(self, x): """ # Avoid to copy data as with np.array([g(x) for g in self._kernels]). y = np.empty((self.Nf, len(x))) - for i, g in enumerate(self._kernels): - y[i] = g(x) + for i, kernel in enumerate(self._kernels): + y[i] = kernel(x) return y def filter(self, s, method='chebyshev', order=30): diff --git a/pygsp/filters/halfcosine.py b/pygsp/filters/halfcosine.py index 9b5604ef..386035b7 100644 --- a/pygsp/filters/halfcosine.py +++ b/pygsp/filters/halfcosine.py @@ -31,7 +31,7 @@ class HalfCosine(Filter): """ - def __init__(self, G, Nf=6, **kwargs): + def __init__(self, G, Nf=6): if Nf <= 2: raise ValueError('The number of filters must be higher than 2.') @@ -45,4 +45,4 @@ def __init__(self, G, Nf=6, **kwargs): for i in range(Nf): g.append(lambda x, ind=i: main_window(x - dila_fact/3. * (ind - 2))) - super(HalfCosine, self).__init__(G, g, **kwargs) + super(HalfCosine, self).__init__(G, g) diff --git a/pygsp/filters/heat.py b/pygsp/filters/heat.py index 1ec81822..dbe0e5da 100644 --- a/pygsp/filters/heat.py +++ b/pygsp/filters/heat.py @@ -61,7 +61,7 @@ class Heat(Filter): """ - def __init__(self, G, tau=10, normalize=False, **kwargs): + def __init__(self, G, tau=10, normalize=False): try: iter(tau) @@ -76,4 +76,4 @@ def kernel(x, t): norm = np.linalg.norm(kernel(G.e, t)) if normalize else 1 g.append(lambda x, t=t, norm=norm: kernel(x, t) / norm) - super(Heat, self).__init__(G, g, **kwargs) + super(Heat, self).__init__(G, g) diff --git a/pygsp/filters/held.py b/pygsp/filters/held.py index bd224f50..a9126270 100644 --- a/pygsp/filters/held.py +++ b/pygsp/filters/held.py @@ -45,7 +45,7 @@ class Held(Filter): """ - def __init__(self, G, a=2./3, **kwargs): + def __init__(self, G, a=2./3): g = [lambda x: held(x * (2./G.lmax), a)] g.append(lambda x: np.real(np.sqrt(1 - (held(x * (2./G.lmax), a)) @@ -67,4 +67,4 @@ def held(val, a): return y - super(Held, self).__init__(G, g, **kwargs) + super(Held, self).__init__(G, g) diff --git a/pygsp/filters/itersine.py b/pygsp/filters/itersine.py index 38f558d1..075ba3b0 100644 --- a/pygsp/filters/itersine.py +++ b/pygsp/filters/itersine.py @@ -35,7 +35,7 @@ class Itersine(Filter): >>> G.plot_signal(s, ax=axes[1]) """ - def __init__(self, G, Nf=6, overlap=2., **kwargs): + def __init__(self, G, Nf=6, overlap=2.): def k(x): return np.sin(0.5*np.pi*np.power(np.cos(x*np.pi), 2)) * ((x >= -0.5)*(x <= 0.5)) @@ -46,4 +46,4 @@ def k(x): for i in range(1, Nf + 1): g.append(lambda x, ind=i: k(x/scale - (ind - overlap/2.)/overlap) / np.sqrt(overlap)*np.sqrt(2)) - super(Itersine, self).__init__(G, g, **kwargs) + super(Itersine, self).__init__(G, g) diff --git a/pygsp/filters/mexicanhat.py b/pygsp/filters/mexicanhat.py index b58ca2fe..9426e3fc 100644 --- a/pygsp/filters/mexicanhat.py +++ b/pygsp/filters/mexicanhat.py @@ -55,8 +55,7 @@ class MexicanHat(Filter): """ - def __init__(self, G, Nf=6, lpfactor=20, scales=None, normalize=False, - **kwargs): + def __init__(self, G, Nf=6, lpfactor=20, scales=None, normalize=False): lmin = G.lmax / lpfactor @@ -82,4 +81,4 @@ def kernel(x, i=i): kernels.append(kernel) - super(MexicanHat, self).__init__(G, kernels, **kwargs) + super(MexicanHat, self).__init__(G, kernels) diff --git a/pygsp/filters/meyer.py b/pygsp/filters/meyer.py index 7252e8c2..9466d0e8 100644 --- a/pygsp/filters/meyer.py +++ b/pygsp/filters/meyer.py @@ -42,7 +42,7 @@ class Meyer(Filter): """ - def __init__(self, G, Nf=6, scales=None, **kwargs): + def __init__(self, G, Nf=6, scales=None): if scales is None: scales = (4./(3 * G.lmax)) * np.power(2., np.arange(Nf-2, -1, -1)) @@ -90,4 +90,4 @@ def v(x): return r - super(Meyer, self).__init__(G, g, **kwargs) + super(Meyer, self).__init__(G, g) diff --git a/pygsp/filters/papadakis.py b/pygsp/filters/papadakis.py index 94f29821..2397a2e6 100644 --- a/pygsp/filters/papadakis.py +++ b/pygsp/filters/papadakis.py @@ -40,7 +40,7 @@ class Papadakis(Filter): >>> G.plot_signal(s, ax=axes[1]) """ - def __init__(self, G, a=0.75, **kwargs): + def __init__(self, G, a=0.75): g = [lambda x: papadakis(x * (2./G.lmax), a)] g.append(lambda x: np.real(np.sqrt(1 - (papadakis(x*(2./G.lmax), a)) ** @@ -61,4 +61,4 @@ def papadakis(val, a): return y - super(Papadakis, self).__init__(G, g, **kwargs) + super(Papadakis, self).__init__(G, g) diff --git a/pygsp/filters/regular.py b/pygsp/filters/regular.py index 85c85987..21473cf9 100644 --- a/pygsp/filters/regular.py +++ b/pygsp/filters/regular.py @@ -48,7 +48,7 @@ class Regular(Filter): >>> G.plot_signal(s, ax=axes[1]) """ - def __init__(self, G, d=3, **kwargs): + def __init__(self, G, d=3): g = [lambda x: regular(x * (2./G.lmax), d)] g.append(lambda x: np.real(np.sqrt(1 - (regular(x * (2./G.lmax), d)) @@ -65,4 +65,4 @@ def regular(val, d): return np.sin(np.pi / 4.*(1 + output)) - super(Regular, self).__init__(G, g, **kwargs) + super(Regular, self).__init__(G, g) diff --git a/pygsp/filters/simoncelli.py b/pygsp/filters/simoncelli.py index 564197c9..07a82edd 100644 --- a/pygsp/filters/simoncelli.py +++ b/pygsp/filters/simoncelli.py @@ -41,7 +41,7 @@ class Simoncelli(Filter): """ - def __init__(self, G, a=2./3, **kwargs): + def __init__(self, G, a=2./3): g = [lambda x: simoncelli(x * (2./G.lmax), a)] g.append(lambda x: np.real(np.sqrt(1 - @@ -63,4 +63,4 @@ def simoncelli(val, a): return y - super(Simoncelli, self).__init__(G, g, **kwargs) + super(Simoncelli, self).__init__(G, g) diff --git a/pygsp/filters/simpletight.py b/pygsp/filters/simpletight.py index 5a2dcc15..402a44aa 100644 --- a/pygsp/filters/simpletight.py +++ b/pygsp/filters/simpletight.py @@ -42,7 +42,7 @@ class SimpleTight(Filter): """ - def __init__(self, G, Nf=6, scales=None, **kwargs): + def __init__(self, G, Nf=6, scales=None): def kernel(x, kerneltype): r""" @@ -98,4 +98,4 @@ def h(x): for i in range(Nf - 1): g.append(lambda x, i=i: kernel(scales[i] * x, 'wavelet')) - super(SimpleTight, self).__init__(G, g, **kwargs) + super(SimpleTight, self).__init__(G, g)