
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "auto_examples/optical_flow_methods_convergence.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_auto_examples_optical_flow_methods_convergence.py>`
        to download the full example code.

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_auto_examples_optical_flow_methods_convergence.py:


Optical flow methods convergence
================================

In this example we test the convergence of the optical flow methods available in
pysteps using idealized motion fields.

To test the convergence, using an example precipitation field we will:

- Read precipitation field from a file
- Morph the precipitation field using a given motion field (linear or rotor) to
  generate a sequence of moving precipitation patterns.
- Using the available optical flow methods, retrieve the motion field from the
  precipitation time sequence (synthetic precipitation observations).

Let's first load the libraries that we will use.

.. GENERATED FROM PYTHON SOURCE LINES 20-33

.. code-block:: Python

    from datetime import datetime
    import time

    import matplotlib.pyplot as plt
    import numpy as np
    from matplotlib.pyplot import get_cmap
    from scipy.ndimage import uniform_filter

    import pysteps as stp
    from pysteps import motion, io, rcparams
    from pysteps.motion.vet import morph
    from pysteps.visualization import plot_precip_field, quiver








.. GENERATED FROM PYTHON SOURCE LINES 34-40

Load the reference precipitation data
-------------------------------------

First, we will import a radar composite from the archive.
You need the pysteps-data archive downloaded and the pystepsrc file
configured with the data_source paths pointing to data folders.

.. GENERATED FROM PYTHON SOURCE LINES 40-46

.. code-block:: Python



    # Selected case
    date = datetime.strptime("201505151630", "%Y%m%d%H%M")
    data_source = rcparams.data_sources["mch"]








.. GENERATED FROM PYTHON SOURCE LINES 47-49

Load the data from the archive
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 49-72

.. code-block:: Python


    root_path = data_source["root_path"]
    path_fmt = data_source["path_fmt"]
    fn_pattern = data_source["fn_pattern"]
    fn_ext = data_source["fn_ext"]
    importer_name = data_source["importer"]
    importer_kwargs = data_source["importer_kwargs"]

    # Find the reference field in the archive
    fns = io.archive.find_by_date(
        date, root_path, path_fmt, fn_pattern, fn_ext, timestep=5, num_prev_files=0
    )

    # Read the reference radar composite
    importer = io.get_method(importer_name, "importer")
    reference_field, quality, metadata = io.read_timeseries(
        fns, importer, **importer_kwargs
    )

    del quality  # Not used

    reference_field = np.squeeze(reference_field)  # Remove time dimension








.. GENERATED FROM PYTHON SOURCE LINES 73-75

Preprocess the data
~~~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 75-97

.. code-block:: Python


    # Convert to mm/h
    reference_field, metadata = stp.utils.to_rainrate(reference_field, metadata)

    # Mask invalid values
    reference_field = np.ma.masked_invalid(reference_field)

    # Plot the reference precipitation
    plot_precip_field(reference_field, title="Reference field")
    plt.show()

    # Log-transform the data [dBR]
    reference_field, metadata = stp.utils.dB_transform(
        reference_field, metadata, threshold=0.1, zerovalue=-15.0
    )

    print("Precip. pattern shape: " + str(reference_field.shape))

    # This suppress nan conversion warnings in plot functions
    reference_field.data[reference_field.mask] = np.nan





.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_001.png
   :alt: Reference field
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_001.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Precip. pattern shape: (640, 710)




.. GENERATED FROM PYTHON SOURCE LINES 98-107

Synthetic precipitation observations
------------------------------------

Now we need to create a series of precipitation fields by applying the ideal
motion field to the reference precipitation field "n" times.

To evaluate the accuracy of the computed_motion vectors, we will use
a relative RMSE measure.
Relative MSE = <(expected_motion - computed_motion)^2> / <expected_motion^2>

.. GENERATED FROM PYTHON SOURCE LINES 107-120

.. code-block:: Python


    # Relative RMSE = Rel_RMSE = sqrt(Relative MSE)
    #
    # - Rel_RMSE = 0%: no error
    # - Rel_RMSE = 100%: The retrieved motion field has an average error equal in
    #   magnitude to the motion field.
    #
    # Relative RMSE is computed over a region surrounding the precipitation
    # field, were there is enough information to retrieve the motion field.
    # The "precipitation region" includes the precipitation pattern plus a margin of
    # approximately 20 grid points.









.. GENERATED FROM PYTHON SOURCE LINES 121-122

Let's create a function to construct different motion fields.

.. GENERATED FROM PYTHON SOURCE LINES 122-173

