Source code for ripplemapper.analyse

"""Mostly a collection of functions to help instantiate a list of image classes and add some contours"""

import warnings

import cv2
import numpy as np

from ripplemapper.classes import RippleContour, RippleImage, RippleImageSeries
from ripplemapper.contour import (a_star, combine_contours, distance_map,
                                  find_contours, smooth_bumps)
from ripplemapper.image import cv_segmentation, detect_edges, process_edges

__all__ = ["add_boundary_contours", "add_a_star_contours", "add_chan_vese_contours", "remove_small_bumps", "remove_small_bumps_from_images"]


[docs] def add_boundary_contours(ripple_images: list[RippleImage] | RippleImage | RippleImageSeries, overwrite: bool = False, level=None, **kwargs) -> list[RippleImage]: """ Add boundary contours to a list of RippleImage objects. Parameters ---------- ripple_images : list[RippleImage] | RippleImage | RippleImageSeries A list of RippleImage objects or a single RippleImage or RippleImageSeries. overwrite : bool, optional Whether to overwrite existing boundary contours, by default False. level : optional Contour level parameter for the find_contours function, by default None. **kwargs Additional keyword arguments for the find_contours function. Returns ------- list[RippleImage] The list of RippleImage objects with added boundary contours. """ if isinstance(ripple_images, RippleImageSeries): ripple_images = ripple_images.images if isinstance(ripple_images, RippleImage): ripple_images = [ripple_images] for ripple_image in ripple_images: if len(ripple_image.contours) > 0: indexes = [] for i in range(len(ripple_image.contours)): if ripple_image.contours[i].method == 'Upper Boundary': indexes.append(i) if ripple_image.contours[i].method == 'Lower Boundary': indexes.append(i) if len(indexes) > 0: if overwrite: warnings.warn(f"Overwriting boundary contours for image: {ripple_image.source_file}") if len(indexes) == 1: ripple_image.contours.pop(indexes[0]) if len(indexes) == 2: ripple_image.contours.pop(indexes[0]) ripple_image.contours.pop(indexes[1] - 1) else: warnings.warn(f"Boundary contours already exist, skipping image: {ripple_image.source_file}") continue edges = detect_edges(ripple_image.image) processed_edges = process_edges(edges) contours = find_contours(processed_edges, level=level) ripple_image.add_contour(np.array([contours[0][:, 0], contours[0][:, 1]]), 'Upper Boundary') ripple_image.add_contour(np.array([contours[1][:, 0], contours[1][:, 1]]), 'Lower Boundary')
[docs] def add_a_star_contours(ripple_images: list[RippleImage] | RippleImage | RippleImageSeries, contour_index: list[int] = [0, 1], overwrite: bool = False) -> list[RippleImage]: """ Add A* contours to a list of RippleImage objects. Parameters ---------- ripple_images : list[RippleImage] | RippleImage | RippleImageSeries A list of RippleImage objects or a single RippleImage or RippleImageSeries. contour_index : list[int], optional List of two integers indicating which contours to use, by default [0, 1]. overwrite : bool, optional Whether to overwrite existing A* contours, by default False. Returns ------- list[RippleImage] The list of RippleImage objects with added A* contours. Raises ------ ValueError If contour_index does not have exactly two integers. """ if len(contour_index) != 2: raise ValueError("contour_index must be a list of two integers.") if isinstance(ripple_images, RippleImageSeries): ripple_images = ripple_images.images if isinstance(ripple_images, RippleImage): ripple_images = [ripple_images] for ripple_image in ripple_images: if len(ripple_image.contours) < 2: warnings.warn(f"RippleImage object must have at least two contours, skipping image: {ripple_image.source_file}") continue methods = [contour.method for contour in ripple_image.contours] if 'A* traversal' in methods: if overwrite: warnings.warn(f"Overwriting A* contour for image: {ripple_image.source_file}") for contour in ripple_image.contours: if contour.method == 'A* traversal': ripple_image.contours.remove(contour) else: warnings.warn(f"A* contour already exists, skipping image: {ripple_image.source_file}") continue cont1 = np.flip(ripple_image.contours[contour_index[0]].values).astype(np.int32).T cont2 = np.flip(ripple_image.contours[contour_index[1]].values).astype(np.int32).T contour = combine_contours(cont1, cont2) bounded_img = np.zeros(ripple_image.image.shape, dtype=np.uint8) bounded_img = cv2.drawContours(bounded_img, [contour], 0, (255, 255, 255), -1) d_map = distance_map(bounded_img) start = (np.argmax(d_map[:, 0]), 0) goal = (np.argmax(d_map[:, -1]), d_map.shape[1] - 1) path = a_star(start, goal, d_map) path = np.flip(np.array(path), axis=0).T ripple_image.add_contour(path, 'A* traversal')
[docs] def add_chan_vese_contours(ripple_images: list[RippleImage] | RippleImage | RippleImageSeries, overwrite: bool = False, use_gradients=False, **kwargs): """ Add Chan-Vese contours to a list of RippleImage objects. Parameters ---------- ripple_images : list[RippleImage] | RippleImage | RippleImageSeries A list of RippleImage objects or a single RippleImage or RippleImageSeries. overwrite : bool, optional Whether to overwrite existing Chan-Vese contours, by default False. use_gradients : bool, optional Whether to use image gradients, by default False. **kwargs Additional keyword arguments for the cv_segmentation function. Returns ------- None """ if isinstance(ripple_images, RippleImageSeries): ripple_images = ripple_images.images if isinstance(ripple_images, RippleImage): ripple_images = [ripple_images] for ripple_image in ripple_images: if len(ripple_image.contours) > 0: methods = [contour.method for contour in ripple_image.contours] if 'Chan-Vese' in methods: if overwrite: warnings.warn(f"Overwriting Chan-Vese contour for image: {ripple_image.source_file}") for contour in ripple_image.contours: if contour.method == 'Chan-Vese': ripple_image.contours.remove(contour) else: warnings.warn(f"Chan-Vese contour already exists, skipping image: {ripple_image.source_file}") continue if use_gradients: grad = np.sum(np.abs(np.gradient(ripple_image.image)), axis=0) img = cv2.GaussianBlur(grad / np.max(grad), (7, 7), 0) + (1 - (ripple_image.image / np.max(ripple_image.image))) cv = cv_segmentation(img, **kwargs) else: cv = cv_segmentation(ripple_image.image, **kwargs) contours = find_contours(cv) ripple_image.add_contour(np.array([contours[0][:, 0], contours[0][:, 1]]), 'Chan-Vese')
[docs] def remove_small_bumps(contour: RippleContour, **kwargs) -> RippleContour: """ Remove small bumps from a RippleContour object. Parameters ---------- contour : RippleContour A RippleContour object to be smoothed. **kwargs Additional keyword arguments for the smooth_bumps function. Returns ------- RippleContour The smoothed RippleContour object. """ return smooth_bumps(contour, **kwargs)
[docs] def remove_small_bumps_from_images(ripple_images: list[RippleImage] | RippleImage, **kwargs) -> list[RippleImage]: """ Remove small bumps from a list of RippleImage objects. Parameters ---------- ripple_images : list[RippleImage] | RippleImage A list of RippleImage objects or a single RippleImage. **kwargs Additional keyword arguments for the remove_small_bumps function. Returns ------- list[RippleImage] The list of RippleImage objects with smoothed contours. """ if isinstance(ripple_images, RippleImageSeries): ripple_images = ripple_images.images if isinstance(ripple_images, RippleImage): ripple_images = [ripple_images] for ripple_image in ripple_images: for contour in ripple_image.contours: if contour is not None: remove_small_bumps(contour, **kwargs) return ripple_images