Skip to content

Comments

In-Situ Cyclone Tracking pipeline#645

Open
mariusaurus wants to merge 75 commits intoNVIDIA:mainfrom
mariusaurus:mkoch/tc_tracking
Open

In-Situ Cyclone Tracking pipeline#645
mariusaurus wants to merge 75 commits intoNVIDIA:mainfrom
mariusaurus:mkoch/tc_tracking

Conversation

@mariusaurus
Copy link
Collaborator

Earth2Studio Pull Request

Description

This recipe demonstrates ensemble forecasting and tropical cyclone tracking using FCN3 or AIFS-ENS with TempestExtremes integration. Additionally, it serves as an example of how to integrate custom downstream analysis tools into an inference pipeline.

This workflow implements an asynchronous execution mode where the CPU runs cyclone detection on completed predictions while the GPU continues producing predictions, enabling tracking with virtually no computational overhead. The pipeline supports three modes: generating ensemble forecasts with inline TC tracking, reproducing individual ensemble members (FCN3 only) to extract atmospheric fields for interesting tracks, and extracting reference tracks from ERA5 reanalysis for validation. Furthermore, it showcases how to integrate custom downstream analysis tools and deal with constraints - such as in-memory file handling and subprocess execution—that can be adapted for other downstream tasks.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.
  • The CHANGELOG.md is up to date with these changes.
  • An issue is linked to this pull request.
  • Assess and address Greptile feedback (AI code review bot for guidance; use discretion, addressing all feedback is not required).

Dependencies

mariusaurus and others added 30 commits December 1, 2025 07:52
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 23, 2026

Greptile Overview

Greptile Summary

This PR adds an in-situ cyclone tracking pipeline that integrates TempestExtremes with Earth2Studio's ensemble forecasting capabilities. The implementation supports asynchronous execution where CPU-based cyclone detection runs in parallel with GPU-based predictions.

Key Changes

  • Added TempestExtremes and AsyncTempestExtremes classes for cyclone tracking integration
  • Implemented ensemble generation mode with inline TC tracking
  • Added coordinate manipulation utilities (tile_xx_to_yy, cat_coords)
  • Created comprehensive test suite and Docker containerization
  • Updated FCN3 model with torch_harmonics version checking

Critical Issue

Import Error: TempestExtremes and AsyncTempestExtremes are added to __all__ in earth2studio/models/dx/__init__.py but are never imported. The classes exist only in recipes/tc_tracking/src/tempest_extremes.py, but the test file attempts to import them from earth2studio.models.dx, which will fail.

Additional Issues

  • Duplicated version check logic in fcn3.py
  • Missing whitespace syntax error in generate_ensembles.py line 153
  • Subprocess execution of user-provided commands in TempestExtremes creates potential security risks
  • Hardcoded /dev/shm path may not exist on all systems

Confidence Score: 2/5

  • This PR has a critical import issue that will cause tests to fail
  • Score of 2 reflects a critical bug where TempestExtremes and AsyncTempestExtremes are exported in earth2studio/models/dx/__init__.py but never imported, causing ImportError. The test file imports from earth2studio.models.dx but the classes are only defined in recipes/tc_tracking/src/tempest_extremes.py. Additionally, there are minor syntax issues and security considerations around subprocess execution.
  • Critical attention needed for earth2studio/models/dx/__init__.py - missing import will break tests. Review recipes/tc_tracking/src/tempest_extremes.py for subprocess security concerns.

Important Files Changed

