.. _reg-wp:

.. currentmodule:: pywt

>>> from __future__ import print_function

Wavelet Packets
===============

Import pywt
-----------

    >>> import pywt

    >>> def format_array(a):
    ...     """Consistent array representation across different systems"""
    ...     import numpy
    ...     a = numpy.where(numpy.abs(a) < 1e-5, 0, a)
    ...     return numpy.array2string(a, precision=5, separator=' ', suppress_small=True)


Create Wavelet Packet structure
-------------------------------

Ok, let's create a sample :class:`WaveletPacket`:

    >>> x = [1, 2, 3, 4, 5, 6, 7, 8]
    >>> wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric')

The input ``data`` and decomposition coefficients are stored in the
:attr:`WaveletPacket.data` attribute:

    >>> print(wp.data)
    [1, 2, 3, 4, 5, 6, 7, 8]

:class:`Nodes <Node>` are identified by :attr:`paths <~Node.path>`. For the root
node the path is ``''`` and the decomposition level is ``0``.

    >>> print(repr(wp.path))
    ''
    >>> print(wp.level)
    0

The ``maxlevel``, if not given as param in the constructor, is automatically
computed:

    >>> print(wp['ad'].maxlevel)
    3


Traversing WP tree:
-------------------

Accessing subnodes:
~~~~~~~~~~~~~~~~~~~

>>> x = [1, 2, 3, 4, 5, 6, 7, 8]
>>> wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric')

First check what is the maximum level of decomposition:

    >>> print(wp.maxlevel)
    3

and try accessing subnodes of the WP tree:

    * 1st level:

        >>> print(wp['a'].data)
        [  2.12132034   4.94974747   7.77817459  10.60660172]
        >>> print(wp['a'].path)
        a

    * 2nd level:

        >>> print(wp['aa'].data)
        [  5.  13.]
        >>> print(wp['aa'].path)
        aa


    * 3rd level:

        >>> print(wp['aaa'].data)
        [ 12.72792206]
        >>> print(wp['aaa'].path)
        aaa


      Ups, we have reached the maximum level of decomposition and got an
      :exc:`IndexError`:

        >>> print(wp['aaaa'].data)
        Traceback (most recent call last):
        ...
        IndexError: Path length is out of range.

Now try some invalid path:

    >>> print(wp['ac'])
    Traceback (most recent call last):
    ...
    ValueError: Subnode name must be in ['a', 'd'], not 'c'.

which just yielded a :exc:`ValueError`.


Accessing Node's attributes:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:class:`WaveletPacket` object is a tree data structure, which evaluates to a set
of :class:`Node` objects. :class:`WaveletPacket` is just a special subclass
of the :class:`Node` class (which in turn inherits from the :class:`BaseNode`).

Tree nodes can be accessed using the ``obj[x]`` (:meth:`Node.__getitem__`)
operator.
Each tree node has a set of attributes: :attr:`~Node.data`, :attr:`~Node.path`,
:attr:`~Node.node_name`, :attr:`~Node.parent`, :attr:`~Node.level`,
:attr:`~Node.maxlevel` and :attr:`~Node.mode`.

>>> x = [1, 2, 3, 4, 5, 6, 7, 8]
>>> wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric')

>>> print(wp['ad'].data)
[-2. -2.]

>>> print(wp['ad'].path)
ad

>>> print(wp['ad'].node_name)
d

>>> print(wp['ad'].parent.path)
a

>>> print(wp['ad'].level)
2

>>> print(wp['ad'].maxlevel)
3

>>> print(wp['ad'].mode)
symmetric


Collecting nodes
~~~~~~~~~~~~~~~~

>>> x = [1, 2, 3, 4, 5, 6, 7, 8]
>>> wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric')


We can get all nodes on the particular level either in ``natural`` order:

    >>> print([node.path for node in wp.get_level(3, 'natural')])
    ['aaa', 'aad', 'ada', 'add', 'daa', 'dad', 'dda', 'ddd']

or sorted based on the band frequency (``freq``):

    >>> print([node.path for node in wp.get_level(3, 'freq')])
    ['aaa', 'aad', 'add', 'ada', 'dda', 'ddd', 'dad', 'daa']

Note that :meth:`WaveletPacket.get_level` also performs automatic decomposition
until it reaches the specified ``level``.


