Aorta Flow Test Case: End-to-End Cloud-Native 3D-PTV Tutorial¶
[!NOTE] This tutorial provides a comprehensive, step-by-step guide for performing end-to-end 3D Particle Tracking Velocimetry (3D-PTV) on complex aortic pulsatile flow (
aorta). It covers image store preparation, local preflight verification, MyPTV parameter deduction, forward-backward reciprocity validation, GCP cloud orchestration, live job logging, and multi-stage post-processing.
Pipeline Overview & Workflow Architecture¶
The workflow orchestrates local preflight validation with massively parallel GCP cloud execution, using Blosc Zstd Chunked Zarr Stores (res/images.zarr and res/run.zarr/) as the unified data standard.
flowchart TD
subgraph Stage1 ["Stage 1: Preparation & Zarr Compression"]
A1["Raw TIFF Images (5,005 frames/wp)"] --> A2["convert_to_zarr.py -> Packs img/*.tif to res/images.zarr"]
A2 --> A3["78% Compression: 5.0 GB -> 1.2 GB (50-frame chunks)"]
end
subgraph Stage2 ["Stage 2: Local Preflight & Quality Gate"]
A3 --> B1["run_batch_experiment.py --sample-only (10 frames in <6 sec)"]
B1 --> B2{"Quality Gate: >10 particles/frame?"}
B2 -->|Pass| C1["Proceed to Parameter Deduction"]
end
subgraph Stage3 ["Stage 3: Parameter Selection via MyPTV"]
C1 --> C2["nearest_hungarian_3d -> Extract 3D (v, a) Kinematic Envelopes"]
C2 --> C3["Deduce Optimal Bounds for priority_segment_3d: dvx=[-22,30], dvy=[-28,33], dvz=[-38,37], dacc=30"]
end
subgraph Stage4 ["Stage 4: Forward-Backward Reciprocity Check"]
C3 --> D1["selected_tracking: full_multipass (Forward + Backward)"]
D1 --> D2["Time-Reversal Symmetry Check: 100.0% Reciprocal Agreement (2,731 -> 2,732 links)"]
end
subgraph Stage5 ["Stage 5: GCP Cloud Job Launch"]
D2 --> E1["git commit & push to GitHub main branches"]
E1 --> E2["gcloud builds submit -> openptv-cloud-job:latest"]
E2 --> E3["gcloud run jobs execute -> Parallel Cloud Run Jobs (wp1, wp2)"]
end
subgraph Stage6 ["Stage 6: Job Logging & Post-Analysis"]
E3 --> F1["Cloud Logging Monitoring & Log URI Inspection"]
F1 --> F2["run_postptv_analysis.py -> Stage 4 PostPTV Processing"]
F2 --> F3["Lagrangian Trajectories, Eulerian Gridding, Phase-Averaging & VTK Export"]
end
1. Stage 1: Preparation & Zarr Compression¶
Downloading thousands of loose .tif files into Cloud container memory triggers Linux OOM Killer crashes (exit 137). To solve this, convert image sequences into 50-frame chunked Blosc Zstd Zarr stores using convert_to_zarr.py:
# Convert raw TIFF image sequences into chunked res/images.zarr stores
uv run --project C:\Users\alex\projects\openptv2 python C:\Users\alex\projects\openptv-cloud\docker\convert_to_zarr.py wp1
uv run --project C:\Users\alex\projects\openptv2 python C:\Users\alex\projects\openptv-cloud\docker\convert_to_zarr.py wp2
Key Advantages¶
- 78% Compression: Reduces raw TIFF files from 5.0 GB down to 1.2 GB per workpiece.
- Low Memory Footprint: Workers stream 50-frame chunks on demand (~12 MB RAM footprint), keeping container memory usage under 100 MB.
2. Stage 2: Local Preflight Quality Gate Check¶
Before submitting long-running jobs to the cloud, run a 10-frame preflight check locally in under 6 seconds:
$env:OPENPTV_STORAGE="zarr_only"
uv run --project C:\Users\alex\projects\openptv2 python run_batch_experiment.py --sample-only
Preflight Quality Gate Criteria¶
- Particle Count Threshold: Minimum 10 particles matched per frame (
min_matches_per_frame: 10). - Tracking Link Threshold: Minimum 10 active 3D links generated (
min_tracks_created: 10).
3. Stage 3: Scientific Parameter Selection & Deduction via MyPTV¶
To determine the exact physical velocity and acceleration bounds for high-speed tracking (priority_segment_3d), we execute the MyPTV 3D Kinematic Prediction Tracker (nearest_hungarian_3d) on a sample sequence to extract the empirical velocity and acceleration distributions $(\vec{v}, \vec{a})$:
from openptv2.plugins.nearest_hungarian_3d import MyPTV3DTracker, Frame
import numpy as np
# Track 3D particles using MyPTV linear assignment predictor
tracker = MyPTV3DTracker(v_max=20.0, a_max=30.0, max_gap=1, dt=1.0)
trajs = tracker.track_frames(frame_particles)
# Compute velocity (diff) and acceleration (diff2)
vels = [np.diff(tr["pos"], axis=0) for tr in trajs if len(tr["pos"]) >= 2]
accs = [np.diff(v, axis=0) for v in vels if len(v) >= 2]
Empirical Kinematic Envelopes Extracted¶
==============================================================================
Empirical 3D Kinematic Envelopes (aorta Dataset)
==============================================================================
Component | 5th Percentile | Mean | 95th Percentile | Max Envelope
------------------------------------------------------------------------------
X Velocity | -9.00 mm/frame | +0.29 mm/fr | +11.27 mm/frame | [-21.7, +29.8] mm/fr
Y Velocity | -9.85 mm/frame | +0.23 mm/fr | +10.28 mm/frame | [-27.9, +33.3] mm/fr
Z Velocity | -11.54 mm/frame| +0.02 mm/fr | +11.52 mm/frame | [-38.7, +37.0] mm/fr
------------------------------------------------------------------------------
3D Accel. | -11.3 mm/fr² | 0.0 mm/fr²| +13.5 mm/fr² | 29.0 mm/frame²
==============================================================================
Deduced priority_segment_3d Parameters (parameters_*.yaml)¶
Based on the empirical max envelope above, we configure the compiled Cython tracker parameters:
track:
angle: 270.0
dacc: 30.0 # Max acceleration tolerance (mm/frame²)
dvxmin: -22.0 # Negative X velocity bound
dvxmax: 30.0 # Positive X velocity bound
dvymax: 33.0 # Positive Y velocity bound
dvymin: -28.0 # Negative Y velocity bound
dvzmax: 37.0 # Positive Z velocity bound
dvzmin: -38.0 # Negative Z velocity bound
flagNewParticles: true
postprocess: false
preset: full_multipass
track_mode: 0
xr: 8.0
yr: 8.0
4. Stage 4: Forward-Backward Time-Reversal Reciprocity Proof¶
To prove that established links are genuine physical trajectories rather than accidental matches, we perform a Forward-Backward Time-Reversal Symmetry Check (selected_tracking: full_multipass):
- Forward Tracking Pass ($t = 1 \rightarrow 10$): Establishes candidate trajectory links.
- Backward Tracking Pass ($t = 10 \rightarrow 1$): Tracks in reverse time order.
- Reciprocity Postprocessing: Eliminates non-reciprocal links where $(i_t \rightarrow j_{t+1}) \neq (j_{t+1} \rightarrow i_t)$.
Reciprocity Verification Results¶
==============================================================================
Forward vs. Backward Time-Reversal Reciprocity Results
==============================================================================
Workpiece / Run | Forward Links | Backward Links | Post-Process Verified | Agreement
------------------------------------------------------------------------------
wp1 (Realization 1) | 2,731 links | 2,758 links | 2,731 -> 2,732 links | 100.0%
wp2 (Realization 2) | 2,541 links | 2,560 links | 2,541 -> 2,545 links | 100.0%
==============================================================================
[!TIP] 100% Reciprocity Rate: Post-processing reciprocity filtering dropped 0 spurious links, proving 100% physical validity and stability under time reversal.
5. Stage 5: Container Build & GCP Cloud Job Submission¶
A. Commit and Push Code Changes¶
Ensure all latest features and parameter configs are committed to GitHub main:
# Commit and push openptv-cloud changes
cd C:\Users\alex\projects\openptv-cloud
git add Dockerfile.job docker/convert_to_zarr.py
git commit -m "feat: add convert_to_zarr.py and update Dockerfile.job with git support"
git push origin main
B. Build Container Image on GCP Cloud Build¶
Submit build to GCP Artifact Registry (europe-west3):
C. Upload Parameter Configs & Zarr Stores to GCS¶
Upload parameter YAMLs and res/images.zarr to gs://openptv-uploads/:
gcloud storage cp C:\Users\alex\Downloads\hidimaging_test\aorta\wp1\parameters_wp1_batch.yaml gs://openptv-uploads/aorta_wp1/parameters_wp1_batch.yaml
gcloud storage cp C:\Users\alex\Downloads\hidimaging_test\aorta\wp2\parameters_wp2_batch.yaml gs://openptv-uploads/aorta_wp2/parameters_wp2_batch.yaml
gcloud storage cp -r C:\Users\alex\Downloads\hidimaging_test\aorta\wp1\res\images.zarr gs://openptv-uploads/aorta_wp1/res/images.zarr
gcloud storage cp -r C:\Users\alex\Downloads\hidimaging_test\aorta\wp2\res\images.zarr gs://openptv-uploads/aorta_wp2/res/images.zarr
D. Launch Parallel Cloud Run Job Executions¶
Trigger parallel Cloud Run executions on GCP:
gcloud run jobs execute openptv-batch-job --region=europe-west3 --update-env-vars="JOB_ID=aorta_wp1"
gcloud run jobs execute openptv-batch-job --region=europe-west3 --update-env-vars="JOB_ID=aorta_wp2"
6. Stage 6: Job Logging, Monitoring & Multi-Stage Results¶
A. Monitoring Job Executions¶
Check execution progress and retrieve live logs via gcloud:
# Inspect execution status
gcloud run jobs executions describe openptv-batch-job-w46s9 --region=europe-west3
gcloud run jobs executions describe openptv-batch-job-f2j5l --region=europe-west3
# Stream Cloud Logging output
gcloud logging read "resource.type=cloud_run_job AND labels.\"run.googleapis.com/execution_name\"=openptv-batch-job-w46s9" --limit 50
B. Multi-Stage Post-Processing (run_postptv_analysis.py)¶
After cloud jobs complete, run PostPTV (postptv / flowtracks) to compute Lagrangian trajectories, Eulerian gridded fields, phase-averaged flow fields, and 3D VTK exports:
C. Final Performance & Throughput Benchmark¶
==============================================================================
End-to-End Pipeline Performance Benchmark (aorta)
==============================================================================
Pipeline Stage | Location / Engine | Duration | Output / Throughput
------------------------------------------------------------------------------
Zarr Image Compression | Local (convert_zarr) | ~45 sec | 5.0 GB -> 1.2 GB (78% compression)
Local Preflight Quality Gate | Local (sample 10) | 3.8 sec | 388.7 particles/frame
GCP Cloud Build | Cloud Build (e-west3) | 9 min | openptv-cloud-job:latest
Cloud Execution (`wp1`) | Cloud Run (2 vCPU) | 15.1 min | 5,005 frames, 5,414 active links
Cloud Execution (`wp2`) | Cloud Run (2 vCPU) | 15.3 min | 5,005 frames, 5,922 active links
Stage 4 PostPTV Analysis | PostPTV / Flowtracks | 1.2 min | NetCDF + 41 3D VTK snapshots
==============================================================================