-
Notifications
You must be signed in to change notification settings - Fork 4
514 lines (460 loc) · 19.2 KB
/
Copy pathci.yml
File metadata and controls
514 lines (460 loc) · 19.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
name: CI
# Comprehensive CI workflow combining unit tests, integration tests, GPU tests, and code quality checks
#
# Test organization:
# - Unit tests: Run on all PRs and pushes, cross-platform (Ubuntu + Windows), multiple Python versions
# - Integration tests: Run with external data, Ubuntu only
# - GPU tests: Self-hosted runners with CUDA support
# - Code quality: Linting and formatting checks
#
# Test markers (gated by opt-in pytest flags; default = skip):
# - slow -> --run-slow
# - requires_gpu -> --run-gpu
# - requires_simpleware -> --run-simpleware (also implies GPU)
# - requires_physicsnemo -> --run-physicsnemo (nvidia-physicsnemo is a base
# dependency; no extra needed)
# - tutorial -> --run-tutorials
# --run-all enables every bucket above at once.
# Tests that need external data download it automatically via fixtures.
on:
push:
branches: [ main, master, develop ]
pull_request:
branches: [ main, master, develop ]
workflow_dispatch:
jobs:
# ============================================================================
# Cross-Platform Unit Tests (Ubuntu + Windows)
# ============================================================================
unit-tests:
name: Unit Tests (${{ matrix.os }}, Python ${{ matrix.python-version }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ['3.11', '3.12', '3.13']
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
lfs: true
- name: Free up disk space on Ubuntu
if: matrix.os == 'ubuntu-latest'
run: |
echo "Disk space before cleanup:"
df -h
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/local/lib/android
sudo rm -rf /opt/ghc
sudo rm -rf /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
echo "Disk space after cleanup:"
df -h
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: pyproject.toml
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
${{ runner.os }}-pip-
- name: Install system dependencies (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y \
libgl1 \
libglib2.0-0 \
libgomp1 \
libsm6 \
libxrender1 \
libxext6 \
libxrandr2 \
libxi6 \
xvfb \
libosmesa6
sudo apt-get clean
sudo rm -rf /var/lib/apt/lists/*
- name: Upgrade pip and build tools
run: |
python -m pip install --upgrade pip setuptools wheel
- name: Install package with test dependencies
# torch-scatter (a base dependency) compiles against torch and needs
# build isolation disabled; pip has no per-package flag for this, so
# --no-build-isolation applies to the whole install (setuptools/wheel
# are already present from the previous step). torch must already be
# importable before that build runs, so install it first.
# torch's headers require C++20 (std::strong_ordering, designated
# initializers, default bit-field member initializers); MSVC defaults to
# an older standard and torch-scatter's setup.py does not request C++20
# itself. CL is read by cl.exe, CXXFLAGS by gcc/g++; each compiler
# ignores the env var meant for the other.
env:
CL: "/std:c++20"
CXXFLAGS: "-std=c++20"
run: |
pip install torch
pip install -e ".[dev]" --no-build-isolation
- name: Clear pip cache
run: |
pip cache purge || true
- name: List installed packages
run: |
pip list
- name: Run unit tests (fast, no GPU/slow/tutorial) - Ubuntu
if: matrix.os == 'ubuntu-latest'
run: |
xvfb-run -a --server-args="-screen 0 1024x768x24" \
pytest tests/ -v --cov=monai_physio --cov-report=xml --cov-report=term --cov-report=html
- name: Run unit tests (fast, no GPU/slow/tutorial) - Windows
if: matrix.os == 'windows-latest'
run: |
pytest tests/ -v --cov=monai_physio --cov-report=xml --cov-report=term --cov-report=html
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
name: codecov-unit-${{ matrix.os }}-py${{ matrix.python-version }}
fail_ci_if_error: false
- name: Upload coverage artifacts
uses: actions/upload-artifact@v6
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
with:
name: coverage-report-unit-tests
path: htmlcov/
retention-days: 7
# ============================================================================
# Integration Tests with External Data (Ubuntu only, on PRs)
# ============================================================================
integration-tests:
name: Integration Tests (with data)
runs-on: ubuntu-latest
needs: unit-tests
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
lfs: true
- name: Free up disk space
run: |
echo "Disk space before cleanup:"
df -h
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/local/lib/android
sudo rm -rf /opt/ghc
sudo rm -rf /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
echo "Disk space after cleanup:"
df -h
- name: Set up Python 3.11
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: pyproject.toml
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-integration-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-integration-
${{ runner.os }}-pip-
- name: Cache test data
uses: actions/cache@v4
with:
path: |
tests/data/
tests/results/
key: test-data-${{ hashFiles('tests/test_*.py') }}-v2
restore-keys: |
test-data-
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libgl1 \
libglib2.0-0 \
libgomp1 \
libsm6 \
libxrender1 \
libxext6 \
libxrandr2 \
libxi6 \
xvfb \
libosmesa6
sudo apt-get clean
sudo rm -rf /var/lib/apt/lists/*
- name: Upgrade pip and build tools
run: |
python -m pip install --upgrade pip setuptools wheel
- name: Install package with test dependencies
# torch-scatter (a base dependency) compiles against torch and needs
# build isolation disabled; pip has no per-package flag for this, so
# --no-build-isolation applies to the whole install (setuptools/wheel
# are already present from the previous step). torch must already be
# importable before that build runs, so install it first.
# torch's headers require C++20 (std::strong_ordering, designated
# initializers, default bit-field member initializers); MSVC defaults to
# an older standard and torch-scatter's setup.py does not request C++20
# itself. CL is read by cl.exe, CXXFLAGS by gcc/g++; each compiler
# ignores the env var meant for the other.
env:
CL: "/std:c++20"
CXXFLAGS: "-std=c++20"
run: |
pip install torch
pip install -e ".[dev]" --no-build-isolation
- name: Clear pip cache
run: |
pip cache purge || true
- name: Run data download tests
run: |
pytest tests/test_download_heart_data.py -v --cov=monai_physio --cov-report=xml
continue-on-error: true
- name: Run data conversion tests
run: |
pytest tests/test_convert_image_4d_to_3d.py -v --cov=monai_physio --cov-append --cov-report=xml
continue-on-error: true
- name: Run contour tools tests
run: |
pytest tests/test_contour_tools.py -v --cov=monai_physio --cov-append --cov-report=xml
continue-on-error: true
- name: Run USD conversion tests
run: |
xvfb-run -a --server-args="-screen 0 1024x768x24" \
pytest tests/test_convert_vtk_to_usd.py -v --cov=monai_physio --cov-append --cov-report=xml
continue-on-error: true
- name: Run USD utility tests
run: |
xvfb-run -a --server-args="-screen 0 1024x768x24" \
pytest tests/test_usd_merge.py tests/test_usd_time_preservation.py -v --cov=monai_physio --cov-append --cov-report=xml
continue-on-error: true
- name: Run all integration tests
run: |
xvfb-run -a --server-args="-screen 0 1024x768x24" \
pytest tests/ -v
continue-on-error: true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: integration-tests
name: codecov-integration
fail_ci_if_error: false
# ============================================================================
# GPU Tests (Self-hosted runners with CUDA)
# ============================================================================
# NOTE: GPU tests are DISABLED for automatic runs because they wait indefinitely
# if no self-hosted runner is available. Enable manually via workflow_dispatch
# or by setting the 'run-gpu-tests' label on the PR.
gpu-tests:
name: GPU Tests
runs-on: [self-hosted, Windows, X64, gpu]
needs: unit-tests
timeout-minutes: 30
# Only run GPU tests on manual trigger or if 'run-gpu-tests' label is present
if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run-gpu-tests')
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
lfs: true
- name: Create venv in RUNNER_TEMP
# The oldest interpreter the project supports, so this job is the one
# that catches a regression at the floor. nightly-health.yml runs the
# newest (3.13) for the opposite reason. Both are on the runner.
run: |
& "C:\Program Files\Python311\python.exe" -m venv "$env:RUNNER_TEMP\monai-physio-venv"
echo "$env:RUNNER_TEMP\monai-physio-venv\Scripts" >> $env:GITHUB_PATH
- name: Check GPU availability
run: nvidia-smi
- name: Check nvcc availability
# torch-scatter compiles CUDA kernels from source when no matching
# pre-built wheel exists. Without nvcc on PATH the build silently falls
# back to a CPU-only extension that only fails later, at test time, with
# an opaque scatter error. Fail here instead.
run: |
$nvcc = Get-Command nvcc -ErrorAction SilentlyContinue
if (-not $nvcc) {
Write-Error "nvcc not found on PATH; torch-scatter would build CPU-only"
exit 1
}
Write-Output "nvcc found at $($nvcc.Source)"
nvcc --version
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Cache uv packages
uses: actions/cache@v4
with:
path: ~\AppData\Local\uv\cache
key: ${{ runner.os }}-uv-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-uv-
- name: Upgrade pip and build tools
run: |
python -m pip install --upgrade pip setuptools wheel uv
- name: Install package with test dependencies
# uv respects [tool.uv.sources], routing torch to pytorch-cu130 for cuda13.
# nvidia-physicsnemo/torch-geometric/torch-scatter are base dependencies
# (installed unconditionally), so Tutorial 9 and requires_physicsnemo-
# marked tests can run on this GPU runner without a separate extra.
# torch-scatter compiles against torch and needs --no-build-isolation-
# package from the very first install, since it's pulled in as soon as
# any extra resolves the base dependencies. The source build is
# unavoidable on CUDA 13 -- data.pyg.org publishes no torch-scatter
# wheel for it on Windows -- and is only needed at all because
# physicsnemo hard-imports torch_scatter instead of using PyG's
# native-backed torch_geometric.utils.scatter; see the AI-surrogate
# comment in pyproject.toml.
# Invoke via python -m uv so uv targets the active venv interpreter.
# torch's headers now require C++20 (std::strong_ordering, designated
# initializers, default bit-field member initializers); MSVC defaults to
# an older standard, and torch-scatter's setup.py does not add
# /std:c++20 itself, so cl.exe rejects the headers. cl.exe reads the CL
# env var as default flags for every invocation.
run: |
$env:CL = "/std:c++20"
python -m uv pip install -e ".[dev_cuda13]" --no-build-isolation-package torch-scatter
- name: Assert CUDA is accessible
run: |
python -c "
import sys, torch, cupy
print(f'PyTorch {torch.__version__} | CUDA toolkit {torch.version.cuda} | CuPy {cupy.__version__}')
if not torch.cuda.is_available():
print('ERROR: torch.cuda.is_available() returned False', file=sys.stderr)
sys.exit(1)
n = torch.cuda.device_count()
if n == 0:
print('ERROR: torch.cuda.device_count() == 0', file=sys.stderr)
sys.exit(1)
cn = cupy.cuda.runtime.getDeviceCount()
if cn == 0:
print('ERROR: cupy.cuda.runtime.getDeviceCount() == 0', file=sys.stderr)
sys.exit(1)
print(f'OK: {n} GPU(s) visible to PyTorch and CuPy')
"
- name: List installed packages
run: |
pip list
- name: Run GPU tests
# A pre-merge gate, so it runs the GPU buckets only and finishes inside
# timeout-minutes. The exhaustive --run-all sweep, tutorials included,
# belongs to nightly-health.yml: running it here too would duplicate six
# hours of work on this same runner and could never fit the timeout.
#
# --require-tutorial-data turns missing data into a failure rather than a
# skip, so this job cannot report green having run nothing.
run: |
pytest tests/ -v --run-gpu --run-physicsnemo --require-tutorial-data --max-test-seconds=600 --timeout=1800 --cov=monai_physio --cov-report=xml --cov-report=term --cov-report=html
env:
CUDA_VISIBLE_DEVICES: 0
# The datasets, the results and the trained networks live outside the
# checkout, which actions/checkout wipes on every run. Each root has a
# "test" subtree that this suite reads and writes, so a CI run never
# touches a full run's files. Unset, each falls back to its in-repo
# default; see data/README.md. These are the same paths
# nightly-health.yml uses, because both jobs run on this same runner.
MONAI_PHYSIO_INPUT_DATA_DIR: D:\MONAI-Physio\nightly-runner\data
MONAI_PHYSIO_OUTPUT_DATA_DIR: D:\MONAI-Physio\nightly-runner\output
MONAI_PHYSIO_WEIGHTS_DIR: D:\MONAI-Physio\nightly-runner\network_weights
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: gpu-tests
name: codecov-gpu
fail_ci_if_error: false
- name: Upload coverage artifacts
uses: actions/upload-artifact@v6
with:
name: coverage-report-gpu
path: htmlcov/
retention-days: 7
# ============================================================================
# Code Quality Checks
# ============================================================================
code-quality:
name: Code Quality Checks
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
lfs: true
- name: Set up Python 3.11
uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: 'pip'
- name: Install dev dependencies
# torch-scatter (a base dependency) compiles against torch and needs
# setuptools present and build isolation disabled; pip has no
# per-package flag for this, so --no-build-isolation applies to the
# whole install. torch must already be importable before that build
# runs, so install it first. gcc's default C++ standard depends on
# version and torch-scatter's setup.py does not request C++20 itself,
# so set it explicitly (torch's headers require it).
env:
CXXFLAGS: "-std=c++20"
run: |
python -m pip install --upgrade pip setuptools
# Ruff runs via pre-commit so its version is pinned in one place
# (.pre-commit-config.yaml); mypy is still invoked directly below.
pip install pre-commit mypy
pip install torch
pip install -e ".[dev]" --no-build-isolation
- name: Check formatting with Ruff (via pre-commit)
run: |
pre-commit run ruff-format --all-files
continue-on-error: false
- name: Lint with Ruff (via pre-commit)
run: |
pre-commit run ruff-check --all-files
continue-on-error: false
- name: Type check with mypy
run: |
mypy src/
continue-on-error: false
# ==============================================================================
# Notes on Excluded Tests
# ==============================================================================
#
# The following tests are excluded from CI and should be run locally:
#
# Slow/GPU-intensive tests:
# - tests/test_register_images_ants.py (slow, computationally intensive)
# - tests/test_register_images_icon.py (requires CUDA for ICON)
# - tests/test_transform_tools.py (depends on slow registration tests)
# - tests/test_segment_chest_total_segmentator.py (requires CUDA for TotalSegmentator)
#
# Tutorial tests (SLOW - hours to complete):
# - tests/test_tutorials.py (runs all 29 tutorial scripts end-to-end)
# These tests are NEVER run in the PR CI and must be opted into
# They execute end-to-end workflows that may take multiple hours
#
# To run locally:
# pytest tests/ -v --run-slow # Run all slow tests
# pytest tests/ -v --run-gpu --run-slow # GPU + slow (typical local dev profile)
# pytest tests/ -v --run-simpleware --run-gpu --run-slow # Full Simpleware coverage
# pytest tests/test_register_images_ants.py -v --run-slow
#
# Self-hosted GPU runner enables ALL buckets via --run-all
# (--run-gpu --run-slow --run-simpleware --run-physicsnemo --run-tutorials).
# That runner installs [dev_cuda13] (nvidia-physicsnemo is a base dependency).
#
# To run tutorial tests (manual only, slow):
# pytest tests/test_tutorials.py -v --run-tutorials
# pytest tests/test_tutorials.py::TestTutorial01HeartGatedCTToUSD -v --run-tutorials