Reconstructing data from Wavelet Packets:
-----------------------------------------

>>> x = [1, 2, 3, 4, 5, 6, 7, 8]
>>> wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric')


Now create a new :class:`Wavelet Packet <WaveletPacket>` and set its nodes with
some data.

    >>> new_wp = pywt.WaveletPacket(data=None, wavelet='db1', mode='symmetric')

    >>> new_wp['aa'] = wp['aa'].data
    >>> new_wp['ad'] = [-2., -2.]

For convenience, :attr:`Node.data` gets automatically extracted from the
:class:`Node` object:

    >>> new_wp['d'] = wp['d']

And reconstruct the data from the ``aa``, ``ad`` and ``d`` packets.

    >>> print(new_wp.reconstruct(update=False))
    [ 1.  2.  3.  4.  5.  6.  7.  8.]

If the ``update`` param in the reconstruct method is set to ``False``, the
node's :attr:`~Node.data` will not be updated.

    >>> print(new_wp.data)
    None

Otherwise, the :attr:`~Node.data` attribute will be set to the reconstructed
value.

    >>> print(new_wp.reconstruct(update=True))
    [ 1.  2.  3.  4.  5.  6.  7.  8.]
    >>> print(new_wp.data)
    [ 1.  2.  3.  4.  5.  6.  7.  8.]


>>> print([n.path for n in new_wp.get_leaf_nodes(False)])
['aa', 'ad', 'd']

>>> print([n.path for n in new_wp.get_leaf_nodes(True)])
['aaa', 'aad', 'ada', 'add', 'daa', 'dad', 'dda', 'ddd']


Removing nodes from Wavelet Packet tree:
----------------------------------------

Let's create a sample data:

    >>> x = [1, 2, 3, 4, 5, 6, 7, 8]
    >>> wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric')

First, start with a tree decomposition at level 2. Leaf nodes in the tree are:

    >>> dummy = wp.get_level(2)
    >>> for n in wp.get_leaf_nodes(False):
    ...     print(n.path, format_array(n.data))
    aa [  5.  13.]
    ad [-2. -2.]
    da [-1. -1.]
    dd [ 0.  0.]

    >>> node = wp['ad']
    >>> print(node)
    ad: [-2. -2.]

To remove a node from the WP tree, use Python's ``del obj[x]``
(:class:`Node.__delitem__`):

    >>> del wp['ad']

The leaf nodes that left in the tree are:

    >>> for n in wp.get_leaf_nodes():
    ...     print(n.path, format_array(n.data))
    aa [  5.  13.]
    da [-1. -1.]
    dd [ 0.  0.]

And the reconstruction is:

    >>> print(wp.reconstruct())
    [ 2.  3.  2.  3.  6.  7.  6.  7.]

Now restore the deleted node value.

    >>> wp['ad'].data = node.data

Printing leaf nodes and tree reconstruction confirms the original state of the
tree:

    >>> for n in wp.get_leaf_nodes(False):
    ...     print(n.path, format_array(n.data))
    aa [  5.  13.]
    ad [-2. -2.]
    da [-1. -1.]
    dd [ 0.  0.]

    >>> print(wp.reconstruct())
    [ 1.  2.  3.  4.  5.  6.  7.  8.]


Lazy evaluation:
----------------

.. note:: This section is for demonstration of pywt internals purposes
          only. Do not rely on the attribute access to nodes as presented in
          this example.

>>> x = [1, 2, 3, 4, 5, 6, 7, 8]
>>> wp = pywt.WaveletPacket(data=x, wavelet='db1', mode='symmetric')

1) At first the wp's attribute ``a`` is None

   >>> print(wp.a)
   None

   **Remember that you should not rely on the attribute access.**

2) At first attempt to access the node it is computed via decomposition
   of its parent node (the wp object itself).

   >>> print(wp['a'])
   a: [  2.12132034   4.94974747   7.77817459  10.60660172]

3) Now the ``wp.a`` is set to the newly created node:

   >>> print(wp.a)
   a: [  2.12132034   4.94974747   7.77817459  10.60660172]

   And so is ``wp.d``:

   >>> print(wp.d)
   d: [-0.70710678 -0.70710678 -0.70710678 -0.70710678]
