Small bug fixes for slide-level embedding #23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Test WSI to embedding consistency | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| workflow_dispatch: | |
| jobs: | |
| docker-test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| actions: write # needed for Buildx GHA cache | |
| # don't run secret-using job on forked PRs | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} | |
| env: | |
| HF_TOKEN: ${{ secrets.HF_TOKEN }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Verify required folders exist | |
| run: | | |
| set -euo pipefail | |
| test -d test/input | |
| test -d test/gt | |
| mkdir -p test/output # ensure host-mapped output exists | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Free disk space on runner | |
| run: | | |
| set -euxo pipefail | |
| df -h | |
| sudo rm -rf /usr/local/lib/android || true | |
| sudo rm -rf /usr/share/dotnet || true | |
| sudo rm -rf /opt/ghc || true | |
| sudo rm -rf "${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}" || true | |
| docker system prune -af || true | |
| sudo apt-get clean | |
| df -h | |
| - name: Build image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: Dockerfile | |
| push: false | |
| load: true | |
| tags: slide2vec:${{ github.sha }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Guard required secret (if needed) | |
| if: ${{ github.event_name != 'pull_request' }} # or drop the condition if PRs from same repo need it | |
| run: | | |
| set -euo pipefail | |
| test -n "${HF_TOKEN:-}" || { echo "HF_TOKEN is required but not set"; exit 1; } | |
| - name: Generate outputs in container | |
| run: | | |
| set -euo pipefail | |
| docker run --rm \ | |
| -e HF_TOKEN="$HF_TOKEN" \ | |
| -v "$GITHUB_WORKSPACE/test/input:/input" \ | |
| -v "$GITHUB_WORKSPACE/test/output:/output" \ | |
| slide2vec:${{ github.sha }} \ | |
| python slide2vec/main.py \ | |
| --config-file /input/config.yaml \ | |
| --skip-datetime \ | |
| --run-on-cpu | |
| - name: Verify output consistency (inside container) | |
| run: | | |
| set -euo pipefail | |
| docker run --rm \ | |
| -v "$GITHUB_WORKSPACE/test/gt:/gt" \ | |
| -v "$GITHUB_WORKSPACE/test/output:/output" \ | |
| slide2vec:${{ github.sha }} \ | |
| bash -lc "python - <<'PY' | |
| import numpy as np, torch | |
| from numpy.testing import assert_array_equal | |
| # coordinates must match exactly (deterministic tiling) | |
| gt_coordinates = np.load('/gt/test-wsi.npy') | |
| coordinates = np.load('/output/coordinates/test-wsi.npy') | |
| assert_array_equal(coordinates, gt_coordinates), f'Coordinates mismatch: {coordinates} vs {gt_coordinates}' | |
| # embeddings: allow tiny numeric drift | |
| gt = torch.load('/gt/test-wsi.pt', map_location='cpu') | |
| emb = torch.load('/output/features/test-wsi.pt', map_location='cpu') | |
| assert emb.shape == gt.shape, f'Shape mismatch: {emb.shape} vs {gt.shape}' | |
| cos = torch.nn.functional.cosine_similarity(emb, gt, dim=-1) | |
| mean_cos = float(cos.mean()) | |
| atol, rtol = 1e-2, 1e-3 | |
| if not torch.allclose(emb, gt, atol=atol, rtol=rtol): | |
| if mean_cos < 0.99: | |
| raise AssertionError(f'Embedding mismatch: mean cosine similarity={mean_cos:.4f} (atol={atol}, rtol={rtol})') | |
| else: | |
| print(f'WARNING: embeddings not allclose, but mean cosine similarity={mean_cos:.4f} (atol={atol}, rtol={rtol})') | |
| else: | |
| print(f'OK: embeddings within tolerance; mean cosine similarity={mean_cos:.4f}') | |
| PY" |