.. code-block:: Python

    def create_motion_field(input_precip, motion_type):
        """
        Create idealized motion fields to be applied to the reference image.

        Parameters
        ----------

        input_precip: numpy array (lat, lon)

        motion_type: str
            The supported motion fields are:

                - linear_x: (u=2, v=0)
                - linear_y: (u=0, v=2)
                - rotor: rotor field

        Returns
        -------
        ideal_motion : numpy array (u, v)
        """

        # Create an imaginary grid on the image and create a motion field to be
        # applied to the image.
        ny, nx = input_precip.shape

        x_pos = np.arange(nx)
        y_pos = np.arange(ny)
        x, y = np.meshgrid(x_pos, y_pos, indexing="ij")

        ideal_motion = np.zeros((2, nx, ny))

        if motion_type == "linear_x":
            ideal_motion[0, :] = 2  # Motion along x
        elif motion_type == "linear_y":
            ideal_motion[1, :] = 2  # Motion along y
        elif motion_type == "rotor":
            x_mean = x.mean()
            y_mean = y.mean()
            norm = np.sqrt(x * x + y * y)
            mask = norm != 0
            ideal_motion[0, mask] = 2 * (y - y_mean)[mask] / norm[mask]
            ideal_motion[1, mask] = -2 * (x - x_mean)[mask] / norm[mask]
        else:
            raise ValueError("motion_type not supported.")

        # We need to swap the axes because the optical flow methods expect
        # (lat, lon) or (y,x) indexing convention.
        ideal_motion = ideal_motion.swapaxes(1, 2)
        return ideal_motion









.. GENERATED FROM PYTHON SOURCE LINES 174-176

Let's create another function that construct the temporal series of
precipitation observations.

.. GENERATED FROM PYTHON SOURCE LINES 176-322

.. code-block:: Python

    def create_observations(input_precip, motion_type, num_times=9):
        """
        Create synthetic precipitation observations by displacing the input field
        using an ideal motion field.

        Parameters
        ----------

        input_precip: numpy array (lat, lon)
            Input precipitation field.

        motion_type: str
            The supported motion fields are:

                - linear_x: (u=2, v=0)
                - linear_y: (u=0, v=2)
                - rotor: rotor field

        num_times: int, optional
            Length of the observations sequence.


        Returns
        -------
        synthetic_observations: numpy array
            Sequence of observations
        """

        ideal_motion = create_motion_field(input_precip, motion_type)

        # The morph function expects (lon, lat) or (x, y) dimensions.
        # Hence, we need to swap the lat,lon axes.

        # NOTE: The motion field passed to the morph function can't have any NaNs.
        # Otherwise, it can result in a segmentation fault.
        morphed_field, mask = morph(
            input_precip.swapaxes(0, 1), ideal_motion.swapaxes(1, 2)
        )

        mask = np.array(mask, dtype=bool)

        synthetic_observations = np.ma.MaskedArray(morphed_field, mask=mask)
        synthetic_observations = synthetic_observations[np.newaxis, :]

        for t in range(1, num_times):
            morphed_field, mask = morph(
                synthetic_observations[t - 1], ideal_motion.swapaxes(1, 2)
            )
            mask = np.array(mask, dtype=bool)

            morphed_field = np.ma.MaskedArray(
                morphed_field[np.newaxis, :], mask=mask[np.newaxis, :]
            )

            synthetic_observations = np.ma.concatenate(
                [synthetic_observations, morphed_field], axis=0
            )

        # Swap  back to (lat, lon)
        synthetic_observations = synthetic_observations.swapaxes(1, 2)

        synthetic_observations = np.ma.masked_invalid(synthetic_observations)

        synthetic_observations.data[np.ma.getmaskarray(synthetic_observations)] = 0

        return ideal_motion, synthetic_observations


    def plot_optflow_method_convergence(input_precip, optflow_method_name, motion_type):
        """
        Test the convergence to the actual solution of the optical flow method used.

        Parameters
        ----------

        input_precip: numpy array (lat, lon)
            Input precipitation field.

        optflow_method_name: str
            Optical flow method name

        motion_type: str
            The supported motion fields are:

                - linear_x: (u=2, v=0)
                - linear_y: (u=0, v=2)
                - rotor: rotor field
        """

        if optflow_method_name.lower() != "darts":
            num_times = 2
        else:
            num_times = 9

        ideal_motion, precip_obs = create_observations(
            input_precip, motion_type, num_times=num_times
        )

        oflow_method = motion.get_method(optflow_method_name)

        elapsed_time = time.perf_counter()

        computed_motion = oflow_method(precip_obs, verbose=False)

        print(
            f"{optflow_method_name} computation time: "
            f"{(time.perf_counter() - elapsed_time):.1f} [s]"
        )

        precip_obs, _ = stp.utils.dB_transform(precip_obs, inverse=True)

        precip_data = precip_obs.max(axis=0)
        precip_data.data[precip_data.mask] = 0

        precip_mask = (uniform_filter(precip_data, size=20) > 0.1) & ~precip_obs.mask.any(
            axis=0
        )

        cmap = get_cmap("jet").copy()
        cmap.set_under("grey", alpha=0.25)
        cmap.set_over("none")

        # Compare retrieved motion field with the ideal one
        plt.figure(figsize=(9, 4))
        plt.subplot(1, 2, 1)
        ax = plot_precip_field(precip_obs[0], title="Reference motion")
        quiver(ideal_motion, step=25, ax=ax)

        plt.subplot(1, 2, 2)
        ax = plot_precip_field(precip_obs[0], title="Retrieved motion")
        quiver(computed_motion, step=25, ax=ax)

        # To evaluate the accuracy of the computed_motion vectors, we will use
        # a relative RMSE measure.
        # Relative MSE = < (expected_motion - computed_motion)^2 > / <expected_motion^2 >
        # Relative RMSE = sqrt(Relative MSE)

        mse = ((ideal_motion - computed_motion)[:, precip_mask] ** 2).mean()

        rel_mse = mse / (ideal_motion[:, precip_mask] ** 2).mean()
        plt.suptitle(
            f"{optflow_method_name} " f"Relative RMSE: {np.sqrt(rel_mse) * 100:.2f}%"
        )
        plt.show()









.. GENERATED FROM PYTHON SOURCE LINES 323-328

Lucas-Kanade
------------

Constant motion x-direction
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 328-330

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "LucasKanade", "linear_x")




.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_002.png
   :alt: LucasKanade Relative RMSE: 1.52%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_002.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    LucasKanade computation time: 1.2 [s]




.. GENERATED FROM PYTHON SOURCE LINES 331-333

Constant motion y-direction
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 333-335

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "LucasKanade", "linear_y")




.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_003.png
   :alt: LucasKanade Relative RMSE: 1.47%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_003.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    LucasKanade computation time: 1.1 [s]




.. GENERATED FROM PYTHON SOURCE LINES 336-338

Rotational motion
~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 338-340

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "LucasKanade", "rotor")




.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_004.png
   :alt: LucasKanade Relative RMSE: 9.57%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_004.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    LucasKanade computation time: 1.2 [s]




.. GENERATED FROM PYTHON SOURCE LINES 341-346

Variational Echo Tracking (VET)
-------------------------------

Constant motion x-direction
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 346-348

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "VET", "linear_x")




.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_005.png
   :alt: VET Relative RMSE: 0.00%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_005.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    VET computation time: 2.0 [s]




.. GENERATED FROM PYTHON SOURCE LINES 349-351

Constant motion y-direction
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 351-353

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "VET", "linear_y")




.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_006.png
   :alt: VET Relative RMSE: 0.00%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_006.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    VET computation time: 1.9 [s]




.. GENERATED FROM PYTHON SOURCE LINES 354-356

Rotational motion
~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 356-358

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "VET", "rotor")




.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_007.png
   :alt: VET Relative RMSE: 3.73%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_007.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    VET computation time: 14.5 [s]




.. GENERATED FROM PYTHON SOURCE LINES 359-364

DARTS
-----

Constant motion x-direction
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 364-366

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "DARTS", "linear_x")




.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_008.png
   :alt: DARTS Relative RMSE: 22.97%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_008.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    DARTS computation time: 1.2 [s]




.. GENERATED FROM PYTHON SOURCE LINES 367-369

Constant motion y-direction
~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 369-371

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "DARTS", "linear_y")




.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_009.png
   :alt: DARTS Relative RMSE: 20.32%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_009.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    DARTS computation time: 1.2 [s]




.. GENERATED FROM PYTHON SOURCE LINES 372-374

Rotational motion
~~~~~~~~~~~~~~~~~

.. GENERATED FROM PYTHON SOURCE LINES 374-377

.. code-block:: Python

    plot_optflow_method_convergence(reference_field, "DARTS", "rotor")

    # sphinx_gallery_thumbnail_number = 5



.. image-sg:: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_010.png
   :alt: DARTS Relative RMSE: 53.43%, Reference motion, Retrieved motion
   :srcset: /auto_examples/images/sphx_glr_optical_flow_methods_convergence_010.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    DARTS computation time: 1.2 [s]





.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (0 minutes 28.305 seconds)


.. _sphx_glr_download_auto_examples_optical_flow_methods_convergence.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: optical_flow_methods_convergence.ipynb <optical_flow_methods_convergence.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: optical_flow_methods_convergence.py <optical_flow_methods_convergence.py>`

    .. container:: sphx-glr-download sphx-glr-download-zip

      :download:`Download zipped: optical_flow_methods_convergence.zip <optical_flow_methods_convergence.zip>`


.. only:: html

 .. rst-class:: sphx-glr-signature

    `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_
