PyStormTracker 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pystormtracker-0.3.0/.gitattributes +0 -0
- pystormtracker-0.3.0/.github/dependabot.yml +16 -0
- pystormtracker-0.3.0/.github/workflows/ci.yml +126 -0
- pystormtracker-0.3.0/.github/workflows/docker-publish.yml +70 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/.github/workflows/python-publish.yml +3 -3
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/.gitignore +6 -3
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/.pre-commit-config.yaml +4 -6
- pystormtracker-0.3.0/.readthedocs.yaml +19 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/CITATION.cff +2 -2
- pystormtracker-0.3.0/Dockerfile +32 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/PKG-INFO +78 -38
- pystormtracker-0.3.0/README.md +149 -0
- pystormtracker-0.3.0/codecov.yml +13 -0
- pystormtracker-0.3.0/data/test/tracks/era5_msl_2.5x2.5_v0.0.2_imilast.txt +38464 -0
- pystormtracker-0.3.0/docs/IntercomparisonProtocol.pdf +0 -0
- pystormtracker-0.3.0/docs/conf.py +29 -0
- pystormtracker-0.3.0/docs/index.md +21 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/pyproject.toml +60 -20
- pystormtracker-0.3.0/src/pystormtracker/data.py +66 -0
- pystormtracker-0.3.0/src/pystormtracker/models/__init__.py +6 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/src/pystormtracker/models/center.py +25 -23
- pystormtracker-0.3.0/src/pystormtracker/models/grid.py +36 -0
- pystormtracker-0.3.0/src/pystormtracker/models/time.py +14 -0
- pystormtracker-0.3.0/src/pystormtracker/models/tracks.py +189 -0
- pystormtracker-0.3.0/src/pystormtracker/simple/detector.py +271 -0
- pystormtracker-0.3.0/src/pystormtracker/simple/linker.py +135 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/src/pystormtracker/stormtracker.py +84 -84
- pystormtracker-0.3.0/tests/conftest.py +30 -0
- pystormtracker-0.3.0/tests/test_center.py +52 -0
- pystormtracker-0.3.0/tests/test_integration.py +197 -0
- pystormtracker-0.3.0/tests/test_simple_detector.py +49 -0
- pystormtracker-0.3.0/tests/test_simple_linker.py +54 -0
- pystormtracker-0.3.0/tests/test_stormtracker.py +94 -0
- pystormtracker-0.3.0/tests/test_tracks.py +124 -0
- pystormtracker-0.3.0/uv.lock +2565 -0
- pystormtracker-0.2.2/.gitattributes +0 -1
- pystormtracker-0.2.2/README.md +0 -111
- pystormtracker-0.2.2/notebooks/detect_center_latlon.ipynb +0 -149
- pystormtracker-0.2.2/notebooks/detect_center_latlon_img_0.png +0 -0
- pystormtracker-0.2.2/notebooks/detect_center_latlon_img_1.png +0 -0
- pystormtracker-0.2.2/notebooks/plot_tracks.ipynb +0 -117
- pystormtracker-0.2.2/notebooks/plot_tracks_img_0.png +0 -0
- pystormtracker-0.2.2/scripts/run_cesm.sh +0 -6
- pystormtracker-0.2.2/scripts/run_cesm_yellowstone.sh +0 -18
- pystormtracker-0.2.2/scripts/run_erai_msl.sh +0 -8
- pystormtracker-0.2.2/scripts/run_erai_vo.sh +0 -8
- pystormtracker-0.2.2/scripts/run_ncep.sh +0 -6
- pystormtracker-0.2.2/src/pystormtracker/models/__init__.py +0 -5
- pystormtracker-0.2.2/src/pystormtracker/models/grid.py +0 -40
- pystormtracker-0.2.2/src/pystormtracker/models/tracks.py +0 -29
- pystormtracker-0.2.2/src/pystormtracker/simple/detector.py +0 -315
- pystormtracker-0.2.2/src/pystormtracker/simple/linker.py +0 -104
- pystormtracker-0.2.2/tests/test_center.py +0 -48
- pystormtracker-0.2.2/tests/test_integration.py +0 -84
- pystormtracker-0.2.2/tests/test_simple_detector.py +0 -60
- pystormtracker-0.2.2/tests/test_simple_linker.py +0 -85
- pystormtracker-0.2.2/tests/test_tracks.py +0 -39
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/LICENSE +0 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/src/pystormtracker/__init__.py +0 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/src/pystormtracker/simple/__init__.py +0 -0
- {pystormtracker-0.2.2 → pystormtracker-0.3.0}/tests/__init__.py +0 -0
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "pip"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "daily"
|
|
12
|
+
|
|
13
|
+
- package-ecosystem: "github-actions"
|
|
14
|
+
directory: "/"
|
|
15
|
+
schedule:
|
|
16
|
+
interval: "daily"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, dev]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, dev]
|
|
8
|
+
types: [opened, synchronize, reopened]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
prepare:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
# Skip PR CI if the head branch is 'dev' to avoid double runs (push trigger covers it)
|
|
18
|
+
if: github.event_name == 'push' || github.event.pull_request.head.ref != 'dev'
|
|
19
|
+
outputs:
|
|
20
|
+
python-versions: ${{ steps.versions.outputs.value }}
|
|
21
|
+
steps:
|
|
22
|
+
- id: versions
|
|
23
|
+
run: echo 'value=["3.11", "3.12", "3.13", "3.14"]' >> $GITHUB_OUTPUT
|
|
24
|
+
|
|
25
|
+
ruff-lint:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
if: github.event_name == 'push' || github.event.pull_request.head.ref != 'dev'
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- name: Set up Python
|
|
31
|
+
uses: actions/setup-python@v6
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.11"
|
|
34
|
+
cache: "pip"
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: pip install ruff
|
|
37
|
+
- name: Lint with Ruff
|
|
38
|
+
run: ruff check .
|
|
39
|
+
|
|
40
|
+
ruff-format:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
if: github.event_name == 'push' || github.event.pull_request.head.ref != 'dev'
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
- name: Set up Python
|
|
46
|
+
uses: actions/setup-python@v6
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.11"
|
|
49
|
+
cache: "pip"
|
|
50
|
+
- name: Install dependencies
|
|
51
|
+
run: pip install ruff
|
|
52
|
+
- name: Check formatting with Ruff
|
|
53
|
+
run: ruff format --check .
|
|
54
|
+
|
|
55
|
+
mypy-typecheck:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
if: github.event_name == 'push' || github.event.pull_request.head.ref != 'dev'
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
- name: Set up Python
|
|
61
|
+
uses: actions/setup-python@v6
|
|
62
|
+
with:
|
|
63
|
+
python-version: "3.11"
|
|
64
|
+
cache: "pip"
|
|
65
|
+
- name: Install dependencies
|
|
66
|
+
run: |
|
|
67
|
+
python -m pip install --upgrade pip
|
|
68
|
+
pip install .[dev]
|
|
69
|
+
- name: Type check with Mypy
|
|
70
|
+
run: mypy src/ tests/
|
|
71
|
+
|
|
72
|
+
unit-tests:
|
|
73
|
+
name: unit-tests (Python ${{ matrix.python-version }})
|
|
74
|
+
needs: prepare
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
strategy:
|
|
77
|
+
matrix:
|
|
78
|
+
python-version: ${{ fromJSON(needs.prepare.outputs.python-versions) }}
|
|
79
|
+
steps:
|
|
80
|
+
- uses: actions/checkout@v4
|
|
81
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
82
|
+
uses: actions/setup-python@v6
|
|
83
|
+
with:
|
|
84
|
+
python-version: ${{ matrix.python-version }}
|
|
85
|
+
cache: "pip"
|
|
86
|
+
- name: Install dependencies
|
|
87
|
+
run: |
|
|
88
|
+
python -m pip install --upgrade pip
|
|
89
|
+
pip install .[dev]
|
|
90
|
+
- name: Run Unit Tests
|
|
91
|
+
run: |
|
|
92
|
+
pytest -vv
|
|
93
|
+
- name: Upload coverage reports to Codecov
|
|
94
|
+
uses: codecov/codecov-action@v5
|
|
95
|
+
with:
|
|
96
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
97
|
+
|
|
98
|
+
integration-tests:
|
|
99
|
+
name: integration-tests (Python ${{ matrix.python-version }})
|
|
100
|
+
needs: prepare
|
|
101
|
+
runs-on: ubuntu-latest
|
|
102
|
+
strategy:
|
|
103
|
+
matrix:
|
|
104
|
+
python-version: ${{ fromJSON(needs.prepare.outputs.python-versions) }}
|
|
105
|
+
steps:
|
|
106
|
+
- uses: actions/checkout@v4
|
|
107
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
108
|
+
uses: actions/setup-python@v6
|
|
109
|
+
with:
|
|
110
|
+
python-version: ${{ matrix.python-version }}
|
|
111
|
+
cache: "pip"
|
|
112
|
+
- name: Install MPI
|
|
113
|
+
run: |
|
|
114
|
+
sudo apt-get update
|
|
115
|
+
sudo apt-get install -y openmpi-bin libopenmpi-dev
|
|
116
|
+
- name: Install dependencies
|
|
117
|
+
run: |
|
|
118
|
+
python -m pip install --upgrade pip
|
|
119
|
+
pip install .[dev]
|
|
120
|
+
- name: Run Integration Tests
|
|
121
|
+
run: |
|
|
122
|
+
pytest -vv tests/test_integration.py --run-all
|
|
123
|
+
- name: Upload coverage reports to Codecov
|
|
124
|
+
uses: codecov/codecov-action@v5
|
|
125
|
+
with:
|
|
126
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
name: Docker Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_run:
|
|
5
|
+
workflows: ["CI"]
|
|
6
|
+
types: [completed]
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
DOCKER_HUB_REPO: xddd/pystormtracker
|
|
10
|
+
GHCR_REPO: ghcr.io/xddd/pystormtracker
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build-and-push:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
packages: write
|
|
18
|
+
# Only run if CI was successful AND it was a push event to main or dev
|
|
19
|
+
# Added head_repository check to prevent checkout of untrusted code in trusted context
|
|
20
|
+
if: >
|
|
21
|
+
github.event.workflow_run.conclusion == 'success' &&
|
|
22
|
+
github.event.workflow_run.head_repository.full_name == github.repository &&
|
|
23
|
+
(github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'workflow_dispatch') &&
|
|
24
|
+
(github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'dev')
|
|
25
|
+
|
|
26
|
+
steps:
|
|
27
|
+
- name: Checkout repository
|
|
28
|
+
# Use the commit from the completed workflow run
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
ref: ${{ github.event.workflow_run.head_sha }}
|
|
32
|
+
|
|
33
|
+
- name: Set up Docker Buildx
|
|
34
|
+
uses: docker/setup-buildx-action@v4
|
|
35
|
+
|
|
36
|
+
- name: Log in to Docker Hub
|
|
37
|
+
uses: docker/login-action@v4
|
|
38
|
+
with:
|
|
39
|
+
username: ${{ vars.DOCKER_HUB_USERNAME }}
|
|
40
|
+
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
41
|
+
|
|
42
|
+
- name: Log in to GHCR
|
|
43
|
+
uses: docker/login-action@v4
|
|
44
|
+
with:
|
|
45
|
+
registry: ghcr.io
|
|
46
|
+
username: ${{ github.actor }}
|
|
47
|
+
password: ${{ secrets.GHCR_PAT }}
|
|
48
|
+
|
|
49
|
+
- name: Extract Docker metadata
|
|
50
|
+
id: meta
|
|
51
|
+
uses: docker/metadata-action@v5
|
|
52
|
+
with:
|
|
53
|
+
images: |
|
|
54
|
+
${{ env.DOCKER_HUB_REPO }}
|
|
55
|
+
${{ env.GHCR_REPO }}
|
|
56
|
+
tags: |
|
|
57
|
+
type=raw,value=dev,pattern=dev
|
|
58
|
+
type=raw,value=latest,pattern=main
|
|
59
|
+
type=semver,pattern={{version}},pattern=main
|
|
60
|
+
type=semver,pattern={{major}}.{{minor}},pattern=main
|
|
61
|
+
|
|
62
|
+
- name: Build and push Docker image
|
|
63
|
+
uses: docker/build-push-action@v7
|
|
64
|
+
with:
|
|
65
|
+
context: .
|
|
66
|
+
push: true
|
|
67
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
68
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
69
|
+
cache-from: type=gha
|
|
70
|
+
cache-to: type=gha,mode=max
|
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
steps:
|
|
18
18
|
- uses: actions/checkout@v4
|
|
19
19
|
|
|
20
|
-
- uses: actions/setup-python@
|
|
20
|
+
- uses: actions/setup-python@v6
|
|
21
21
|
with:
|
|
22
22
|
python-version: "3.x"
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
python -m build
|
|
28
28
|
|
|
29
29
|
- name: Upload distributions
|
|
30
|
-
uses: actions/upload-artifact@
|
|
30
|
+
uses: actions/upload-artifact@v7
|
|
31
31
|
with:
|
|
32
32
|
name: release-dists
|
|
33
33
|
path: dist/
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
echo "version=${VERSION#v}" >> $GITHUB_OUTPUT
|
|
55
55
|
|
|
56
56
|
- name: Retrieve release distributions
|
|
57
|
-
uses: actions/download-artifact@
|
|
57
|
+
uses: actions/download-artifact@v8
|
|
58
58
|
with:
|
|
59
59
|
name: release-dists
|
|
60
60
|
path: dist/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
repos:
|
|
2
2
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
-
rev:
|
|
3
|
+
rev: v5.0.0
|
|
4
4
|
hooks:
|
|
5
5
|
- id: trailing-whitespace
|
|
6
6
|
- id: end-of-file-fixer
|
|
@@ -9,16 +9,14 @@ repos:
|
|
|
9
9
|
- id: debug-statements
|
|
10
10
|
|
|
11
11
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
12
|
-
rev: v0.
|
|
12
|
+
rev: v0.9.9
|
|
13
13
|
hooks:
|
|
14
|
-
# Run the linter.
|
|
15
14
|
- id: ruff
|
|
16
15
|
args: [ --fix ]
|
|
17
|
-
# Run the formatter.
|
|
18
16
|
- id: ruff-format
|
|
19
17
|
|
|
20
18
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
21
|
-
rev: v1.
|
|
19
|
+
rev: v1.15.0
|
|
22
20
|
hooks:
|
|
23
21
|
- id: mypy
|
|
24
|
-
additional_dependencies: [
|
|
22
|
+
additional_dependencies: [numpy, pooch, scipy, pytest, pandas, xarray, dask, distributed, h5netcdf]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Read the Docs configuration file
|
|
2
|
+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
3
|
+
|
|
4
|
+
version: 2
|
|
5
|
+
|
|
6
|
+
build:
|
|
7
|
+
os: ubuntu-24.04
|
|
8
|
+
tools:
|
|
9
|
+
python: "3.14"
|
|
10
|
+
|
|
11
|
+
python:
|
|
12
|
+
install:
|
|
13
|
+
- method: pip
|
|
14
|
+
path: .
|
|
15
|
+
extra_requirements:
|
|
16
|
+
- dev
|
|
17
|
+
|
|
18
|
+
sphinx:
|
|
19
|
+
configuration: docs/conf.py
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Use Python 3.14 slim as the base image
|
|
2
|
+
FROM python:3.14-slim
|
|
3
|
+
|
|
4
|
+
# Install system dependencies for MPI and building packages
|
|
5
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
6
|
+
build-essential \
|
|
7
|
+
libopenmpi-dev \
|
|
8
|
+
openmpi-bin \
|
|
9
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
10
|
+
|
|
11
|
+
# Set working directory
|
|
12
|
+
WORKDIR /app
|
|
13
|
+
|
|
14
|
+
# Create data directory for mounting
|
|
15
|
+
RUN mkdir /data && chmod 777 /data
|
|
16
|
+
|
|
17
|
+
# Copy the project files
|
|
18
|
+
COPY . .
|
|
19
|
+
|
|
20
|
+
# Install the package and its dependencies
|
|
21
|
+
RUN pip install --no-cache-dir .
|
|
22
|
+
|
|
23
|
+
# Add a non-root user for security
|
|
24
|
+
RUN useradd -m pst
|
|
25
|
+
USER pst
|
|
26
|
+
|
|
27
|
+
# Volume for data persistence
|
|
28
|
+
VOLUME /data
|
|
29
|
+
|
|
30
|
+
# Default command
|
|
31
|
+
ENTRYPOINT ["stormtracker"]
|
|
32
|
+
CMD ["--help"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyStormTracker
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A Parallel Object-Oriented Cyclone Tracker in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/mwyau/PyStormTracker
|
|
6
6
|
Project-URL: Repository, https://github.com/mwyau/PyStormTracker.git
|
|
@@ -13,57 +13,72 @@ Classifier: Development Status :: 4 - Beta
|
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
14
|
Classifier: License :: OSI Approved :: BSD License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
20
|
-
Requires-Python: >=3.
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist: numpy>=1.24.0
|
|
25
|
-
Requires-Dist: scipy>=1.10.0
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: numpy>=1.23.0
|
|
23
|
+
Requires-Dist: scipy>=1.9.0
|
|
24
|
+
Requires-Dist: xarray[accel,io,parallel]>=2024.9.0
|
|
26
25
|
Provides-Extra: dev
|
|
27
|
-
Requires-Dist: mpi4py>=
|
|
28
|
-
Requires-Dist: mypy>=1.
|
|
29
|
-
Requires-Dist:
|
|
26
|
+
Requires-Dist: mpi4py>=4.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.15.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: myst-parser>=2.0.0; extra == 'dev'
|
|
30
29
|
Requires-Dist: pandas>=2.2.0; extra == 'dev'
|
|
31
|
-
Requires-Dist: pre-commit>=
|
|
30
|
+
Requires-Dist: pre-commit>=4.0.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
32
32
|
Requires-Dist: pytest>=8.1.1; extra == 'dev'
|
|
33
|
-
Requires-Dist: ruff>=0.
|
|
33
|
+
Requires-Dist: ruff>=0.9.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: sphinx>=7.2.0; extra == 'dev'
|
|
34
36
|
Description-Content-Type: text/markdown
|
|
35
37
|
|
|
36
38
|
# PyStormTracker
|
|
37
39
|
|
|
40
|
+
[](https://github.com/mwyau/PyStormTracker/actions/workflows/ci.yml)
|
|
41
|
+
[](https://pystormtracker.readthedocs.io/en/latest/?badge=latest)
|
|
42
|
+
[](https://codecov.io/github/mwyau/PyStormTracker)
|
|
43
|
+
[](https://pypi.org/project/PyStormTracker/)
|
|
44
|
+

|
|
45
|
+

|
|
46
|
+
[](https://hub.docker.com/r/xddd/pystormtracker)
|
|
47
|
+
[](https://github.com/orgs/xddd/packages/container/package/pystormtracker)
|
|
38
48
|
[](https://doi.org/10.5281/zenodo.18764813)
|
|
39
49
|
|
|
40
50
|
PyStormTracker provides the implementation of the "Simple Tracker" algorithm used for cyclone trajectory analysis in **Yau and Chang (2020)**. It was originally developed at the **National Center for Atmospheric Research (NCAR)** as part of the **2015 Summer Internships in Parallel Computational Science (SIParCS)** program, utilizing a task-parallel strategy with temporal decomposition and a tree reduction algorithm to process large climate datasets.
|
|
41
51
|
|
|
42
52
|
## Features
|
|
43
53
|
|
|
44
|
-
- **Modern Python
|
|
45
|
-
- **
|
|
54
|
+
- **Modern Python Support**: Strictly targets **Python 3.11+** with comprehensive type hints and 100% strict `mypy` compliance.
|
|
55
|
+
- **Xarray Integrated**: Leverages `xarray` with optimized I/O, acceleration, and parallel extras for robust, high-performance coordinate-aware processing and lazy data loading.
|
|
46
56
|
- **Parallel Backends**:
|
|
47
57
|
- **Dask (Default)**: Automatically scales to all available CPU cores on local machines.
|
|
48
58
|
- **MPI**: Supports distributed execution via `mpi4py`.
|
|
49
59
|
- **Serial**: Standard sequential execution for smaller datasets or debugging.
|
|
50
|
-
- **Robust Detection**:
|
|
51
|
-
- **
|
|
60
|
+
- **Robust Detection**: Optimized $O(N)$ extrema filtering and robust handling of masked/missing data.
|
|
61
|
+
- **CI/CD Integrated**: Automated linting, type-checking, and code coverage reporting via GitHub Actions.
|
|
62
|
+
- **Standardized Output**: Results are exported to the IMILAST intercomparison format (.txt) with readable datetime strings and formatted numeric values.
|
|
52
63
|
|
|
53
64
|
## Technical Methodology
|
|
54
65
|
|
|
55
66
|
PyStormTracker treats meteorological fields as 2D images and leverages `scipy.ndimage` for robust feature detection:
|
|
56
67
|
|
|
57
|
-
- **Local Extrema Detection**: Uses
|
|
68
|
+
- **Local Extrema Detection**: Uses an optimized sliding window filter to identify local minima (cyclones) or maxima (anticyclones/vorticity).
|
|
58
69
|
- **Intensity & Refinement**: Applies the **Laplacian operator** (`laplace`) to measure the "sharpness" of the field at each detected center point. This is used to resolve duplicates and ensure only the most physically intense point is kept when multiple adjacent pixels are flagged.
|
|
59
70
|
- **Spherical Continuity**: Utilizes `mode='wrap'` for all filters to correctly handle periodic boundaries across the Prime Meridian, enabling seamless tracking across the entire globe.
|
|
60
71
|
- **Heuristic Linking**: Implements a nearest-neighbor linking strategy to connect detected centers into trajectories across successive time steps.
|
|
61
72
|
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
Full documentation, including API references and advanced usage examples, is available at [pystormtracker.readthedocs.io](https://pystormtracker.readthedocs.io/).
|
|
76
|
+
|
|
62
77
|
## Installation
|
|
63
78
|
|
|
64
79
|
### Prerequisites
|
|
65
|
-
- Python 3.
|
|
66
|
-
- (Optional)
|
|
80
|
+
- Python 3.11+
|
|
81
|
+
- (Optional) OpenMPI for MPI support.
|
|
67
82
|
|
|
68
83
|
### From PyPI (Recommended)
|
|
69
84
|
You can install the latest stable version of PyStormTracker directly from PyPI:
|
|
@@ -78,9 +93,10 @@ pip install PyStormTracker
|
|
|
78
93
|
cd PyStormTracker
|
|
79
94
|
```
|
|
80
95
|
|
|
81
|
-
2. Install
|
|
96
|
+
2. Install with **uv** (Recommended):
|
|
82
97
|
```bash
|
|
83
|
-
|
|
98
|
+
uv sync --extra dev
|
|
99
|
+
uv run pre-commit install --hook-type pre-push
|
|
84
100
|
```
|
|
85
101
|
|
|
86
102
|
## Usage
|
|
@@ -88,7 +104,7 @@ pip install PyStormTracker
|
|
|
88
104
|
Once installed, you can use the `stormtracker` command directly:
|
|
89
105
|
|
|
90
106
|
```bash
|
|
91
|
-
stormtracker -i
|
|
107
|
+
stormtracker -i era5_msl_2.5x2.5.nc -v msl -o my_tracks
|
|
92
108
|
```
|
|
93
109
|
|
|
94
110
|
### Command Line Arguments
|
|
@@ -96,50 +112,74 @@ stormtracker -i data/test/slp.2012.nc -v slp -o my_tracks
|
|
|
96
112
|
| Argument | Short | Description |
|
|
97
113
|
| :--- | :--- | :--- |
|
|
98
114
|
| `--input` | `-i` | **Required.** Path to the input NetCDF file. |
|
|
99
|
-
| `--var` | `-v` | **Required.** Variable name to track (e.g., `
|
|
100
|
-
| `--output` | `-o` | **Required.** Path to the output
|
|
115
|
+
| `--var` | `-v` | **Required.** Variable name to track (e.g., `msl`, `vo`). |
|
|
116
|
+
| `--output` | `-o` | **Required.** Path to the output track file (appends `.txt` if missing). |
|
|
101
117
|
| `--num` | `-n` | Number of time steps to process. |
|
|
102
118
|
| `--mode` | `-m` | `min` (default) for low pressure, `max` for vorticity/high pressure. |
|
|
103
119
|
| `--backend` | `-b` | `dask` (default), `serial`, or `mpi`. |
|
|
104
120
|
| `--workers` | `-w` | Number of Dask workers (defaults to CPU core count). |
|
|
105
121
|
|
|
106
|
-
|
|
122
|
+
## Development
|
|
107
123
|
|
|
108
|
-
|
|
124
|
+
### Setup
|
|
125
|
+
Using **uv** is the recommended way to set up your environment:
|
|
109
126
|
```bash
|
|
110
|
-
|
|
127
|
+
# Install dependencies and sync virtual environment
|
|
128
|
+
uv sync --extra dev
|
|
129
|
+
|
|
130
|
+
# Install pre-push hooks
|
|
131
|
+
uv run pre-commit install --hook-type pre-push
|
|
111
132
|
```
|
|
112
133
|
|
|
113
|
-
|
|
134
|
+
### Quality Control
|
|
135
|
+
Run automated checks using **uv run**:
|
|
136
|
+
|
|
137
|
+
**Linting & Formatting:**
|
|
114
138
|
```bash
|
|
115
|
-
|
|
139
|
+
uv run ruff check . --fix
|
|
140
|
+
uv run ruff format .
|
|
116
141
|
```
|
|
117
142
|
|
|
118
|
-
|
|
143
|
+
**Type Checking:**
|
|
144
|
+
```bash
|
|
145
|
+
uv run mypy src/
|
|
146
|
+
```
|
|
119
147
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
-
|
|
148
|
+
### Tiered Testing
|
|
149
|
+
To keep development cycles fast, testing is tiered:
|
|
150
|
+
- **Fast Tests**: Default local runs (skips slow tests).
|
|
151
|
+
- **Slow Tests**: ONLY long-running integration/regression tests.
|
|
152
|
+
- **Full Suite**: Everything (used in CI).
|
|
123
153
|
|
|
124
|
-
|
|
154
|
+
**Run fast unit tests only (Default):**
|
|
155
|
+
```bash
|
|
156
|
+
uv run pytest
|
|
157
|
+
```
|
|
125
158
|
|
|
126
|
-
Run
|
|
159
|
+
**Run ONLY slow/integration tests:**
|
|
160
|
+
```bash
|
|
161
|
+
uv run pytest --run-slow
|
|
162
|
+
```
|
|
127
163
|
|
|
164
|
+
**Run everything:**
|
|
128
165
|
```bash
|
|
129
|
-
pytest
|
|
166
|
+
uv run pytest --run-all
|
|
130
167
|
```
|
|
131
168
|
|
|
132
169
|
## Citations
|
|
133
170
|
|
|
134
171
|
If you use this software in your research, please cite the following:
|
|
135
172
|
|
|
136
|
-
- **Yau, A. M. W., and E. K. M. Chang**, 2020: Finding Storm Track Activity Metrics That Are Highly Correlated with Weather Impacts. Part I: Frameworks for Evaluation and Accumulated Track Activity. *J. Climate*, **33**, 10169–10186, https://doi.org/10.1175/JCLI-D-20-0393.1.
|
|
173
|
+
- **Yau, A. M. W., and E. K. M. Chang**, 2020: Finding Storm Track Activity Metrics That Are Highly Correlated with Weather Impacts. Part I: Frameworks for Evaluation and Accumulated Track Activity. *J. Climate*, **33**, 10169–10186, https://doi.org/10.1175/JCLI-D-20-0393.1.
|
|
137
174
|
|
|
138
175
|
- **Yau, A. M. W.**, 2026: mwyau/PyStormTracker. *Zenodo*, https://doi.org/10.5281/zenodo.18764813.
|
|
139
176
|
|
|
140
177
|
## References
|
|
141
178
|
|
|
142
179
|
- **Yau, A. M. W., K. Paul and J. Dennis**, 2016: PyStormTracker: A Parallel Object-Oriented Cyclone Tracker in Python. *96th American Meteorological Society Annual Meeting*, New Orleans, LA. *Zenodo*, https://doi.org/10.5281/zenodo.18868625.
|
|
180
|
+
- **Neu, U., et al.**, 2013: IMILAST: A Community Effort to Intercompare Extratropical Cyclone Detection and Tracking Algorithms. *Bull. Amer. Meteor. Soc.*, **94**, 529–547, https://doi.org/10.1175/BAMS-D-11-00154.1.
|
|
181
|
+
- **IMILAST Intercomparison Protocol**: https://proclim.scnat.ch/en/activities/project_imilast/intercomparison
|
|
182
|
+
- **IMILAST Data Download**: https://proclim.scnat.ch/en/activities/project_imilast/data_download
|
|
143
183
|
|
|
144
184
|
## License
|
|
145
185
|
|