FrontTracker
  • Home

API

  • Front
  • FrontEllipse
  • FrontSkeleton
  • FrontTracker
  • Geometry

Demos

  • Getting Started with FrontTracker
  • Visualizing Labeled Fronts
  • Front Orientation Analysis
  • Front Lifecycle Analysis
FrontTracker
  • Demos
  • Front Lifecycle Analysis

Front Lifecycle Analysis¶

This notebook demonstrates how to use FrontTracker to follow the lifecycle of an oceanic front through its detected path.
The path is constructed by combining automated detection with manual corrections, ensuring continuity across consecutive timesteps.

In this demo, we will:

  1. Build the frontal path (lifecycle) by selecting the appropriate label for each day.
  2. Extract daily metrics for the tracked front, including area, length, width, frontal_line, and gradient magnitude statistics (gm_mean, gm_min, gm_max).
  3. Visualize the lifecycle by plotting the spatial evolution of the front on maps for each day.
  4. Summarize temporal variability with time series of:
    • Frontal area to show changes in spatial extent.
    • Gradient magnitude statistics to highlight variations in frontal intensity.

This notebook provides a step-by-step example of how a single front can be tracked, quantified, and visualized throughout its lifecycle, complementing the static front identification presented in previous demos.

In [1]:
Copied!
# -----------------------------------
# LIBRARY IMPORTS
# -----------------------------------
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from descartes import PolygonPatch

import cartopy
from matplotlib.colors import ListedColormap

from fronttracker import FrontTracker

# -----------------------------------
# LOAD DATA
# -----------------------------------
gm = np.transpose(np.load(".../gm.npy"), (2, 1, 0))
latitude = np.load(".../latitude.npy")
longitude = np.load(".../longitude.npy")
time = np.load(".../time.npy", allow_pickle=True)
time = np.arange(np.datetime64(time[0].strftime("%Y-%m-%d")), np.datetime64(time[-1].strftime("%Y-%m-%d")) + np.timedelta64(1, 'D'),np.timedelta64(1, 'D'))

# -----------------------------------
# FRONT THRESHOLD
# -----------------------------------
treshold = 0.08

# -----------------------------------
# FRONT IDENTIFICATION AND TRACKING
# -----------------------------------

# Initialize the front tracker
front_tracker = FrontTracker()

# Identify fronts in the GM field using the threshold defined earlier
front_tracker.identify_fronts(time, longitude, latitude, gm, treshold=treshold)

# Retrieve the tabular data of all detected front pixels
front_tracker.data
# ----------------------------------- # LIBRARY IMPORTS # ----------------------------------- import numpy as np import pandas as pd import matplotlib.pyplot as plt from descartes import PolygonPatch import cartopy from matplotlib.colors import ListedColormap from fronttracker import FrontTracker # ----------------------------------- # LOAD DATA # ----------------------------------- gm = np.transpose(np.load(".../gm.npy"), (2, 1, 0)) latitude = np.load(".../latitude.npy") longitude = np.load(".../longitude.npy") time = np.load(".../time.npy", allow_pickle=True) time = np.arange(np.datetime64(time[0].strftime("%Y-%m-%d")), np.datetime64(time[-1].strftime("%Y-%m-%d")) + np.timedelta64(1, 'D'),np.timedelta64(1, 'D')) # ----------------------------------- # FRONT THRESHOLD # ----------------------------------- treshold = 0.08 # ----------------------------------- # FRONT IDENTIFICATION AND TRACKING # ----------------------------------- # Initialize the front tracker front_tracker = FrontTracker() # Identify fronts in the GM field using the threshold defined earlier front_tracker.identify_fronts(time, longitude, latitude, gm, treshold=treshold) # Retrieve the tabular data of all detected front pixels front_tracker.data
Out[1]:
time longitude latitude gm labels
0 2023-11-27 100.375 9.125 0.082830 0
1 2023-11-27 100.375 9.375 0.087372 0
2 2023-11-27 100.375 9.625 0.082713 0
3 2023-11-27 100.625 9.375 0.083204 0
4 2023-11-27 103.125 9.875 0.080204 1
... ... ... ... ... ...
8177 2024-04-21 102.875 11.125 0.081668 817
8178 2024-04-21 102.875 11.375 0.133640 817
8179 2024-04-21 102.875 11.625 0.119224 817
8180 2024-04-21 103.625 10.375 0.088009 818
8181 2024-04-21 103.625 10.625 0.086561 818