Filename Overview
earth2studio/models/dx/init.py added TempestExtremes exports to __all__ without importing them, causing ImportError in tests
recipes/tc_tracking/src/tempest_extremes.py implemented TempestExtremes and AsyncTempestExtremes classes for cyclone tracking with subprocess execution and thread pooling
recipes/tc_tracking/src/modes/generate_ensembles.py added ensemble generation mode with cyclone tracking integration and stability checks
earth2studio/models/px/fcn3.py added version check for torch_harmonics with duplicated logic
test/models/dx/test_tempest_extremes.py added comprehensive tests for TempestExtremes, but will fail due to missing imports in dx/init.py

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 files reviewed, 6 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 48 to 59
__all__ = [
"AsyncTempestExtremes",
"ClimateNet",
"CorrDiff",
"CorrDiffCMIP6",
"CorrDiffTaiwan",
"PrecipitationAFNO",
"PrecipitationAFNOv2",
"SolarRadiationAFNO1H",
"SolarRadiationAFNO6H",
"TempestExtremes",
"WindgustAFNO",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TempestExtremes and AsyncTempestExtremes are exported in __all__ but never imported

Suggested change
from earth2studio.models.dx.tempest_extremes import (
AsyncTempestExtremes,
TempestExtremes,
)
from earth2studio.models.dx.tc_tracking import (
TCTrackerVitart,
TCTrackerWuDuan,
)
from earth2studio.models.dx.wind_gust import WindgustAFNO # noqa
__all__ = [
"AsyncTempestExtremes",
"ClimateNet",
"CorrDiff",
"CorrDiffCMIP6",
"CorrDiffTaiwan",
"PrecipitationAFNO",
"PrecipitationAFNOv2",
"SolarRadiationAFNO1H",
"SolarRadiationAFNO6H",
"TempestExtremes",
"WindgustAFNO",
]

"""
for cmd in (self.detect_cmd[0], self.stitch_cmd[0]):
try:
run(cmd, capture_output=True) # noqa: S603
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

running arbitrary commands without validating executable path creates a security risk - user-provided detect_cmd and stitch_cmd are executed via subprocess without any sanitization

Comment on lines +292 to +296
ram_path = "/dev/shm" # noqa: S108
if not os.path.exists(ram_path):
raise ValueError(
"/dev/shm not available. Set use_ram=False and use store_dir to specify a different location." # noqa: S108
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hardcoded path /dev/shm may not exist on all systems (e.g., some containers or non-Linux systems)

Comment on lines +56 to +63
if Version(torch_harmonics.__version__) >= Version("0.8.1"):
from torch_harmonics.disco import cuda_kernels_is_available

_cuda_extension_available = cuda_kernels_is_available()
else:
from importlib.util import find_spec

_cuda_extension_available = find_spec("disco_cuda_extension") is not None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated version check logic - same code appears in lines 38-50 inside the try block

# exit()

# set random state or apply perturbation
if (not "model" in cfg) or (cfg.model == "fcn3"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing whitespace around in operator

Suggested change
if (not "model" in cfg) or (cfg.model == "fcn3"):
if (not "model" in cfg) or (cfg.model == "fcn3"):

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

If TempestExtremes command fails (detected by "EXCEPTION" in output)
"""
# detect nodes
out = run(command, capture_output=True) # noqa: S603
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify that TempestExtremes executables come from trusted sources, as they are executed via subprocess



def tile_xx_to_yy(
xx: torch.Tensor, xx_coords: CoordSystem, yy: torch.Tensor, yy_coords: CoordSystem
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need both yy and yy_coords if I am understanding the function correctly? Only yy_coords should be sufficient I think.

Can the parameters match the same as map coords:

x: torch.Tensor,
input_coords: CoordSystem,
output_coords: CoordSystem,

return CoordSystem(adjusted_coords), mapping


def tile_xx_to_yy(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if tile is the right word we want here as I think this would imply repeating functionality like pyt. Perhaps something like broadcast_coords would be good, and perhaps this can expand in functionality as needed

xx: torch.Tensor, xx_coords: CoordSystem, yy: torch.Tensor, yy_coords: CoordSystem
) -> tuple[torch.Tensor, CoordSystem]:
"""Tile tensor xx to match the leading dimensions of tensor yy and update coords accordingly.
eg if xx has shape (4, 721, 1440) and yy has shape (8, 2, 35, 721, 1440),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is good, and definitely needed. Can you make this and example section of the API doc?

https://github.com/NVIDIA/earth2studio/blob/main/earth2studio/models/dx/corrdiff_cmip6.py#L126

So we get the render in the API docs if needed.

if dim not in coy:
raise ValueError(f"dim {dim} is not in coords: {list(coy)}.")

# fix difference in latitude
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intent of this mapping here? I think it would be maybe better to just having the cat only doing a cat, not intermix some mapping, but unsure about the use case

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this would mean just force the user to first map_coords then cat_coords if needed.

handshake_size,
)
from earth2studio.utils.coords import map_coords, split_coords
from earth2studio.utils.coords import (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is too large to happen in one go... lets split it up a little. Can you start with opening a smaller PR with just the addition of the coord utils.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the respective updates to the hens recipe with the imports. Thanks!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants