Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeseries method to rippleimageseries #5

Merged
merged 2 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/tutorial/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,27 @@ All of these functions also work with `RippleImageSeries` objects, e.g.
series.images[0].plot()
plt.show()

Note, here we have passed the overwrite keyword since those contour methods already exist.

For a more detailed explanation of how to use these functions, see the :ref:`reference` section.

Visualising an Image Series
****************************

We have multiple methods attached to rimg and rimgs that help visualise the interface.

First we can produce animated .gif files easily:

.. code-block:: python

series.animate("example_series.gif")

We can also produce a timeseries plot of any of the contours in our series:

.. code-block:: python

series.timeseries("Upper Boundary")

Saving and loading data
************************

Expand Down
Binary file not shown.
46 changes: 46 additions & 0 deletions examples/visualisation/plotting_a_timeseries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
===================================
Plotting a timeseries of a contour
===================================

A RippleImageSeries is simply a container class for a list of RippleImages.

This example demonstrates how to plot a RippleImageSeries using inbuilt plotting methods.
"""

from matplotlib import pyplot as plt

from ripplemapper.analyse import (add_a_star_contours, add_boundary_contours,
add_chan_vese_contours)
from ripplemapper.classes import RippleImageSeries
from ripplemapper.data.example import example_dir
from ripplemapper.io import load_dir_to_obj

#################################################################
#
# We can create a list of RippleImages from a list of image files.
# In this example we use the load_dir_to_obj method to load all images in a directory into RippleImage objects.
#
# Passing this list to the RippleImageSeries constructor will create a RippleImageSeries object.

ripple_images = load_dir_to_obj(example_dir)
series = RippleImageSeries(ripple_images)
add_boundary_contours(series, sigma=2)
add_a_star_contours(series)
add_chan_vese_contours(series)

#################################################################
#
# We can plot a timeseries of this object, showing how the same contour evolves over time.
#
# We can refer to the contour via index:

series.timeseries(1)
plt.show()

#################################################################
#
# or via the method name:

series.timeseries('A* traversal')
plt.show()
3 changes: 3 additions & 0 deletions ripplemapper/analyse.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def add_boundary_contours(ripple_images: list[RippleImage] | RippleImage | Rippl
ripple_images = [ripple_images]
for ripple_image in ripple_images:
if len(ripple_image.contours) > 0:
# TODO: refactor to use new get_contour method
indexes = []
for i in range(len(ripple_image.contours)):
if ripple_image.contours[i].method == 'Upper Boundary':
Expand Down Expand Up @@ -57,6 +58,7 @@ def add_a_star_contours(ripple_images: list[RippleImage] | RippleImage | RippleI
if len(ripple_image.contours) < 2:
warnings.warn(f"RippleImage object must have at least two contours, skipping image: {ripple_image.source_file}")
continue
# TODO: refactor to use new get_contour method
methods = [contour.method for contour in ripple_image.contours]
if 'A* traversal' in methods:
if overwrite:
Expand Down Expand Up @@ -92,6 +94,7 @@ def add_chan_vese_contours(ripple_images: list[RippleImage] | RippleImage | Ripp
ripple_images = [ripple_images]
for ripple_image in ripple_images:
if len(ripple_image.contours) > 0:
# TODO: refactor to use new get_contour method
methods = [contour.method for contour in ripple_image.contours]
if 'Chan-Vese' in methods:
if overwrite:
Expand Down
22 changes: 21 additions & 1 deletion ripplemapper/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from ripplemapper.contour import smooth_bumps
from ripplemapper.image import preprocess_image
from ripplemapper.io import load_image
from ripplemapper.visualisation import plot_contours, plot_image
from ripplemapper.visualisation import (plot_contours, plot_image,
plot_timeseries)

__all__ = ['RippleContour', 'RippleImage', 'RippleImageSeries']

Expand Down Expand Up @@ -100,6 +101,18 @@ def add_contour(self, *args):
contour = RippleContour(*args, image=self)
self.contours.append(contour)

def get_contour(self, contour: str | int):
"""Return a given contour for the image."""
if isinstance(contour, int):
return self.contours[contour]
elif isinstance(contour, str):
for cont in self.contours:
if contour.lower() in cont.method.lower():
return cont
else:
raise ValueError("Invalid input, expected an integer or method string")


def smooth_contours(self, **kwargs):
"""Smooth all the contours in the image."""
self.contours = [contour.smooth(**kwargs) for contour in self.contours]
Expand Down Expand Up @@ -194,6 +207,13 @@ def save(self, fname: str = False, save_image_data: bool = False):
image.save(fname=image_fname, save_image_data=save_image_data)
return fname

def timeseries(self, contour: str | int = 0, **kwargs):
"""Plot a timeseries of the same contour."""
contours = [img.get_contour(contour) for img in self.images]
labels = [img.source_file.split('/')[-1] for img in self.images]
plot_timeseries(contours, labels)


def _load(self, file: str):
"""Load the image series from a file."""
with gzip.open(file, 'rb') as f:
Expand Down
16 changes: 16 additions & 0 deletions ripplemapper/tests/test_class_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@
import matplotlib.pyplot as plt
import pytest

from ripplemapper.analyse import add_boundary_contours
from ripplemapper.classes import RippleContour


def test_get_contour_index(loaded_example_image_with_contours):
contour = loaded_example_image_with_contours.get_contour(0)
assert contour is not None

def test_get_contour_method(loaded_example_image_with_contours):
method = "Lower Boundary"
contour = loaded_example_image_with_contours.get_contour(method)
assert contour is not None
assert contour.method == method

def test_ripple_contour_to_physical(loaded_example_contour):
# Assuming the function is not yet implemented
loaded_example_contour.to_physical()
Expand All @@ -19,6 +30,11 @@ def test_ripple_contour_plot(loaded_example_contour):
loaded_example_contour.plot()
plt.close()

def test_timeseries_plot(loaded_example_image_series):
add_boundary_contours(loaded_example_image_series)
loaded_example_image_series.timeseries(0)
plt.close()

def test_ripple_contour_smooth(loaded_example_contour):
loaded_example_contour.smooth()
# Assuming the smooth function does not return anything but modifies in place
Expand Down
9 changes: 9 additions & 0 deletions ripplemapper/visualisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import matplotlib.pyplot as plt
import numpy as np

__all__ = ['plot_contours', 'plot_image', 'plot_timeseries']

def plot_contours(ripple_contours, *args, **kwargs):
"""Plot the contour."""
Expand Down Expand Up @@ -56,3 +57,11 @@ def plot_image(ripple_image, include_contours: bool=True, cmap: str='gray', **k
for contour in ripple_image.contours:
plt.plot(contour.values[:][1], contour.values[:][0], label=contour.method)
plt.legend()


def plot_timeseries(contours, labels, **kwargs):
"""Plot a timeseries of contours."""
for i, contour in enumerate(contours):
plt.plot(contour.values[1], contour.values[0], label=labels[i], **kwargs)
plt.gca().invert_yaxis()
plt.legend()
Loading