8182 rows × 5 columns

In [2]:
Copied!
# Link fronts between consecutive time steps (temporal tracking)
front_tracker.track()

# Retrieve a DataFrame that maps each front label to its next label in the tracking path
front_tracker.data_labels
# Link fronts between consecutive time steps (temporal tracking) front_tracker.track() # Retrieve a DataFrame that maps each front label to its next label in the tracking path front_tracker.data_labels
Out[2]:
labels next_labels
0 0 None
4 1 None
5 2 6
7 3 None
23 4 8
... ... ...
8163 814 None
8165 815 None
8166 816 None
8172 817 None
8180 818 None

819 rows × 2 columns

In [3]:
Copied!
# Build the frontal path by combining automated detection with manual corrections
frontal_path = [None]
frontal_path += front_tracker.get_frontal_path(7)
frontal_path += front_tracker.get_frontal_path(548)[:2]
frontal_path += front_tracker.get_frontal_path(559)
frontal_path += front_tracker.get_frontal_path(579)[:3]
frontal_path += front_tracker.get_frontal_path(598)
frontal_path += front_tracker.get_frontal_path(700)
frontal_path += front_tracker.get_frontal_path(728)
frontal_path += front_tracker.get_frontal_path(789)
frontal_path.append(None)

print(f"Frontal path length: {len(frontal_path)}")
print(frontal_path)
# Build the frontal path by combining automated detection with manual corrections frontal_path = [None] frontal_path += front_tracker.get_frontal_path(7) frontal_path += front_tracker.get_frontal_path(548)[:2] frontal_path += front_tracker.get_frontal_path(559) frontal_path += front_tracker.get_frontal_path(579)[:3] frontal_path += front_tracker.get_frontal_path(598) frontal_path += front_tracker.get_frontal_path(700) frontal_path += front_tracker.get_frontal_path(728) frontal_path += front_tracker.get_frontal_path(789) frontal_path.append(None) print(f"Frontal path length: {len(frontal_path)}") print(frontal_path)
Frontal path length: 147
[None, 7, 9, 13, 19, 26, 31, 36, 40, 44, 50, 56, 58, 60, 61, 63, 68, 73, 77, 83, 88, 92, 97, 102, 107, 112, 117, 124, 131, 139, 144, 148, 154, 159, 165, 171, 178, 183, 191, 198, 205, 213, 220, 227, 234, 242, 249, 257, 264, 271, 281, 288, 294, 298, 305, 312, 319, 326, 332, 339, 345, 351, 356, 361, 366, 372, 377, 383, 387, 389, 394, 396, 398, 402, 408, 411, 415, 419, 421, 425, 429, 434, 437, 440, 444, 449, 452, 455, 458, 461, 465, 470, 477, 482, 488, 495, 500, 508, 515, 523, 530, 536, 542, 548, 553, 559, 567, 574, 579, 584, 590, 598, 609, 616, 624, 633, 641, 649, 658, 665, 672, 680, 687, 693, 700, 708, 715, 719, 723, 728, 732, 738, 744, 749, 754, 759, 766, 773, 778, 785, 789, 792, 795, 803, 810, 814, None]
In [4]:
Copied!
# -----------------------------------
# VISUALIZATION SETUP
# -----------------------------------

# Create a large figure with a grid of 11 rows × 14 columns
fig = plt.figure(figsize=(4.8*4, 6.2*4))
nrows, ncols = 11, 14

