4D Object By Change - Customizing the algorithm

The 4D-OBC implementation provided by py4dgeo can be customized similarly to how M3C2 is customized. The fundamental idea is to create your own algorithm class derived from py4dgeo’s RegionGrowingAlgorithm class and override only those parts of the algorithm that you want to change. This notebook introduces the currently existing customization points.

[1]:
import py4dgeo
import numpy as np

import _py4dgeo  # The C++ bindings module for py4dgeo
[2]:
# Load test data
analysis = py4dgeo.SpatiotemporalAnalysis("synthetic.zip")
[3]:
# We are disabling log messages on this tutorial to increase the readability of the output
import logging

logging.disable()

Distance Measure

By default, 4D-OBC uses a normalized Dynamic Time Warping distance measure. You can provide your own Python function, although all the same warnings as with M3C2 apply: Python code will be significantly slower compared to C++ implementations and will be run sequentially even if you are using OpenMP.

[4]:
def custom_distance(params: _py4dgeo.TimeseriesDistanceFunctionData):
    mask = ~np.isnan(params.ts1) & ~np.isnan(params.ts2)
    if not np.any(mask):
        return np.nan

    # Mask the two input arrays
    masked_ts1 = params.ts1[mask]
    masked_ts2 = params.ts2[mask]

    return np.sum(np.abs(masked_ts1 - masked_ts2)) / np.sum(
        np.abs(np.maximum(masked_ts1, masked_ts2))
    )
[5]:
class CustomDistance4DOBC(py4dgeo.RegionGrowingAlgorithm):
    def distance_measure(self):
        return custom_distance
[6]:
analysis.invalidate_results()
objects = CustomDistance4DOBC(
    neighborhood_radius=2.0,
    seed_subsampling=20,
).run(analysis)

The params data structure passed to the distance function contains the following fields: * ts1 and ts2 are the time series to compare which may include NaN values * norm1 and norm2 which are normalization factors

Prioritizing seeds

The 4D-OBC algorithm finds a number of seed locations for region growing and then prioritizes these seeds by sorting them according to a criterion. You can pass your own criterium like this:

[7]:
def sorting_criterion(seed):
    # Choose a random score, resulting in random seed order
    return np.random.rand()
[8]:
class CustomSeedSorting(py4dgeo.RegionGrowingAlgorithm):
    def seed_sorting_scorefunction(self):
        return sorting_criterion
[9]:
analysis.invalidate_results()
objects = CustomSeedSorting(
    neighborhood_radius=2.0,
    seed_subsampling=20,
).run(analysis)

Rejecting grown objects

After the region growing is done, the algorithm class calls it method filter_objects to check whether the object should be used or discarded. The method must return True (keep) or False (discard):

[10]:
class RejectSmallObjects(py4dgeo.RegionGrowingAlgorithm):
    def filter_objects(self, obj):
        return len(obj.indices) > 10
[11]:
analysis.invalidate_results()
objects = RejectSmallObjects(
    neighborhood_radius=2.0,
    seed_subsampling=20,
).run(analysis)

Seed point detection

If you would like to run an entirely different algorithm to determine the seeds for region growing, you can do so by overriding find_seedpoints:

[12]:
from py4dgeo.segmentation import RegionGrowingSeed
[13]:
class DifferentSeeds(py4dgeo.RegionGrowingAlgorithm):
    def find_seedpoints(self):
        # Use one seed for corepoint 0 and the entire time interval
        return [RegionGrowingSeed(0, 0, self.analysis.distances.shape[1] - 1)]
[14]:
analysis.invalidate_results()
objects = DifferentSeeds(
    neighborhood_radius=2.0,
    seed_subsampling=20,
).run(analysis)