{"action":"created","comment":{"url":"https://api.github.com/repos/odlgroup/odl/pulls/comments/82864624","pull_request_review_id":3741658,"id":82864624,"diff_hunk":"@@ -0,0 +1,799 @@\n+# Copyright 2014-2016 The ODL development group\n+#\n+# This file is part of ODL.\n+#\n+# ODL is free software: you can redistribute it and/or modify\n+# it under the terms of the GNU General Public License as published by\n+# the Free Software Foundation, either version 3 of the License, or\n+# (at your option) any later version.\n+#\n+# ODL is distributed in the hope that it will be useful,\n+# but WITHOUT ANY WARRANTY; without even the implied warranty of\n+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+# GNU General Public License for more details.\n+#\n+# You should have received a copy of the GNU General Public License\n+# along with ODL.  If not, see <http://www.gnu.org/licenses/>.\n+\n+\"\"\"Bindings to the PyWavelets backend for wavelet transforms.\n+\n+`PyWavelets <https://pywavelets.readthedocs.io/>`_ is a Python library\n+for wavelet transforms in arbitrary dimensions, featuring a large number\n+of built-in wavelet filters.\n+\"\"\"\n+\n+# Imports for common Python 2/3 codebase\n+from __future__ import print_function, division, absolute_import\n+from future import standard_library\n+standard_library.install_aliases()\n+\n+from itertools import product\n+import numpy as np\n+\n+try:\n+    import pywt\n+    PYWT_AVAILABLE = True\n+except ImportError:\n+    PYWT_AVAILABLE = False\n+\n+\n+__all__ = ('PAD_MODES_ODL2PYWT', 'PYWT_SUPPORTED_MODES', 'PYWT_AVAILABLE',\n+           'pywt_wavelet', 'pywt_pad_mode', 'pywt_coeff_shapes',\n+           'pywt_flat_coeff_size',\n+           'pywt_flat_array_from_coeffs', 'pywt_coeffs_from_flat_array',\n+           'pywt_single_level_decomp', 'pywt_single_level_recon',\n+           'pywt_multi_level_decomp', 'pywt_multi_level_recon')\n+\n+\n+PAD_MODES_ODL2PYWT = {'constant': 'zpd',\n+                      'periodic': 'ppd',\n+                      'symmetric': 'sym',\n+                      'order0': 'cpd',\n+                      'order1': 'sp1',\n+                      'pywt_periodic': 'per'}\n+PYWT_SUPPORTED_MODES = PAD_MODES_ODL2PYWT.values()\n+\n+\n+def pywt_wavelet(wavelet):\n+    \"\"\"Convert ``wavelet`` to a `pywt.Wavelet` instance.\"\"\"\n+    if isinstance(wavelet, pywt.Wavelet):\n+        return wavelet\n+    else:\n+        return pywt.Wavelet(wavelet)\n+\n+\n+def pywt_pad_mode(pad_mode, pad_const=0):\n+    \"\"\"Convert ODL-style padding mode to pywt-style padding mode.\"\"\"\n+    pad_mode = str(pad_mode).lower()\n+    if pad_mode in PYWT_SUPPORTED_MODES:\n+        return pad_mode\n+    elif pad_mode == 'constant' and pad_const != 0.0:\n+        raise ValueError('constant padding with constant != 0 not supported '\n+                         'for `pywt` back-end')\n+    else:\n+        try:\n+            return PAD_MODES_ODL2PYWT[pad_mode]\n+        except KeyError:\n+            raise ValueError(\"`pad_mode` '{}' not understood\".format(pad_mode))\n+\n+\n+def _check_nlevels(nlevels, data_len, wavelet):\n+    \"\"\"Check if ``nlevels`` is valid for given data length and wavelet.\"\"\"\n+    max_levels = pywt.dwt_max_level(data_len, wavelet.dec_len)\n+    if max_levels == 0:\n+        raise ValueError('data size {} too small for transform, results '\n+                         'in maximal `nlevels` of 0'.format(data_len))\n+    if not 0 < nlevels <= max_levels:\n+        raise ValueError('`nlevels` must satisfy 0 < nlevels <= {}, got {}'\n+                         ''.format(max_levels, nlevels))\n+\n+\n+def pywt_coeff_shapes(shape, wavelet, nlevels, mode):\n+    \"\"\"Return a list of coefficient shapes in the specified transform.\n+\n+    The wavelet transform specified by ``wavelet``, ``nlevels`` and\n+    ``mode`` produces a sequence of approximation and detail\n+    coefficients. This function computes the shape of those\n+    coefficient arrays and returns them as a list.\n+\n+    Parameters\n+    ----------\n+    shape : sequence\n+        Shape of an input to the transform.\n+    wavelet : string or `pywt.Wavelet`\n+        Specification of the wavelet to be used in the transform.\n+        If a string is given, it is converted to a `pywt.Wavelet`.\n+        Use `pywt.wavelist` to get a list of available wavelets.\n+    nlevels : positive int\n+        Number of scaling levels to be used in the decomposition. The\n+        maximum number of levels can be calculated with\n+        `pywt.dwt_max_level`.\n+    mode : string, optional\n+        PyWavelets style signal extension mode. See `signal extension modes`_\n+        for available options.\n+\n+    Returns\n+    -------\n+    shapes : list\n+        The shapes of the approximation and detail coefficients at\n+        different scaling levels in the following order:\n+\n+            ``[shape_aN, shape_DN, ..., shape_D1]``\n+\n+        Here, ``shape_aN`` is the shape of the N-th level approximation\n+        coefficient array, and ``shape_Di`` is the shape of all\n+        i-th level detail coefficients.\n+\n+    See Also\n+    --------\n+    pywt.dwt_coeff_len : function used to determine coefficient sizes at\n+        each scaling level\n+\n+    Examples\n+    --------\n+    Determine the coefficient shapes for 2 scaling levels in 3 dimensions\n+    with zero-padding. Approximation coefficient shape comes first, then\n+    the level-2 detail coefficient and last the level-1 detail coefficient:\n+\n+    >>> pywt_coeff_shapes(shape=(16, 17, 18), wavelet='db2', nlevels=2,\n+    ...                   mode='zpd')\n+    [(6, 6, 6), (6, 6, 6), (9, 10, 10)]\n+\n+    References\n+    ----------\n+    .. _signal extension modes:\n+       https://pywavelets.readthedocs.io/en/latest/ref/signal-extension-\\\n+modes.html\n+    \"\"\"\n+    shape = tuple(shape)\n+    if any(int(s) != s for s in shape):\n+        raise ValueError('`shape` may only contain integers, got {}'\n+                         ''.format(shape))\n+    wavelet = pywt_wavelet(wavelet)\n+\n+    nlevels, nlevels_in = int(nlevels), nlevels\n+    if nlevels_in != nlevels:\n+        raise ValueError('`nlevels` must be integer, got {}'\n+                         ''.format(nlevels_in))\n+    # TODO: adapt for axes\n+    _check_nlevels(nlevels, min(shape), wavelet)\n+\n+    mode, mode_in = str(mode).lower(), mode\n+    if mode not in PYWT_SUPPORTED_MODES:\n+        raise ValueError(\"mode '{}' not understood\".format(mode_in))\n+\n+    # Use pywt.dwt_coeff_len to determine the coefficient lengths at each\n+    # scale recursively. Start with the image shape and use the last created\n+    # shape for the next step.\n+    shape_list = [shape]\n+    for _ in range(nlevels):\n+        shape = tuple(pywt.dwt_coeff_len(n, filter_len=wavelet.dec_len,\n+                                         mode=mode)\n+                      for n in shape_list[-1])\n+        shape_list.append(shape)\n+\n+    # Add a duplicate of the last entry for the approximation coefficients\n+    shape_list.append(shape_list[-1])\n+\n+    # We created the list in reversed order, reverse it. Remove also the\n+    # superfluous image shape at the end.\n+    shape_list.reverse()\n+    shape_list.pop()\n+    return shape_list\n+\n+\n+def pywt_flat_coeff_size(shape, wavelet, nlevels, mode):\n+    \"\"\"Return the size of a flat array containing all coefficients.\n+\n+    The wavelet transform specified by ``wavelet``, ``nlevels`` and\n+    ``mode`` produces a sequence of approximation and detail\n+    coefficients. This function computes the total size of those\n+    coefficients when stored in a flat vector.\n+\n+    Parameters\n+    ----------\n+    shape : sequence\n+        Shape of an input to the transform.\n+    wavelet : string or `pywt.Wavelet`\n+        Specification of the wavelet to be used in the transform.\n+        If a string is given, it is converted to a `pywt.Wavelet`.\n+        Use `pywt.wavelist` to get a list of available wavelets.\n+    nlevels : positive int\n+        Number of scaling levels to be used in the decomposition. The\n+        maximum number of levels can be calculated with\n+        `pywt.dwt_max_level`.\n+    mode : string, optional\n+        PyWavelets style signal extension mode. See `signal extension modes`_\n+        for available options.\n+\n+    Returns\n+    -------\n+    flat_size : int\n+        Size of a flat array containing all approximation and detail\n+        coefficients for the specified transform.\n+\n+    See Also\n+    --------\n+    pywt_coeff_shapes : calculate the shapes of coefficients in a\n+        specified wavelet transform\n+\n+    Examples\n+    --------\n+    Determine the total size of a flat coefficient array for 2 scaling\n+    levels in 3 with zero-padding:\n+\n+    >>> pywt_flat_coeff_size(shape=(16, 17, 18), wavelet='db2', nlevels=2,\n+    ...                      mode='zpd')\n+    8028\n+    >>> 16 * 17 * 18  # original size, smaller -> redundancy in transform\n+    4896\n+\n+    References\n+    ----------\n+    .. _signal extension modes:\n+       https://pywavelets.readthedocs.io/en/latest/ref/signal-extension-\\\n+modes.html\n+    \"\"\"\n+    shapes = pywt_coeff_shapes(shape, wavelet, nlevels, mode)\n+    # 1 x approx coeff and (2**n - 1) x detail coeff\n+    ndim = len(shapes[0])\n+    return (np.prod(shapes[0]) +\n+            (2 ** ndim - 1) * sum(np.prod(shape)\n+                                  for shape in shapes[1:]))\n+\n+\n+def pywt_flat_array_from_coeffs(coeffs):\n+    \"\"\"Return a flat array from a Pywavelets coefficient sequence.\n+\n+    Parameters\n+    ----------\n+    coeffs : ordered sequence\n+        Sequence of approximation and detail coefficients in the format\n+\n+            ``[aN, DN, ... D1]``,\n+\n+        where ``aN`` is the N-th level approximation coefficient array and\n+        ``Di`` the tuple of i-th level detail coefficient arrays.\n+\n+    Returns\n+    -------\n+    arr : `numpy.ndarray`\n+        Flat coefficient vector containing approximation and detail\n+        coefficients in the same order as they appear in ``coeffs``.\n+\n+    See Also\n+    --------\n+    pywt_coeffs_from_flat_array : Conversion from flat array to\n+        coefficient list, the inverse of this function.\n+\n+    Examples\n+    --------\n+    Flatten a list of 1 approximation and ``2 ** 2 - 1 = 3`` detail\n+    coefficients (sizes not representative):\n+\n+    >>> approx = [[1, 1]]  # shape (1, 2)\n+    >>> details = ([[2, 2]], [[3, 3]], [[4, 4]])  # 3 * shape (1, 2)\n+    >>> pywt_flat_array_from_coeffs([approx, details])\n+    array([1, 1, 2, 2, 3, 3, 4, 4])\n+    \"\"\"\n+    # We convert to numpy array since we need the sizes anyway, and np.size\n+    # is as expensive as np.asarray().size.\n+    approx = np.asarray(coeffs[0])\n+    details_list = []\n+    flat_sizes = [approx.size]\n+\n+    for details in coeffs[1:]:\n+        if isinstance(details, tuple):\n+            detail_arrs = tuple(np.asarray(detail) for detail in details)\n+        else:\n+            # Single detail coefficient array\n+            detail_arrs = (np.asarray(details),)\n+\n+        flat_sizes.append(detail_arrs[0].size)\n+        details_list.append(detail_arrs)\n+\n+    ndim = approx.ndim\n+    dtype = approx.dtype\n+    dcoeffs_per_scale = 2 ** ndim - 1\n+\n+    flat_total_size = flat_sizes[0] + dcoeffs_per_scale * sum(flat_sizes[1:])\n+    flat_coeff = np.empty(flat_total_size, dtype=dtype)\n+\n+    start = 0\n+    stop = flat_sizes[0]\n+    flat_coeff[start:stop] = approx.ravel()\n+\n+    for fsize, details in zip(flat_sizes[1:], details_list):\n+        for detail in details:\n+            start, stop = stop, stop + fsize\n+            flat_coeff[start:stop] = detail.ravel()\n+\n+    return flat_coeff\n+\n+\n+def pywt_coeffs_from_flat_array(arr, shapes):\n+    \"\"\"Convert a flat array into a ``pywt`` coefficient list.\n+\n+    Parameters\n+    ----------\n+    arr : `array-like`\n+        A flat coefficient vector containing approximation and detail\n+        coefficients with order and sizes determined by ``shapes``.\n+    shapes : sequence\n+        The shapes of the approximation and detail coefficients at\n+        different scaling levels in the following order:\n+\n+            ``[shape_aN, shape_DN, ..., shape_D1]``\n+\n+        Here, ``shape_aN`` is the shape of the N-th level approximation\n+        coefficient array, and ``shape_Di`` is the shape of all\n+        i-th level detail coefficients.\n+\n+    Returns\n+    -------\n+    coeff_list : structured list\n+        List of approximation and detail coefficients in the format\n+\n+            ``[aN, DN, ... D1]``,\n+\n+        where ``aN`` is the N-th level approximation coefficient array and\n+        ``Di`` the tuple of i-th level detail coefficient arrays. Each of\n+        the ``Di`` tuples has length ``2 ** ndim - 1``, where ``ndim`` is\n+        the number of dimensions of ``arr``.\n+\n+    See Also\n+    --------\n+    pywt_flat_array_from_coeffs : Conversion from coefficient list to\n+        flat array, i.e. the inverse of this function.\n+\n+    Examples\n+    --------\n+    Turn the flat array from the example in `pywt_flat_array_from_coeffs`\n+    back into a coefficient list:\n+\n+    >>> arr = [1, 1, 2, 2, 3, 3, 4, 4]\n+    >>> # approximation and detail coefficient shapes\n+    >>> shapes = [(1, 2), (1, 2)]\n+    >>>\n+    >>> coeffs = pywt_coeffs_from_flat_array(arr, shapes)\n+    >>> approx, details = coeffs\n+    >>> print(approx)\n+    [[1 1]]\n+    >>> print(details)\n+    (array([[2, 2]]), array([[3, 3]]), array([[4, 4]]))\n+    \"\"\"\n+    arr = np.asarray(arr)\n+    flat_sizes = [np.prod(shp) for shp in shapes]\n+    start = 0\n+    stop = flat_sizes[0]\n+    coeff_list = [arr[start:stop].reshape(shapes[0])]\n+    ndim = len(shapes[0])\n+    dcoeffs_per_scale = 2 ** ndim - 1\n+\n+    for fsize, shape in zip(flat_sizes[1:], shapes[1:]):\n+        start, stop = stop, stop + dcoeffs_per_scale * fsize\n+\n+        detail_coeffs = tuple(\n+            c.reshape(shape)\n+            for c in np.split(arr[start:stop], dcoeffs_per_scale))\n+\n+        coeff_list.append(detail_coeffs)\n+\n+    return coeff_list\n+\n+\n+def pywt_dict_keys(length):\n+    \"\"\"Return a list of coefficient dictionary keys as used in ``pywt``.\n+\n+    The dictionary keys are composed of the characters ``'a'`` and ``'d'``\n+    and stand for \"approximation\" and \"detail\" coefficients. The letter\n+    in the i-th position of a key string signalizes which filtering\n+    operation the input undergoes in the i-th coordinate axis. For example,\n+    ``'ada'`` stands for low-pass filtering (-> approximation) in the\n+    first and last axes and high-pass filtering (-> detail) in the\n+    middle axis.\n+\n+    Examples\n+    --------\n+    There are 8 variants for ``length=3``:\n+\n+    >>> pywt_dict_keys(length=3)\n+    ['aaa', 'aad', 'ada', 'add', 'daa', 'dad', 'dda', 'ddd']\n+    \"\"\"\n+    return list(''.join(k) for k in product('ad', repeat=length))\n+\n+\n+def pywt_single_level_decomp(arr, wavelet, mode):\n+    \"\"\"Return single level wavelet decomposition coefficients from ``arr``.\n+\n+    Parameters\n+    ----------\n+    arr : `array-like`\n+        Input array to the wavelet decomposition.\n+    wavelet : string or `pywt.Wavelet`\n+        Specification of the wavelet to be used in the transform.\n+        If a string is given, it is converted to a `pywt.Wavelet`.\n+        Use `pywt.wavelist` to get a list of available wavelets.\n+    mode : string, optional\n+        PyWavelets style signal extension mode. See `signal extension modes`_\n+        for available options.\n+\n+    Returns\n+    -------\n+    approx : `numpy.ndarray`\n+        Approximation coefficients, a single array.\n+    details : tuple of `numpy.ndarray`'s\n+        Detail coefficients, ``2 ** ndim - 1`` arrays, where ``ndim``\n+        is the number of dimensions of ``arr``.\n+\n+    See Also\n+    --------\n+    pywt_single_level_recon : Single-level reconstruction, i.e. the\n+        inverse of this function.\n+    pywt_multi_level_decomp : Multi-level version of the decompostion.\n+\n+    Examples\n+    --------\n+    Decomposition of a small two-dimensional array using a Haar wavelet\n+    and zero padding:\n+\n+    >>> arr = [[1, 1, 1],\n+    ...        [1, 0, 0],\n+    ...        [0, 1, 1]]\n+    >>> coeffs = pywt_single_level_decomp(arr, wavelet='haar', mode='zpd')\n+    >>> approx, details = coeffs\n+    >>> print(approx)\n+    [[ 1.5  0.5]\n+     [ 0.5  0.5]]\n+    >>> for detail in details:\n+    ...     print(detail)\n+    [[ 0.5  0.5]\n+     [-0.5  0.5]]\n+    [[ 0.5  0.5]\n+     [ 0.5  0.5]]\n+    [[-0.5  0.5]\n+     [-0.5  0.5]]\n+\n+    References\n+    ----------\n+    .. _signal extension modes:\n+       https://pywavelets.readthedocs.io/en/latest/ref/signal-extension-\\\n+modes.html\n+    \"\"\"\n+    # Handle input\n+    arr = np.asarray(arr)\n+    wavelet = pywt_wavelet(wavelet)\n+    mode, mode_in = str(mode).lower(), mode\n+    if mode not in PYWT_SUPPORTED_MODES:\n+        raise ValueError(\"mode '{}' not understood\".format(mode_in))\n+\n+    # Compute single level DWT using pywt.dwtn and pick the approximation\n+    # and detail coefficients from the dictionary\n+    coeff_dict = pywt.dwtn(arr, wavelet, mode)\n+    dict_keys = pywt_dict_keys(arr.ndim)\n+    approx = coeff_dict[dict_keys[0]]\n+    details = tuple(coeff_dict[k] for k in dict_keys[1:])\n+    return approx, details\n+\n+\n+def pywt_single_level_recon(approx, details, wavelet, mode, recon_shape=None):\n+    \"\"\"Return single level wavelet reconstruction from given coefficients.\n+\n+    Parameters\n+    ----------\n+    approx : `array-like`\n+        Approximation coefficients.\n+    details : sequence of `array-like`'s\n+        Detail coefficients. The length of the sequence must be\n+        ``2 ** ndim - 1``, where ``ndim`` is the number of dimensions\n+        in ``approx``.\n+    wavelet :  string or `pywt.Wavelet`\n+        Specification of the wavelet to be used in the transform.\n+        Use `pywt.wavelist` to get a list of available wavelets.\n+    mode : string, optional\n+        PyWavelets style signal extension mode. See `signal extension modes`_\n+        for available options.\n+    recon_shape : sequence of ints, optional\n+        Shape of the array to be reconstructed. Without this parameter,\n+        the reconstructed array always has even shape due to upsampling\n+        by a factor of 2. To get reconstructions with odd shapes, this\n+        parameter is required.\n+\n+    Returns\n+    -------\n+    recon : `numpy.ndarray`\n+        The single-level wavelet reconstruction.\n+\n+    See Also\n+    --------\n+    pywt_single_level_decomp : Single-level decomposition, i.e. the\n+        inverse of this function.\n+    pywt_multi_level_recon : Multi-level version of the reconstruction.\n+\n+    Examples\n+    --------\n+    Take the coefficients from the example in `pywt_single_level_decomp`\n+    and reconstruct the original array. Without ``recon_shape``, we get\n+    a ``(4, 4)`` array as reconstruction:\n+\n+    >>> approx = [[1.5, 0.5],\n+    ...           [0.5, 0.5]]\n+    >>>\n+    >>> details = ([[ 0.5, 0.5],\n+    ...             [-0.5, 0.5]],\n+    ...            [[ 0.5, 0.5],\n+    ...             [ 0.5, 0.5]],\n+    ...            [[-0.5, 0.5],\n+    ...             [-0.5, 0.5]])\n+    >>>\n+    >>> # Gives even shape by default\n+    >>> pywt_single_level_recon(approx, details, wavelet='haar', mode='zpd')\n+    array([[ 1.,  1.,  1.,  0.],\n+           [ 1.,  0.,  0.,  0.],\n+           [ 0.,  1.,  1.,  0.],\n+           [ 0.,  0.,  0.,  0.]])\n+    >>>","path":"odl/trafos/backends/pywt_bindings.py","position":535,"original_position":535,"commit_id":"54c2033e3ecaa9cd6024d0156f5ce2ac6e749eee","original_commit_id":"54c2033e3ecaa9cd6024d0156f5ce2ac6e749eee","user":{"login":"kohr-h","id":5030250,"avatar_url":"https://avatars.githubusercontent.com/u/5030250?v=3","gravatar_id":"","url":"https://api.github.com/users/kohr-h","html_url":"https://github.com/kohr-h","followers_url":"https://api.github.com/users/kohr-h/followers","following_url":"https://api.github.com/users/kohr-h/following{/other_user}","gists_url":"https://api.github.com/users/kohr-h/gists{/gist_id}","starred_url":"https://api.github.com/users/kohr-h/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kohr-h/subscriptions","organizations_url":"https://api.github.com/users/kohr-h/orgs","repos_url":"https://api.github.com/users/kohr-h/repos","events_url":"https://api.github.com/users/kohr-h/events{/privacy}","received_events_url":"https://api.github.com/users/kohr-h/received_events","type":"User","site_admin":false},"body":"I actually want a blank line there due to the subsequent comment line. ","created_at":"2016-10-11T18:58:12Z","updated_at":"2016-10-11T18:58:13Z","html_url":"https://github.com/odlgroup/odl/pull/547#discussion_r82864624","pull_request_url":"https://api.github.com/repos/odlgroup/odl/pulls/547","_links":{"self":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/comments/82864624"},"html":{"href":"https://github.com/odlgroup/odl/pull/547#discussion_r82864624"},"pull_request":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/547"}}},"pull_request":{"url":"https://api.github.com/repos/odlgroup/odl/pulls/547","id":83275613,"html_url":"https://github.com/odlgroup/odl/pull/547","diff_url":"https://github.com/odlgroup/odl/pull/547.diff","patch_url":"https://github.com/odlgroup/odl/pull/547.patch","issue_url":"https://api.github.com/repos/odlgroup/odl/issues/547","number":547,"state":"open","locked":false,"title":"Refactor Fourier  and Wavelet modules, make Wavelet n-dimensional","user":{"login":"kohr-h","id":5030250,"avatar_url":"https://avatars.githubusercontent.com/u/5030250?v=3","gravatar_id":"","url":"https://api.github.com/users/kohr-h","html_url":"https://github.com/kohr-h","followers_url":"https://api.github.com/users/kohr-h/followers","following_url":"https://api.github.com/users/kohr-h/following{/other_user}","gists_url":"https://api.github.com/users/kohr-h/gists{/gist_id}","starred_url":"https://api.github.com/users/kohr-h/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kohr-h/subscriptions","organizations_url":"https://api.github.com/users/kohr-h/orgs","repos_url":"https://api.github.com/users/kohr-h/repos","events_url":"https://api.github.com/users/kohr-h/events{/privacy}","received_events_url":"https://api.github.com/users/kohr-h/received_events","type":"User","site_admin":false},"body":"This un-clutters the `fourier.py` module and puts all the `pyFFTW`-specific stuff in a separate `backends` subpackage, plus the utilities like `reciprocal_space` into a `utils` subpackage. Basically no code changed.\r\n\r\nTODOs:\r\n\r\n- [x] Improve test coverage. Was probably quite bad before, but now it has even become worse. Needs some parametrization and broader test cases.\r\n- [x] Move remaining `pywt` specific code out of `wavelet.py`. Not that much left, in one or two places maybe.\r\n- [x] Rename `reciprocal` -> `reciprocal_grid` in the Fourier part\r\n- [x] Refactor Fourier tests and parametrize more (could be even more, but anyway)\r\n- [x] Refactor Wavelet tests and parametrize more\r\n- [x] Test wavelet code with more data types (in particular complex)\r\n- [x] Add test comparing with hand-calculated result for wavelet trafo\r\n\r\nEdit: TODOs moved here","created_at":"2016-08-30T13:54:34Z","updated_at":"2016-10-11T18:58:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1cf6c9b560e58ea15ec4516f7148f7e9d58ab799","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/odlgroup/odl/pulls/547/commits","review_comments_url":"https://api.github.com/repos/odlgroup/odl/pulls/547/comments","review_comment_url":"https://api.github.com/repos/odlgroup/odl/pulls/comments{/number}","comments_url":"https://api.github.com/repos/odlgroup/odl/issues/547/comments","statuses_url":"https://api.github.com/repos/odlgroup/odl/statuses/54c2033e3ecaa9cd6024d0156f5ce2ac6e749eee","head":{"label":"odlgroup:refactor_fourier","ref":"refactor_fourier","sha":"54c2033e3ecaa9cd6024d0156f5ce2ac6e749eee","user":{"login":"odlgroup","id":15667171,"avatar_url":"https://avatars.githubusercontent.com/u/15667171?v=3","gravatar_id":"","url":"https://api.github.com/users/odlgroup","html_url":"https://github.com/odlgroup","followers_url":"https://api.github.com/users/odlgroup/followers","following_url":"https://api.github.com/users/odlgroup/following{/other_user}","gists_url":"https://api.github.com/users/odlgroup/gists{/gist_id}","starred_url":"https://api.github.com/users/odlgroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odlgroup/subscriptions","organizations_url":"https://api.github.com/users/odlgroup/orgs","repos_url":"https://api.github.com/users/odlgroup/repos","events_url":"https://api.github.com/users/odlgroup/events{/privacy}","received_events_url":"https://api.github.com/users/odlgroup/received_events","type":"Organization","site_admin":false},"repo":{"id":45596393,"name":"odl","full_name":"odlgroup/odl","owner":{"login":"odlgroup","id":15667171,"avatar_url":"https://avatars.githubusercontent.com/u/15667171?v=3","gravatar_id":"","url":"https://api.github.com/users/odlgroup","html_url":"https://github.com/odlgroup","followers_url":"https://api.github.com/users/odlgroup/followers","following_url":"https://api.github.com/users/odlgroup/following{/other_user}","gists_url":"https://api.github.com/users/odlgroup/gists{/gist_id}","starred_url":"https://api.github.com/users/odlgroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odlgroup/subscriptions","organizations_url":"https://api.github.com/users/odlgroup/orgs","repos_url":"https://api.github.com/users/odlgroup/repos","events_url":"https://api.github.com/users/odlgroup/events{/privacy}","received_events_url":"https://api.github.com/users/odlgroup/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/odlgroup/odl","description":"Operator Discretization Library","fork":false,"url":"https://api.github.com/repos/odlgroup/odl","forks_url":"https://api.github.com/repos/odlgroup/odl/forks","keys_url":"https://api.github.com/repos/odlgroup/odl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/odlgroup/odl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/odlgroup/odl/teams","hooks_url":"https://api.github.com/repos/odlgroup/odl/hooks","issue_events_url":"https://api.github.com/repos/odlgroup/odl/issues/events{/number}","events_url":"https://api.github.com/repos/odlgroup/odl/events","assignees_url":"https://api.github.com/repos/odlgroup/odl/assignees{/user}","branches_url":"https://api.github.com/repos/odlgroup/odl/branches{/branch}","tags_url":"https://api.github.com/repos/odlgroup/odl/tags","blobs_url":"https://api.github.com/repos/odlgroup/odl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/odlgroup/odl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/odlgroup/odl/git/refs{/sha}","trees_url":"https://api.github.com/repos/odlgroup/odl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/odlgroup/odl/statuses/{sha}","languages_url":"https://api.github.com/repos/odlgroup/odl/languages","stargazers_url":"https://api.github.com/repos/odlgroup/odl/stargazers","contributors_url":"https://api.github.com/repos/odlgroup/odl/contributors","subscribers_url":"https://api.github.com/repos/odlgroup/odl/subscribers","subscription_url":"https://api.github.com/repos/odlgroup/odl/subscription","commits_url":"https://api.github.com/repos/odlgroup/odl/commits{/sha}","git_commits_url":"https://api.github.com/repos/odlgroup/odl/git/commits{/sha}","comments_url":"https://api.github.com/repos/odlgroup/odl/comments{/number}","issue_comment_url":"https://api.github.com/repos/odlgroup/odl/issues/comments{/number}","contents_url":"https://api.github.com/repos/odlgroup/odl/contents/{+path}","compare_url":"https://api.github.com/repos/odlgroup/odl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/odlgroup/odl/merges","archive_url":"https://api.github.com/repos/odlgroup/odl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/odlgroup/odl/downloads","issues_url":"https://api.github.com/repos/odlgroup/odl/issues{/number}","pulls_url":"https://api.github.com/repos/odlgroup/odl/pulls{/number}","milestones_url":"https://api.github.com/repos/odlgroup/odl/milestones{/number}","notifications_url":"https://api.github.com/repos/odlgroup/odl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/odlgroup/odl/labels{/name}","releases_url":"https://api.github.com/repos/odlgroup/odl/releases{/id}","deployments_url":"https://api.github.com/repos/odlgroup/odl/deployments","created_at":"2015-11-05T07:57:55Z","updated_at":"2016-10-02T15:13:23Z","pushed_at":"2016-10-11T12:40:22Z","git_url":"git://github.com/odlgroup/odl.git","ssh_url":"git@github.com:odlgroup/odl.git","clone_url":"https://github.com/odlgroup/odl.git","svn_url":"https://github.com/odlgroup/odl","homepage":null,"size":32132,"stargazers_count":14,"watchers_count":14,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":7,"mirror_url":null,"open_issues_count":144,"forks":7,"open_issues":144,"watchers":14,"default_branch":"master"}},"base":{"label":"odlgroup:master","ref":"master","sha":"309e99125b4152330d359463ba704692df479f32","user":{"login":"odlgroup","id":15667171,"avatar_url":"https://avatars.githubusercontent.com/u/15667171?v=3","gravatar_id":"","url":"https://api.github.com/users/odlgroup","html_url":"https://github.com/odlgroup","followers_url":"https://api.github.com/users/odlgroup/followers","following_url":"https://api.github.com/users/odlgroup/following{/other_user}","gists_url":"https://api.github.com/users/odlgroup/gists{/gist_id}","starred_url":"https://api.github.com/users/odlgroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odlgroup/subscriptions","organizations_url":"https://api.github.com/users/odlgroup/orgs","repos_url":"https://api.github.com/users/odlgroup/repos","events_url":"https://api.github.com/users/odlgroup/events{/privacy}","received_events_url":"https://api.github.com/users/odlgroup/received_events","type":"Organization","site_admin":false},"repo":{"id":45596393,"name":"odl","full_name":"odlgroup/odl","owner":{"login":"odlgroup","id":15667171,"avatar_url":"https://avatars.githubusercontent.com/u/15667171?v=3","gravatar_id":"","url":"https://api.github.com/users/odlgroup","html_url":"https://github.com/odlgroup","followers_url":"https://api.github.com/users/odlgroup/followers","following_url":"https://api.github.com/users/odlgroup/following{/other_user}","gists_url":"https://api.github.com/users/odlgroup/gists{/gist_id}","starred_url":"https://api.github.com/users/odlgroup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odlgroup/subscriptions","organizations_url":"https://api.github.com/users/odlgroup/orgs","repos_url":"https://api.github.com/users/odlgroup/repos","events_url":"https://api.github.com/users/odlgroup/events{/privacy}","received_events_url":"https://api.github.com/users/odlgroup/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/odlgroup/odl","description":"Operator Discretization Library","fork":false,"url":"https://api.github.com/repos/odlgroup/odl","forks_url":"https://api.github.com/repos/odlgroup/odl/forks","keys_url":"https://api.github.com/repos/odlgroup/odl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/odlgroup/odl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/odlgroup/odl/teams","hooks_url":"https://api.github.com/repos/odlgroup/odl/hooks","issue_events_url":"https://api.github.com/repos/odlgroup/odl/issues/events{/number}","events_url":"https://api.github.com/repos/odlgroup/odl/events","assignees_url":"https://api.github.com/repos/odlgroup/odl/assignees{/user}","branches_url":"https://api.github.com/repos/odlgroup/odl/branches{/branch}","tags_url":"https://api.github.com/repos/odlgroup/odl/tags","blobs_url":"https://api.github.com/repos/odlgroup/odl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/odlgroup/odl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/odlgroup/odl/git/refs{/sha}","trees_url":"https://api.github.com/repos/odlgroup/odl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/odlgroup/odl/statuses/{sha}","languages_url":"https://api.github.com/repos/odlgroup/odl/languages","stargazers_url":"https://api.github.com/repos/odlgroup/odl/stargazers","contributors_url":"https://api.github.com/repos/odlgroup/odl/contributors","subscribers_url":"https://api.github.com/repos/odlgroup/odl/subscribers","subscription_url":"https://api.github.com/repos/odlgroup/odl/subscription","commits_url":"https://api.github.com/repos/odlgroup/odl/commits{/sha}","git_commits_url":"https://api.github.com/repos/odlgroup/odl/git/commits{/sha}","comments_url":"https://api.github.com/repos/odlgroup/odl/comments{/number}","issue_comment_url":"https://api.github.com/repos/odlgroup/odl/issues/comments{/number}","contents_url":"https://api.github.com/repos/odlgroup/odl/contents/{+path}","compare_url":"https://api.github.com/repos/odlgroup/odl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/odlgroup/odl/merges","archive_url":"https://api.github.com/repos/odlgroup/odl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/odlgroup/odl/downloads","issues_url":"https://api.github.com/repos/odlgroup/odl/issues{/number}","pulls_url":"https://api.github.com/repos/odlgroup/odl/pulls{/number}","milestones_url":"https://api.github.com/repos/odlgroup/odl/milestones{/number}","notifications_url":"https://api.github.com/repos/odlgroup/odl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/odlgroup/odl/labels{/name}","releases_url":"https://api.github.com/repos/odlgroup/odl/releases{/id}","deployments_url":"https://api.github.com/repos/odlgroup/odl/deployments","created_at":"2015-11-05T07:57:55Z","updated_at":"2016-10-02T15:13:23Z","pushed_at":"2016-10-11T12:40:22Z","git_url":"git://github.com/odlgroup/odl.git","ssh_url":"git@github.com:odlgroup/odl.git","clone_url":"https://github.com/odlgroup/odl.git","svn_url":"https://github.com/odlgroup/odl","homepage":null,"size":32132,"stargazers_count":14,"watchers_count":14,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":7,"mirror_url":null,"open_issues_count":144,"forks":7,"open_issues":144,"watchers":14,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/547"},"html":{"href":"https://github.com/odlgroup/odl/pull/547"},"issue":{"href":"https://api.github.com/repos/odlgroup/odl/issues/547"},"comments":{"href":"https://api.github.com/repos/odlgroup/odl/issues/547/comments"},"review_comments":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/547/comments"},"review_comment":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/odlgroup/odl/pulls/547/commits"},"statuses":{"href":"https://api.github.com/repos/odlgroup/odl/statuses/54c2033e3ecaa9cd6024d0156f5ce2ac6e749eee"}}}}
{"actor":{"display_login":"kohr-h"}}