for k in range(len(time)):
    ax = fig.add_subplot(nrows, ncols, k+1, projection=cartopy.crs.PlateCarree())
    
    land = cartopy.feature.NaturalEarthFeature('physical', 'land', scale="10m", edgecolor='k', facecolor=cartopy.feature.COLORS['land'])
    ax.add_feature(land, facecolor='lightgray', linewidth=0.25)
    gl = ax.gridlines(draw_labels=True, linewidth=0.25, color='gray', alpha=0.5, linestyle='--')
    gl.right_labels = False
    if k % ncols != 0:
        gl.left_labels = False 
    
    # Plot gradient magnitude (GM) field
    pc = ax.pcolor(longitude, latitude, gm[k], vmin=0.0, vmax=0.3, cmap="jet")
    
    # If a frontal path exists for this timestep
    if frontal_path[k] is not None:
        label = frontal_path[k]
        
        # Get all detected fronts at this time
        _df = front_tracker.data[front_tracker.data.time == time[k]]
        uniq = _df.labels.unique()
    
        for _label in uniq:
            front = front_tracker.fronts[_label]
            
            # Try to compute contours
            try:
                front.contouring()
            except Exception:
                continue
                
            # Highlight the tracked front in pink, others in orange
            color = "deeppink" if _label == label else "orange"

            # Draw each polygon of the front contours
            for poly in front.contours:
                x, y = poly.exterior.xy
                ax.plot(x, y, c=color, linewidth=2)
                
    ax.text(102, 4.8, k, color="red", va="center", ha="center", fontsize=16)

plt.show()
# ----------------------------------- # VISUALIZATION SETUP # ----------------------------------- # Create a large figure with a grid of 11 rows × 14 columns fig = plt.figure(figsize=(4.8*4, 6.2*4)) nrows, ncols = 11, 14 for k in range(len(time)): ax = fig.add_subplot(nrows, ncols, k+1, projection=cartopy.crs.PlateCarree()) land = cartopy.feature.NaturalEarthFeature('physical', 'land', scale="10m", edgecolor='k', facecolor=cartopy.feature.COLORS['land']) ax.add_feature(land, facecolor='lightgray', linewidth=0.25) gl = ax.gridlines(draw_labels=True, linewidth=0.25, color='gray', alpha=0.5, linestyle='--') gl.right_labels = False if k % ncols != 0: gl.left_labels = False # Plot gradient magnitude (GM) field pc = ax.pcolor(longitude, latitude, gm[k], vmin=0.0, vmax=0.3, cmap="jet") # If a frontal path exists for this timestep if frontal_path[k] is not None: label = frontal_path[k] # Get all detected fronts at this time _df = front_tracker.data[front_tracker.data.time == time[k]] uniq = _df.labels.unique() for _label in uniq: front = front_tracker.fronts[_label] # Try to compute contours try: front.contouring() except Exception: continue # Highlight the tracked front in pink, others in orange color = "deeppink" if _label == label else "orange" # Draw each polygon of the front contours for poly in front.contours: x, y = poly.exterior.xy ax.plot(x, y, c=color, linewidth=2) ax.text(102, 4.8, k, color="red", va="center", ha="center", fontsize=16) plt.show()
No description has been provided for this image
In [5]:
Copied!
# -----------------------------------
# EXTRACT FRONT METRICS
# -----------------------------------

# Dictionary used to collect daily metrics of the front over its lifecycle
metrics = { "area": [], "width": [], "length": [], "frontal_line": [], "gm_mean": [], "gm_min": [], "gm_max": []}

# Loop over frontal_path (excluding the None separators at start and end)
for k in range(1, len(frontal_path) - 1):
    label = frontal_path[k]
    front = front_tracker.fronts[label]
    
    # Compute metrics for this front
    front.metrics()

    # Store values in the corresponding lists
    metrics["area"].append(front.area)
    metrics["width"].append(front.width)
    metrics["length"].append(front.length)
    metrics["frontal_line"].append(front.skeleton.length)
    metrics["gm_mean"].append(front.gm.mean())
    metrics["gm_min"].append(front.gm.min())
    metrics["gm_max"].append(front.gm.max())

# Convert lists to NumPy arrays and pad with zeros at both ends
for key in metrics:
    metrics[key] = np.array([0] + metrics[key] + [0])
# ----------------------------------- # EXTRACT FRONT METRICS # ----------------------------------- # Dictionary used to collect daily metrics of the front over its lifecycle metrics = { "area": [], "width": [], "length": [], "frontal_line": [], "gm_mean": [], "gm_min": [], "gm_max": []} # Loop over frontal_path (excluding the None separators at start and end) for k in range(1, len(frontal_path) - 1): label = frontal_path[k] front = front_tracker.fronts[label] # Compute metrics for this front front.metrics() # Store values in the corresponding lists metrics["area"].append(front.area) metrics["width"].append(front.width) metrics["length"].append(front.length) metrics["frontal_line"].append(front.skeleton.length) metrics["gm_mean"].append(front.gm.mean()) metrics["gm_min"].append(front.gm.min()) metrics["gm_max"].append(front.gm.max()) # Convert lists to NumPy arrays and pad with zeros at both ends for key in metrics: metrics[key] = np.array([0] + metrics[key] + [0])
In [6]:
Copied!
# -----------------------------------
# PLOT FRONT METRICS
# -----------------------------------

# Create a large figure with a grid of 2 rows × 1 column
fig, axs = plt.subplots(2, 1, figsize=(6.2*2, 4.8*0.75))

# --- Panel 1: Frontal area ---
axs[0].plot(np.arange(0, len(time)), metrics["area"])
axs[0].set_ylim(0,65000)
axs[0].set_yticks(np.arange(0, 60001, 15000))
axs[0].set_ylabel(r"Area (km$^2$)")
axs[0].set_xlim(0,len(time)-1)
axs[0].set_xticks(np.arange(0, 150, 5))

# --- Panel 2: Gradient magnitude statistics ---
axs[1].plot(time[1:-1], metrics["gm_mean"][1:-1])
axs[1].fill_between(time[1:-1], metrics["gm_min"][1:-1], metrics["gm_max"][1:-1], alpha=0.3)
axs[1].set_ylim(0.05,0.3)
axs[1].set_yticks(np.arange(0.05, 0.31, 0.05))
axs[1].set_ylabel(r"GM (°C (10 km)$^{-1}$)")
axs[1].set_xlim(time[0],time[-1])
axs[1].set_xticks(time[::5])
axs[1].tick_params(axis='x', rotation=90)

plt.show()
# ----------------------------------- # PLOT FRONT METRICS # ----------------------------------- # Create a large figure with a grid of 2 rows × 1 column fig, axs = plt.subplots(2, 1, figsize=(6.2*2, 4.8*0.75)) # --- Panel 1: Frontal area --- axs[0].plot(np.arange(0, len(time)), metrics["area"]) axs[0].set_ylim(0,65000) axs[0].set_yticks(np.arange(0, 60001, 15000)) axs[0].set_ylabel(r"Area (km$^2$)") axs[0].set_xlim(0,len(time)-1) axs[0].set_xticks(np.arange(0, 150, 5)) # --- Panel 2: Gradient magnitude statistics --- axs[1].plot(time[1:-1], metrics["gm_mean"][1:-1]) axs[1].fill_between(time[1:-1], metrics["gm_min"][1:-1], metrics["gm_max"][1:-1], alpha=0.3) axs[1].set_ylim(0.05,0.3) axs[1].set_yticks(np.arange(0.05, 0.31, 0.05)) axs[1].set_ylabel(r"GM (°C (10 km)$^{-1}$)") axs[1].set_xlim(time[0],time[-1]) axs[1].set_xticks(time[::5]) axs[1].tick_params(axis='x', rotation=90) plt.show()
No description has been provided for this image
Previous

Built with MkDocs using a theme provided by Read the Docs.
« Previous