feat: Add combined build (#5895)

* Add combined build

* Link script location update

* Security and validation fixes

* Allow specifying target path in PR description

* fix: Version match

* Fix build detection issue

* fix: Playwright deploy

* Separate out the branch merge guard

* Update docs and link info

* test: Update the layout change to wait for network idle

* Move audit late so the rest of the build can be worked on

* Add text with network check to ensure we see this change is updated

* Attempt to fix the mpr loading on ohif-downstream

* PR review comments

* Update docs

* Update to CS3D 4.20.0

* PR comments

* Add log on ohif-integration builds

* Update build test

* Removed unused space to kickoff build
This commit is contained in:
Bill Wallace 2026-03-17 07:52:17 -04:00 committed by GitHub
parent 15ef4c2e0f
commit 1df671e9ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 1047 additions and 301 deletions

View File

@ -112,61 +112,6 @@ jobs:
- checkout - checkout
- attach_workspace: - attach_workspace:
at: ~/repo at: ~/repo
# SECURITY AUDIT - only when bun.lock has changed
- run:
name: 'Security Audit - High Risk Vulnerabilities'
command: |
git fetch origin master 2>/dev/null || true
BASE_REF=$(git merge-base HEAD origin/master 2>/dev/null)
if [[ -z "$BASE_REF" ]]; then
echo "Could not determine base ref (e.g. shallow clone or no origin/master), skipping security audit."
exit 0
fi
CHANGED_FILES=$(git diff --name-only origin/master...HEAD 2>/dev/null || echo "")
if ! echo "$CHANGED_FILES" | grep -qx 'bun.lock'; then
echo "⏭️ bun.lock unchanged - skipping security audit."
exit 0
fi
echo "🔍 bun.lock changed - running bun audit for security vulnerabilities..."
echo "Checking for HIGH-RISK vulnerabilities..."
# Define ignored vulnerabilities with comments
IGNORED_VULNS=(
"GHSA-3ppc-4f35-3m26" # CVE-2026-26996 - minimatch via itk-wasm and glob is safe because it does NOT use the CLI
# CVE-2026-26996 - minimatch via other packages are strictly for building and CI/CD purposes; no user supplied expressions are passed to minimatch
"GHSA-7r86-cg39-jmmj" # CVE-2026-27903 - minimatch same as above
"GHSA-23c5-xmqv-rm74" # CVE-2026-27904 - minimatch same as above
)
# Build ignore flags
IGNORE_FLAGS=""
for vuln in "${IGNORED_VULNS[@]}"; do
IGNORE_FLAGS="$IGNORE_FLAGS --ignore=$vuln"
done
if bun audit $IGNORE_FLAGS --audit-level high; then
echo "✅ No high-risk vulnerabilities found"
echo "🎉 Security audit passed!"
else
echo ""
echo "❌ HIGH-RISK VULNERABILITIES DETECTED!"
echo "======================================"
echo ""
echo "🔧 To fix these issues:"
echo " 1. Run: bun audit"
echo " 2. Review the vulnerability details"
echo " 3. Update affected packages to secure versions"
echo " 4. Test your changes"
echo " 5. Re-run: bun audit --audit-level high"
echo ""
echo "📋 Full audit report:"
bun audit $IGNORE_FLAGS --audit-level low || true
echo ""
echo "❌ This build cannot proceed until high-risk vulnerabilities are resolved."
exit 1
fi
- run: - run:
name: Install Dependencies name: Install Dependencies
command: bun install --frozen-lockfile command: bun install --frozen-lockfile
@ -405,6 +350,68 @@ jobs:
npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record --parallel npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record --parallel
start-command: yarn run test:data && yarn run test:e2e:serve start-command: yarn run test:data && yarn run test:e2e:serve
SECURITY_AUDIT:
<<: *defaults
resource_class: large
steps:
- install_bun
- checkout
- run:
name: 'Security Audit - High Risk Vulnerabilities'
command: |
git fetch origin master 2>/dev/null || true
BASE_REF=$(git merge-base HEAD origin/master 2>/dev/null)
if [[ -z "$BASE_REF" ]]; then
echo "Could not determine base ref (e.g. shallow clone or no origin/master), skipping security audit."
exit 0
fi
CHANGED_FILES=$(git diff --name-only origin/master...HEAD 2>/dev/null || echo "")
if ! echo "$CHANGED_FILES" | grep -qx 'bun.lock'; then
echo "⏭️ bun.lock unchanged - skipping security audit."
exit 0
fi
echo "🔍 bun.lock changed - running bun audit for security vulnerabilities..."
echo "Checking for HIGH-RISK vulnerabilities..."
# Define ignored vulnerabilities with comments
IGNORED_VULNS=(
"GHSA-3ppc-4f35-3m26" # CVE-2026-26996 - minimatch via itk-wasm and glob is safe because it does NOT use the CLI
# CVE-2026-26996 - minimatch via other packages are strictly for building and CI/CD purposes; no user supplied expressions are passed to minimatch
"GHSA-7r86-cg39-jmmj" # CVE-2026-27903 - minimatch same as above
"GHSA-23c5-xmqv-rm74" # CVE-2026-27904 - minimatch same as above
)
# Build ignore flags
IGNORE_FLAGS=""
for vuln in "${IGNORED_VULNS[@]}"; do
IGNORE_FLAGS="$IGNORE_FLAGS --ignore=$vuln"
done
if bun audit $IGNORE_FLAGS --audit-level high; then
echo "✅ No high-risk vulnerabilities found"
echo "🎉 Security audit passed!"
exit 0
else
echo ""
echo "❌ HIGH-RISK VULNERABILITIES DETECTED!"
echo "======================================"
echo ""
echo "🔧 To fix these issues:"
echo " 1. Run: bun audit"
echo " 2. Review the vulnerability details"
echo " 3. Update affected packages to secure versions"
echo " 4. Test your changes"
echo " 5. Re-run: bun audit --audit-level high"
echo ""
echo "📋 Full audit report:"
bun audit $IGNORE_FLAGS --audit-level low || true
echo ""
echo "❌ This build cannot proceed until high-risk vulnerabilities are resolved."
exit 1
fi
DOCKER_MULTIARCH_MANIFEST: DOCKER_MULTIARCH_MANIFEST:
<<: *defaults <<: *defaults
resource_class: large resource_class: large
@ -494,6 +501,10 @@ workflows:
- CYPRESS: - CYPRESS:
name: 'Cypress Tests' name: 'Cypress Tests'
context: cypress context: cypress
- SECURITY_AUDIT:
filters:
branches:
ignore: master
# viewer-dev.ohif.org # viewer-dev.ohif.org
DEPLOY_MASTER: DEPLOY_MASTER:

View File

@ -2,6 +2,19 @@ name: Playwright Tests
on: on:
pull_request: pull_request:
branches: [master, release/*] branches: [master, release/*]
workflow_dispatch:
inputs:
cs3d_ref:
description: >-
CS3D branch (e.g. main, origin:feat/foo) or version (e.g. 4.18.2, 4.19+, 4.x). Only used
when ohif-integration label is present or via workflow_dispatch.
required: false
default: '4.19+'
permissions:
contents: read
pull-requests: read
issues: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
@ -9,7 +22,7 @@ concurrency:
jobs: jobs:
playwright-tests: playwright-tests:
timeout-minutes: 60 timeout-minutes: 120
runs-on: self-hosted runs-on: self-hosted
strategy: strategy:
fail-fast: false fail-fast: false
@ -25,14 +38,85 @@ jobs:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install Yarn - name: Install Yarn
run: npm install -g yarn@1.22.22 run: npm install -g yarn@1.22.22
# ── CS3D integration: detect label and ref type ──────────────────────
- name: Check for CS3D integration label
id: cs3d-check
run: bash .scripts/ci/cs3d-check-integration.sh
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
CS3D_REF_INPUT: ${{ github.event.inputs.cs3d_ref || '4.19+' }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
- name: Detect CS3D ref type
id: cs3d-ref
if: steps.cs3d-check.outputs.enabled == 'true'
run: |
REF="${CS3D_REF}"
if [[ "$REF" =~ ^[0-9]+\.[0-9x]+\+?(\.[0-9x]+)?(-[a-zA-Z0-9._]+)?$ ]]; then
echo "type=version" >> "$GITHUB_OUTPUT"
RESOLVED=$(node .scripts/cs3d-resolve-version.mjs "$REF")
echo "version=$RESOLVED" >> "$GITHUB_OUTPUT"
echo "::notice::CS3D version: $REF -> $RESOLVED"
else
echo "type=branch" >> "$GITHUB_OUTPUT"
echo "::notice::CS3D branch: $REF"
fi
env:
CS3D_REF: ${{ steps.cs3d-check.outputs.cs3d_ref }}
# ── CS3D branch path: clone and build before OHIF install ───────────
- name: Clone CS3D
if: steps.cs3d-check.outputs.enabled == 'true' && steps.cs3d-ref.outputs.type == 'branch'
run: |
REF="${CS3D_REF}"
if [[ "$REF" == *:* ]]; then
REPO="https://github.com/${REF%%:*}/cornerstone3D.git"
BRANCH="${REF#*:}"
else
REPO="https://github.com/cornerstonejs/cornerstone3D.git"
BRANCH="$REF"
fi
echo "::notice::Cloning CS3D from $REPO branch $BRANCH"
git clone --depth 1 --branch "$BRANCH" "$REPO" libs/@cornerstonejs
env:
CS3D_REF: ${{ steps.cs3d-check.outputs.cs3d_ref }}
- name: Install & Build CS3D
if: steps.cs3d-check.outputs.enabled == 'true' && steps.cs3d-ref.outputs.type == 'branch'
working-directory: libs/@cornerstonejs
run: bun install --frozen-lockfile && bun run build:esm
# ── Common: install OHIF dependencies ───────────────────────────────
- name: Install dependencies - name: Install dependencies
run: bun install --frozen-lockfile run: bun install --frozen-lockfile
# ── CS3D branch path: link packages after OHIF install ──────────────
- name: Link CS3D packages
if: steps.cs3d-check.outputs.enabled == 'true' && steps.cs3d-ref.outputs.type == 'branch'
working-directory: libs/@cornerstonejs
run: node scripts/link-ohif-cornerstone-node-modules.mjs "$GITHUB_WORKSPACE"
# ── CS3D version path: update versions after OHIF install ───────────
- name: Set CS3D version
if: steps.cs3d-check.outputs.enabled == 'true' && steps.cs3d-ref.outputs.type == 'version'
run: |
node .scripts/cs3d-set-version.mjs "${CS3D_VERSION}"
bun install
env:
CS3D_VERSION: ${{ steps.cs3d-ref.outputs.version }}
# ── Common: run tests ───────────────────────────────────────────────
- name: Install Playwright browsers - name: Install Playwright browsers
run: npx playwright install run: npx playwright install
- name: Run Playwright tests - name: Run Playwright tests
run: | run: |
export NODE_OPTIONS="--max_old_space_size=10192" export NODE_OPTIONS="--max_old_space_size=10192"
bun run test:e2e:coverage bun run test:e2e:coverage
# ── Common: collect test results and coverage ───────────────────────
- name: Create directory of test results - name: Create directory of test results
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
run: | run: |
@ -56,3 +140,70 @@ jobs:
name: coverage-report-pr name: coverage-report-pr
path: coverage path: coverage
retention-days: 3 retention-days: 3
# ── CS3D: build and deploy preview to Netlify ───────────────────────
- name: Log build context (OHIF/CS3D branch and version for build diagnosis)
if: steps.cs3d-check.outputs.enabled == 'true'
run: |
if [[ "$CS3D_REF_TYPE" == "branch" ]]; then
echo "::notice::Build type: ohif-downstream | OHIF: ${{ github.repository }}@${{ github.ref }} (${{ github.sha }}) | CS3D: branch ${{ steps.cs3d-check.outputs.cs3d_ref }}"
else
echo "::notice::Build type: ohif-upstream | OHIF: ${{ github.repository }}@${{ github.ref }} (${{ github.sha }}) | CS3D: version ${{ steps.cs3d-ref.outputs.version }}"
fi
node .scripts/log-build-context.mjs
env:
BUILD_TYPE: ${{ steps.cs3d-ref.outputs.type == 'branch' && 'ohif-downstream' || 'ohif-upstream' }}
CS3D_REF_TYPE: ${{ steps.cs3d-ref.outputs.type }}
- name: Build OHIF viewer (CS3D preview)
if: steps.cs3d-check.outputs.enabled == 'true'
run: bun run build:ci
- name: Deploy CS3D preview to Netlify
if: steps.cs3d-check.outputs.enabled == 'true'
run: |
RESULT=$(npx netlify-cli deploy --dir=platform/app/dist --alias="cs3d-pr-${PR_NUM}" --json --filter=@ohif/app) || {
echo "::error::Netlify deploy command failed"
exit 1
}
URL=$(echo "$RESULT" | jq -r '.deploy_url')
if [[ -z "$URL" || "$URL" == "null" ]]; then
echo "::error::Netlify deploy did not return a valid URL"
echo "$RESULT"
exit 1
fi
echo "::notice::CS3D preview deployed: $URL"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
PR_NUM: ${{ github.event.pull_request.number || 'manual' }}
# ── CS3D: log results ───────────────────────────────────────────────
- name: Log CS3D build used
if: steps.cs3d-check.outputs.enabled == 'true'
run: |
if [[ "$CS3D_REF_TYPE" == "branch" ]]; then
echo "::notice::CS3D integration PASSED with branch ${CS3D_REF} (linked from libs/@cornerstonejs)"
else
echo "::notice::CS3D integration PASSED with @cornerstonejs/*@${CS3D_VERSION}"
fi
env:
CS3D_REF_TYPE: ${{ steps.cs3d-ref.outputs.type }}
CS3D_REF: ${{ steps.cs3d-check.outputs.cs3d_ref }}
CS3D_VERSION: ${{ steps.cs3d-ref.outputs.version }}
# ── Separate job: block merge when using a CS3D branch ─────────────
cs3d-branch-merge-guard:
name: "CS3D Branch Merge Guard"
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Check for CS3D branch usage
run: bash .scripts/ci/cs3d-branch-merge-guard.sh
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
CS3D_REF_INPUT: ${{ github.event.inputs.cs3d_ref || '4.19+' }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}

3
.gitignore vendored
View File

@ -61,5 +61,8 @@ tests/playwright-report/
**/.claude/settings.local.json **/.claude/settings.local.json
# cornerstone3D local linking
libs/
# Backup files # Backup files
*~ *~

View File

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# CS3D branch merge guard: blocks merge when tests ran against a CS3D branch (not a version).
# Exits 0 when merge is allowed or guard is skipped; exits 1 when merge must be blocked.
#
# Required env: GH_TOKEN, EVENT_NAME, REPO, PR_NUMBER
# Optional env: CS3D_REF_INPUT (for workflow_dispatch, default 4.19+)
set -e
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
echo "::notice::workflow_dispatch — no merge to block, skipping guard."
exit 0
elif [[ "$EVENT_NAME" == "pull_request" ]]; then
LABELS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" --jq '.[].name')
if echo "$LABELS" | grep -q "ohif-integration"; then
ENABLED=true
CS3D_REF=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.body' \
| sed -n 's/^[[:space:]]*CS3D_REF:[[:space:]]*\([^[:space:]]*\).*/\1/p' | head -1)
if [[ -z "$CS3D_REF" ]]; then
CS3D_REF="4.19+"
fi
else
ENABLED=false
fi
else
ENABLED=false
fi
if [[ "$ENABLED" != "true" ]]; then
echo "::notice::No ohif-integration label — skipping merge guard."
exit 0
fi
# Check if the ref is a branch (not a version)
if [[ "$CS3D_REF" =~ ^[0-9]+\.[0-9x]+\+?(\.[0-9x]+)?(-[a-zA-Z0-9._]+)?$ ]]; then
echo "::notice::CS3D ref '$CS3D_REF' is a version — merge allowed."
exit 0
fi
echo "::error::Tests ran against CS3D branch '${CS3D_REF}' — this build cannot be merged."
echo "::error::Re-run with a published CS3D version (e.g. 4.19+) before merging."
exit 1

View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
# CS3D integration check: detects ohif-integration label and parses CS3D_REF.
# Writes to GITHUB_OUTPUT: enabled (true|false), cs3d_ref (when enabled).
#
# Required env: GH_TOKEN, EVENT_NAME, REPO, PR_NUMBER, GITHUB_OUTPUT
# Optional env: CS3D_REF_INPUT (for workflow_dispatch, default 4.19+)
set -e
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
echo "cs3d_ref=${CS3D_REF_INPUT:-4.19+}" >> "$GITHUB_OUTPUT"
elif [[ "$EVENT_NAME" == "pull_request" ]]; then
LABELS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" --jq '.[].name')
if echo "$LABELS" | grep -q "ohif-integration"; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
REF=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}" --jq '.body' \
| sed -n 's/^[[:space:]]*CS3D_REF:[[:space:]]*\([^[:space:]]*\).*/\1/p' | head -1)
if [[ -z "$REF" ]]; then
REF="4.19+"
fi
echo "cs3d_ref=${REF}" >> "$GITHUB_OUTPUT"
echo "::notice::CS3D ref from PR body: ${REF}"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
fi
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
fi

View File

@ -0,0 +1,63 @@
#!/usr/bin/env node
/**
* Resolves a version pattern to a concrete npm version of @cornerstonejs/core.
*
* Patterns:
* 4.18.2 -> 4.18.2 (exact, returned as-is)
* 4.18.2-beta.3 -> 4.18.2-beta.3 (exact prerelease)
* 4.x -> latest 4.* from npm
* 4.17.x -> latest 4.17.* from npm
* 4.19+ -> latest >=4.19.0 <5.0.0-0 from npm (4.19 and later, same major)
*
* Prints the resolved version to stdout.
*/
import { execSync } from 'child_process';
const pattern = process.argv[2];
if (!pattern) {
console.error('Usage: cs3d-resolve-version.mjs <pattern>');
console.error(' e.g. 4.x, 4.17.x, 4.19+, 4.18.2, 4.18.2-beta.3');
process.exit(1);
}
// Exact version (no wildcard or range) — pass through unchanged
if (!pattern.includes('x') && !pattern.endsWith('+')) {
console.log(pattern);
process.exit(0);
}
// Convert "M.m+" to npm semver range ">=M.m.0 <(M+1).0.0-0"
let npmRange = pattern;
const plusMatch = pattern.match(/^(\d+)\.(\d+)\+$/);
if (plusMatch) {
const major = Number(plusMatch[1]);
const minor = Number(plusMatch[2]);
npmRange = `>=${major}.${minor}.0 <${major + 1}.0.0-0`;
}
try {
// Let npm resolve the range: @package@<range> → concrete version
const raw = execSync(`npm view @cornerstonejs/core@"${npmRange}" version --json`, {
encoding: 'utf8',
timeout: 30_000,
});
const resolved = JSON.parse(raw);
if (!resolved) {
console.error(`npm returned no version when resolving @cornerstonejs/core@${npmRange}`);
process.exit(1);
}
// npm may return an array of versions for ranges; pick the latest (last) one
const version = Array.isArray(resolved) ? resolved[resolved.length - 1] : resolved;
console.log(version);
} catch (err) {
const message =
(err && (err.stderr?.toString() || err.message || String(err))) ||
`Unknown error resolving @cornerstonejs/core@${pattern}`;
console.error(`Failed to resolve @cornerstonejs/core version for pattern "${pattern}":`);
console.error(message);
process.exit(1);
}

View File

@ -0,0 +1,124 @@
#!/usr/bin/env node
/**
* Updates all @cornerstonejs/* package versions across the OHIF workspace.
*
* Usage: node .scripts/cs3d-set-version.mjs <version>
*
* Only updates the 8 main CS3D packages (not codec packages):
* adapters, ai, core, dicom-image-loader, labelmap-interpolation,
* nifti-volume-loader, polymorphic-segmentation, tools
*/
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
import { resolve, dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = resolve(__dirname, '..');
const version = process.argv[2];
if (!version) {
console.error('Usage: cs3d-set-version.mjs <version>');
console.error(' e.g. 4.18.2, 4.19.0-beta.1');
process.exit(1);
}
// The 8 CS3D packages that are built from source (not codecs)
const CS3D_PACKAGES = [
'@cornerstonejs/adapters',
'@cornerstonejs/ai',
'@cornerstonejs/core',
'@cornerstonejs/dicom-image-loader',
'@cornerstonejs/labelmap-interpolation',
'@cornerstonejs/nifti-volume-loader',
'@cornerstonejs/polymorphic-segmentation',
'@cornerstonejs/tools',
];
// Read root package.json to get workspace globs
const rootPkgPath = resolve(rootDir, 'package.json');
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf8'));
const workspaceGlobs = rootPkg.workspaces?.packages || rootPkg.workspaces || [];
// Collect all package.json paths from workspace globs
function findWorkspacePackageJsons() {
const paths = [rootPkgPath]; // include root
for (const pattern of workspaceGlobs) {
const parts = pattern.split('/');
let searchDir = rootDir;
let hasWildcard = false;
for (const part of parts) {
if (part === '*') {
hasWildcard = true;
break;
}
searchDir = join(searchDir, part);
}
if (hasWildcard) {
try {
const entries = readdirSync(searchDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const pkgJson = join(searchDir, entry.name, 'package.json');
if (existsSync(pkgJson)) {
paths.push(pkgJson);
}
}
}
} catch {
// directory doesn't exist, skip
}
} else {
const pkgJson = join(rootDir, pattern, 'package.json');
if (existsSync(pkgJson)) {
paths.push(pkgJson);
}
}
}
return paths;
}
// Update a dependencies object, returning count of changes
function updateDeps(deps, targetVersion) {
let count = 0;
if (!deps) return count;
for (const pkg of CS3D_PACKAGES) {
if (pkg in deps && deps[pkg] !== targetVersion) {
deps[pkg] = targetVersion;
count++;
}
}
return count;
}
const pkgPaths = findWorkspacePackageJsons();
let totalChanges = 0;
for (const pkgPath of pkgPaths) {
const content = readFileSync(pkgPath, 'utf8');
const pkg = JSON.parse(content);
let changes = 0;
changes += updateDeps(pkg.dependencies, version);
changes += updateDeps(pkg.devDependencies, version);
changes += updateDeps(pkg.peerDependencies, version);
changes += updateDeps(pkg.resolutions, version);
if (changes > 0) {
// Preserve original formatting (detect indent)
const indent = content.match(/^(\s+)/m)?.[1] || ' ';
writeFileSync(pkgPath, JSON.stringify(pkg, null, indent) + '\n');
const rel = pkgPath.replace(rootDir + '/', '').replace(rootDir + '\\', '');
console.log(` Updated ${rel} (${changes} packages)`);
totalChanges += changes;
}
}
console.log(
`\nDone: ${totalChanges} version(s) updated to ${version} across ${pkgPaths.length} package files.`
);

View File

@ -0,0 +1,108 @@
#!/usr/bin/env node
/**
* Logs build context (OHIF branch/version, CS3D source) for diagnosing build issues on GitHub.
* Run from repo root. Used by version.mjs and can be invoked from CI workflows.
*/
import { execa } from 'execa';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..');
function log(msg) {
console.log(`[build-context] ${msg}`);
}
async function detectCs3dSource() {
const libsCs3d = path.join(REPO_ROOT, 'libs/@cornerstonejs');
const coreInNodeModules = path.join(REPO_ROOT, 'node_modules/@cornerstonejs/core');
try {
const libsExists = await fs.access(libsCs3d).then(() => true).catch(() => false);
if (!libsExists) {
return { source: 'npm', detail: 'published @cornerstonejs packages (no libs/@cornerstonejs)' };
}
const coreStat = await fs.lstat(coreInNodeModules).catch(() => null);
const isSymlink = coreStat?.isSymbolicLink();
if (isSymlink) {
const target = await fs.readlink(coreInNodeModules);
return { source: 'integrated', detail: `linked from libs/@cornerstonejs (→ ${target})` };
}
return { source: 'npm', detail: 'published @cornerstonejs packages (libs exists but not linked)' };
} catch {
return { source: 'unknown', detail: 'could not detect' };
}
}
async function getCs3dVersion() {
try {
const corePkg = path.join(REPO_ROOT, 'node_modules/@cornerstonejs/core/package.json');
const pkg = JSON.parse(await fs.readFile(corePkg, 'utf-8'));
return pkg.version || 'unknown';
} catch {
return 'unknown';
}
}
async function getCs3dBranchFromLibs() {
try {
const libsCs3d = path.join(REPO_ROOT, 'libs/@cornerstonejs');
const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
cwd: libsCs3d,
});
return stdout;
} catch {
return null;
}
}
async function run() {
const inCI = process.env.GITHUB_ACTIONS === 'true';
const buildType = inCI ? (process.env.BUILD_TYPE || 'ohif-upstream') : 'local';
log('═══════════════════════════════════════════════════════════════');
log('Build context (for diagnosing GitHub build issues)');
log('═══════════════════════════════════════════════════════════════');
if (inCI) {
log(`Build type: ${process.env.BUILD_TYPE || 'ohif-upstream'}`);
log(`GitHub repo: ${process.env.GITHUB_REPOSITORY || 'unknown'}`);
log(`GitHub ref: ${process.env.GITHUB_REF || 'unknown'}`);
log(`GitHub SHA: ${process.env.GITHUB_SHA || 'unknown'}`);
log(`Workflow: ${process.env.GITHUB_WORKFLOW || 'unknown'}`);
}
try {
const { stdout: branch } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
cwd: REPO_ROOT,
});
const { stdout: sha } = await execa('git', ['rev-parse', '--short', 'HEAD'], {
cwd: REPO_ROOT,
});
log(`OHIF branch: ${branch} (${sha})`);
} catch {
log('OHIF branch: (not a git repo or error)');
}
const cs3d = await detectCs3dSource();
log(`CS3D source: ${cs3d.source}${cs3d.detail}`);
if (cs3d.source === 'integrated') {
const branch = await getCs3dBranchFromLibs();
if (branch) log(`CS3D branch (libs/@cornerstonejs): ${branch}`);
} else {
const ver = await getCs3dVersion();
log(`CS3D version (@cornerstonejs/core): ${ver}`);
}
log('═══════════════════════════════════════════════════════════════');
}
run().catch((err) => {
console.error('[build-context] Error:', err.message);
process.exitCode = 1;
});

View File

@ -105,7 +105,6 @@ For commercial support, academic collaborations, and answers to common
questions; please use [Get Support](https://ohif.org/get-support/) to contact questions; please use [Get Support](https://ohif.org/get-support/) to contact
us. us.
## Developing ## Developing
### Branches ### Branches
@ -169,6 +168,64 @@ yarn config set workspaces-experimental true
yarn install --frozen-lockfile yarn install --frozen-lockfile
``` ```
### Cornerstone3D Integration Testing
OHIF's Playwright end-to-end tests can run against a **CS3D branch** or a
**published CS3D version**, allowing changes that span both repositories to be
validated together before merging.
#### Setting up an integration build
1. Add the **`ohif-integration`** label to your OHIF pull request.
2. In the PR body, add a line specifying the CS3D ref:
```
CS3D_REF: feat/my-feature
```
- **Version ref** (e.g. `4.19+`, `4.18.2`) — the workflow resolves it to an
exact published version and swaps the CS3D dependency via npm.
- **Branch ref** (e.g. `main`, `cornerstonejs:feat/foo`) — the workflow
clones the branch, builds CS3D from source with `bun run build:esm`, and
symlinks the built packages into OHIF's `node_modules`.
- For forks, use the `<owner>:<branch>` format
(e.g. `myGithubUser:feat/foo`).
- If no `CS3D_REF` is specified, the default is `4.19+`.
3. The workflow can also be triggered manually via **workflow_dispatch** with a
`cs3d_ref` input.
#### What happens in CI
The [Playwright workflow](.github/workflows/playwright.yml) runs two jobs:
| Job | Purpose |
|-----|---------|
| **Playwright Tests** | Builds OHIF (with CS3D linked or version-swapped), runs the full Playwright suite, uploads test results and coverage, and deploys a Netlify preview when `ohif-integration` is active. |
| **CS3D Branch Merge Guard** | A lightweight check that **fails** when the `ohif-integration` label is present and `CS3D_REF` points to a branch (not a version). This prevents merging while still letting the Playwright tests show green so you can see whether the code actually works. |
#### Testing changes that span both repos
If a feature requires changes in both Cornerstone3D and OHIF:
1. Create your feature branch in CS3D and push it.
2. Create a matching branch in OHIF.
3. Add the `ohif-integration` label to the OHIF pull request.
4. In the PR body, add: `CS3D_REF: <your-cs3d-branch>`.
5. Playwright tests will build CS3D from source, link it, and run the full
suite. The merge guard will block merge until you switch to a published
version — but you can see the test results and the preview deploy while
iterating.
6. Once the CS3D side is merged and published, update the PR body to reference
the published version (e.g. `CS3D_REF: 4.19+`). The tests will run against
the registry version and the merge guard will pass.
#### Preview deploys
When `ohif-integration` is active, the Playwright workflow also builds the OHIF
viewer and deploys it to Netlify as a preview. This gives you a live URL to
manually test the combined CS3D + OHIF changes without running anything locally.
For details on linking CS3D locally for development, see the
[Cornerstone3D README](libs/@cornerstonejs/README.md#local-development-linking--unlinking).
## Commands ## Commands
These commands are available from the root directory. Each project directory These commands are available from the root directory. Each project directory

331
bun.lock
View File

@ -1,5 +1,6 @@
{ {
"lockfileVersion": 1, "lockfileVersion": 1,
"configVersion": 0,
"workspaces": { "workspaces": {
"": { "": {
"name": "ohif-monorepo-root", "name": "ohif-monorepo-root",
@ -50,7 +51,7 @@
}, },
"addOns/externals/devDependencies": { "addOns/externals/devDependencies": {
"name": "@externals/devDependencies", "name": "@externals/devDependencies",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@kitware/vtk.js": "34.15.1", "@kitware/vtk.js": "34.15.1",
@ -131,22 +132,22 @@
}, },
"addOns/externals/dicom-microscopy-viewer": { "addOns/externals/dicom-microscopy-viewer": {
"name": "@externals/dicom-microscopy-viewer", "name": "@externals/dicom-microscopy-viewer",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"dicom-microscopy-viewer": "0.48.17", "dicom-microscopy-viewer": "0.48.17",
}, },
}, },
"extensions/cornerstone": { "extensions/cornerstone": {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.19.0", "@cornerstonejs/adapters": "4.20.0",
"@cornerstonejs/ai": "4.19.0", "@cornerstonejs/ai": "4.20.0",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/labelmap-interpolation": "4.19.0", "@cornerstonejs/labelmap-interpolation": "4.20.0",
"@cornerstonejs/polymorphic-segmentation": "4.19.0", "@cornerstonejs/polymorphic-segmentation": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"@icr/polyseg-wasm": "0.4.0", "@icr/polyseg-wasm": "0.4.0",
"@itk-wasm/morphological-contour-interpolation": "1.1.0", "@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "34.15.1", "@kitware/vtk.js": "34.15.1",
@ -164,9 +165,9 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7", "@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.19.0", "@cornerstonejs/dicom-image-loader": "4.20.0",
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
"dicom-parser": "1.8.21", "dicom-parser": "1.8.21",
"hammerjs": "2.0.8", "hammerjs": "2.0.8",
@ -178,18 +179,18 @@
}, },
"extensions/cornerstone-dicom-pmap": { "extensions/cornerstone-dicom-pmap": {
"name": "@ohif/extension-cornerstone-dicom-pmap", "name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.19.0", "@cornerstonejs/adapters": "4.20.0",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@kitware/vtk.js": "34.15.1", "@kitware/vtk.js": "34.15.1",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/i18n": "3.13.0-beta.39", "@ohif/i18n": "3.13.0-beta.41",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
@ -200,15 +201,15 @@
}, },
"extensions/cornerstone-dicom-rt": { "extensions/cornerstone-dicom-rt": {
"name": "@ohif/extension-cornerstone-dicom-rt", "name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/i18n": "3.13.0-beta.39", "@ohif/i18n": "3.13.0-beta.41",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
@ -219,18 +220,18 @@
}, },
"extensions/cornerstone-dicom-seg": { "extensions/cornerstone-dicom-seg": {
"name": "@ohif/extension-cornerstone-dicom-seg", "name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.19.0", "@cornerstonejs/adapters": "4.20.0",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@kitware/vtk.js": "34.15.1", "@kitware/vtk.js": "34.15.1",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/i18n": "3.13.0-beta.39", "@ohif/i18n": "3.13.0-beta.41",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
@ -241,19 +242,19 @@
}, },
"extensions/cornerstone-dicom-sr": { "extensions/cornerstone-dicom-sr": {
"name": "@ohif/extension-cornerstone-dicom-sr", "name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.19.0", "@cornerstonejs/adapters": "4.20.0",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"classnames": "2.5.1", "classnames": "2.5.1",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-measurement-tracking": "3.13.0-beta.39", "@ohif/extension-measurement-tracking": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
"dicom-parser": "1.8.21", "dicom-parser": "1.8.21",
"hammerjs": "2.0.8", "hammerjs": "2.0.8",
@ -263,19 +264,19 @@
}, },
"extensions/cornerstone-dynamic-volume": { "extensions/cornerstone-dynamic-volume": {
"name": "@ohif/extension-cornerstone-dynamic-volume", "name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"classnames": "2.5.1", "classnames": "2.5.1",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/i18n": "3.13.0-beta.39", "@ohif/i18n": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
"dicom-parser": "1.8.21", "dicom-parser": "1.8.21",
"hammerjs": "2.0.8", "hammerjs": "2.0.8",
@ -285,7 +286,7 @@
}, },
"extensions/default": { "extensions/default": {
"name": "@ohif/extension-default", "name": "@ohif/extension-default",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/calculate-suv": "1.1.0", "@cornerstonejs/calculate-suv": "1.1.0",
@ -294,8 +295,8 @@
"react-color": "2.19.3", "react-color": "2.19.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/i18n": "3.13.0-beta.39", "@ohif/i18n": "3.13.0-beta.41",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
"dicomweb-client": "0.10.4", "dicomweb-client": "0.10.4",
"prop-types": "15.8.1", "prop-types": "15.8.1",
@ -309,7 +310,7 @@
}, },
"extensions/dicom-microscopy": { "extensions/dicom-microscopy": {
"name": "@ohif/extension-dicom-microscopy", "name": "@ohif/extension-dicom-microscopy",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/codec-charls": "1.2.3", "@cornerstonejs/codec-charls": "1.2.3",
@ -320,10 +321,10 @@
"mathjs": "12.4.3", "mathjs": "12.4.3",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/i18n": "3.13.0-beta.39", "@ohif/i18n": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
@ -334,14 +335,14 @@
}, },
"extensions/dicom-pdf": { "extensions/dicom-pdf": {
"name": "@ohif/extension-dicom-pdf", "name": "@ohif/extension-dicom-pdf",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"classnames": "2.5.1", "classnames": "2.5.1",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
"dicom-parser": "1.8.21", "dicom-parser": "1.8.21",
"hammerjs": "2.0.8", "hammerjs": "2.0.8",
@ -351,14 +352,14 @@
}, },
"extensions/dicom-video": { "extensions/dicom-video": {
"name": "@ohif/extension-dicom-video", "name": "@ohif/extension-dicom-video",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"classnames": "2.5.1", "classnames": "2.5.1",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
"dicom-parser": "1.8.21", "dicom-parser": "1.8.21",
"hammerjs": "2.0.8", "hammerjs": "2.0.8",
@ -368,20 +369,20 @@
}, },
"extensions/measurement-tracking": { "extensions/measurement-tracking": {
"name": "@ohif/extension-measurement-tracking", "name": "@ohif/extension-measurement-tracking",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"@xstate/react": "3.2.2", "@xstate/react": "3.2.2",
"xstate": "4.38.3", "xstate": "4.38.3",
}, },
"peerDependencies": { "peerDependencies": {
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"classnames": "2.5.1", "classnames": "2.5.1",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
"lodash.debounce": "4.0.8", "lodash.debounce": "4.0.8",
@ -394,14 +395,14 @@
}, },
"extensions/test-extension": { "extensions/test-extension": {
"name": "@ohif/extension-test", "name": "@ohif/extension-test",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"classnames": "2.5.1", "classnames": "2.5.1",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
"dicom-parser": "1.8.21", "dicom-parser": "1.8.21",
"hammerjs": "2.0.8", "hammerjs": "2.0.8",
@ -411,7 +412,7 @@
}, },
"extensions/tmtv": { "extensions/tmtv": {
"name": "@ohif/extension-tmtv", "name": "@ohif/extension-tmtv",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"classnames": "2.5.1", "classnames": "2.5.1",
@ -428,16 +429,16 @@
}, },
"extensions/usAnnotation": { "extensions/usAnnotation": {
"name": "@ohif/extension-ultrasound-pleura-bline", "name": "@ohif/extension-ultrasound-pleura-bline",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/i18n": "3.13.0-beta.39", "@ohif/i18n": "3.13.0-beta.41",
"@ohif/ui-next": "3.13.0-beta.39", "@ohif/ui-next": "3.13.0-beta.41",
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.28.0", "@babel/core": "7.28.0",
@ -476,7 +477,7 @@
}, },
"modes/basic": { "modes/basic": {
"name": "@ohif/mode-basic", "name": "@ohif/mode-basic",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
}, },
@ -485,19 +486,19 @@
"webpack-merge": "5.10.0", "webpack-merge": "5.10.0",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-rt": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-rt": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-seg": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-seg": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/extension-dicom-pdf": "3.13.0-beta.39", "@ohif/extension-dicom-pdf": "3.13.0-beta.41",
"@ohif/extension-dicom-video": "3.13.0-beta.39", "@ohif/extension-dicom-video": "3.13.0-beta.41",
}, },
}, },
"modes/basic-dev-mode": { "modes/basic-dev-mode": {
"name": "@ohif/mode-basic-dev-mode", "name": "@ohif/mode-basic-dev-mode",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"i18next": "17.3.1", "i18next": "17.3.1",
@ -507,17 +508,17 @@
"webpack-merge": "5.10.0", "webpack-merge": "5.10.0",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/extension-dicom-pdf": "3.13.0-beta.39", "@ohif/extension-dicom-pdf": "3.13.0-beta.41",
"@ohif/extension-dicom-video": "3.13.0-beta.39", "@ohif/extension-dicom-video": "3.13.0-beta.41",
}, },
}, },
"modes/basic-test-mode": { "modes/basic-test-mode": {
"name": "@ohif/mode-test", "name": "@ohif/mode-test",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"i18next": "17.3.1", "i18next": "17.3.1",
@ -527,19 +528,19 @@
"webpack-merge": "5.10.0", "webpack-merge": "5.10.0",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/extension-dicom-pdf": "3.13.0-beta.39", "@ohif/extension-dicom-pdf": "3.13.0-beta.41",
"@ohif/extension-dicom-video": "3.13.0-beta.39", "@ohif/extension-dicom-video": "3.13.0-beta.41",
"@ohif/extension-measurement-tracking": "3.13.0-beta.39", "@ohif/extension-measurement-tracking": "3.13.0-beta.41",
"@ohif/extension-test": "3.13.0-beta.39", "@ohif/extension-test": "3.13.0-beta.41",
}, },
}, },
"modes/longitudinal": { "modes/longitudinal": {
"name": "@ohif/mode-longitudinal", "name": "@ohif/mode-longitudinal",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"i18next": "17.3.1", "i18next": "17.3.1",
@ -549,33 +550,33 @@
"webpack-merge": "5.10.0", "webpack-merge": "5.10.0",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-rt": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-rt": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-seg": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-seg": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/extension-dicom-pdf": "3.13.0-beta.39", "@ohif/extension-dicom-pdf": "3.13.0-beta.41",
"@ohif/extension-dicom-video": "3.13.0-beta.39", "@ohif/extension-dicom-video": "3.13.0-beta.41",
"@ohif/extension-measurement-tracking": "3.13.0-beta.39", "@ohif/extension-measurement-tracking": "3.13.0-beta.41",
"@ohif/mode-basic": "3.13.0-beta.39", "@ohif/mode-basic": "3.13.0-beta.41",
}, },
}, },
"modes/microscopy": { "modes/microscopy": {
"name": "@ohif/mode-microscopy", "name": "@ohif/mode-microscopy",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"i18next": "17.3.1", "i18next": "17.3.1",
}, },
"peerDependencies": { "peerDependencies": {
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-dicom-microscopy": "3.13.0-beta.39", "@ohif/extension-dicom-microscopy": "3.13.0-beta.41",
}, },
}, },
"modes/preclinical-4d": { "modes/preclinical-4d": {
"name": "@ohif/mode-preclinical-4d", "name": "@ohif/mode-preclinical-4d",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
}, },
@ -594,7 +595,7 @@
}, },
"modes/segmentation": { "modes/segmentation": {
"name": "@ohif/mode-segmentation", "name": "@ohif/mode-segmentation",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"i18next": "17.3.1", "i18next": "17.3.1",
@ -636,7 +637,7 @@
}, },
"modes/tmtv": { "modes/tmtv": {
"name": "@ohif/mode-tmtv", "name": "@ohif/mode-tmtv",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"i18next": "17.3.1", "i18next": "17.3.1",
@ -657,14 +658,14 @@
}, },
"modes/usAnnotation": { "modes/usAnnotation": {
"name": "@ohif/mode-ultrasound-pleura-bline", "name": "@ohif/mode-ultrasound-pleura-bline",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-ultrasound-pleura-bline": "3.13.0-beta.39", "@ohif/extension-ultrasound-pleura-bline": "3.13.0-beta.41",
"i18next": "17.3.1", "i18next": "17.3.1",
}, },
"devDependencies": { "devDependencies": {
@ -693,34 +694,34 @@
}, },
"platform/app": { "platform/app": {
"name": "@ohif/app", "name": "@ohif/app",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/codec-charls": "1.2.3", "@cornerstonejs/codec-charls": "1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7", "@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.19.0", "@cornerstonejs/dicom-image-loader": "4.20.0",
"@emotion/serialize": "1.3.3", "@emotion/serialize": "1.3.3",
"@ohif/core": "3.13.0-beta.39", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.39", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-rt": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-rt": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-seg": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-seg": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.39", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.39", "@ohif/extension-default": "3.13.0-beta.41",
"@ohif/extension-dicom-microscopy": "3.13.0-beta.39", "@ohif/extension-dicom-microscopy": "3.13.0-beta.41",
"@ohif/extension-dicom-pdf": "3.13.0-beta.39", "@ohif/extension-dicom-pdf": "3.13.0-beta.41",
"@ohif/extension-dicom-video": "3.13.0-beta.39", "@ohif/extension-dicom-video": "3.13.0-beta.41",
"@ohif/extension-test": "3.13.0-beta.39", "@ohif/extension-test": "3.13.0-beta.41",
"@ohif/extension-ultrasound-pleura-bline": "3.13.0-beta.39", "@ohif/extension-ultrasound-pleura-bline": "3.13.0-beta.41",
"@ohif/i18n": "3.13.0-beta.39", "@ohif/i18n": "3.13.0-beta.41",
"@ohif/mode-basic-dev-mode": "3.13.0-beta.39", "@ohif/mode-basic-dev-mode": "3.13.0-beta.41",
"@ohif/mode-longitudinal": "3.13.0-beta.39", "@ohif/mode-longitudinal": "3.13.0-beta.41",
"@ohif/mode-microscopy": "3.13.0-beta.39", "@ohif/mode-microscopy": "3.13.0-beta.41",
"@ohif/mode-test": "3.13.0-beta.39", "@ohif/mode-test": "3.13.0-beta.41",
"@ohif/mode-ultrasound-pleura-bline": "3.13.0-beta.39", "@ohif/mode-ultrasound-pleura-bline": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"@ohif/ui-next": "3.13.0-beta.39", "@ohif/ui-next": "3.13.0-beta.41",
"@svgr/webpack": "8.1.0", "@svgr/webpack": "8.1.0",
"@types/react": "18.3.23", "@types/react": "18.3.23",
"classnames": "2.5.1", "classnames": "2.5.1",
@ -772,7 +773,7 @@
}, },
"platform/cli": { "platform/cli": {
"name": "@ohif/cli", "name": "@ohif/cli",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"bin": { "bin": {
"ohif-cli": "src/index.js", "ohif-cli": "src/index.js",
}, },
@ -796,7 +797,7 @@
}, },
"platform/core": { "platform/core": {
"name": "@ohif/core", "name": "@ohif/core",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
@ -821,16 +822,16 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7", "@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/dicom-image-loader": "4.19.0", "@cornerstonejs/dicom-image-loader": "4.20.0",
"@ohif/ui": "3.13.0-beta.39", "@ohif/ui": "3.13.0-beta.41",
"cornerstone-math": "0.1.9", "cornerstone-math": "0.1.9",
"dicom-parser": "1.8.21", "dicom-parser": "1.8.21",
}, },
}, },
"platform/i18n": { "platform/i18n": {
"name": "@ohif/i18n", "name": "@ohif/i18n",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"i18next-locize-backend": "2.2.2", "i18next-locize-backend": "2.2.2",
@ -855,7 +856,7 @@
}, },
"platform/ui": { "platform/ui": {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@testing-library/react": "13.4.0", "@testing-library/react": "13.4.0",
"browser-detect": "0.2.28", "browser-detect": "0.2.28",
@ -905,7 +906,7 @@
}, },
"platform/ui-next": { "platform/ui-next": {
"name": "@ohif/ui-next", "name": "@ohif/ui-next",
"version": "3.13.0-beta.39", "version": "3.13.0-beta.41",
"dependencies": { "dependencies": {
"@radix-ui/react-accordion": "1.2.11", "@radix-ui/react-accordion": "1.2.11",
"@radix-ui/react-checkbox": "1.3.2", "@radix-ui/react-checkbox": "1.3.2",
@ -1240,9 +1241,9 @@
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
"@cornerstonejs/adapters": ["@cornerstonejs/adapters@4.19.0", "", { "dependencies": { "@babel/runtime-corejs2": "7.26.10", "buffer": "6.0.3", "dcmjs": "0.49.4", "gl-matrix": "3.4.3", "ndarray": "1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "4.19.0", "@cornerstonejs/tools": "4.19.0" } }, "sha512-321AA/3CNwe4bE7NHtKz1agTr6ckvxga1iM8ohIDPHL5GLc0Ooj4kRJMjEatTe+WmeDa4lug3VhV2FxmqCPjTg=="], "@cornerstonejs/adapters": ["@cornerstonejs/adapters@4.20.0", "", { "dependencies": { "@babel/runtime-corejs2": "7.26.10", "buffer": "6.0.3", "dcmjs": "0.49.4", "gl-matrix": "3.4.3", "ndarray": "1.0.19" }, "peerDependencies": { "@cornerstonejs/core": "4.20.0", "@cornerstonejs/tools": "4.20.0" } }, "sha512-OxplEaHadms6mZ5fX8o9zkMVw7rSjxP6+KRovBAqLXlXsM9hj7MgencWG2JX6SR2bUXvHVusiV9cdECcDMNvsg=="],
"@cornerstonejs/ai": ["@cornerstonejs/ai@4.19.0", "", { "dependencies": { "@babel/runtime-corejs2": "7.26.10", "buffer": "6.0.3", "dcmjs": "0.49.4", "gl-matrix": "3.4.3", "lodash.clonedeep": "4.5.0", "ndarray": "1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "4.19.0", "@cornerstonejs/tools": "4.19.0" } }, "sha512-BWIbvUDLzmB0SEZhvFmhrhGEXdXplZkY5qEUfXCsbVcrUs9KsOrFrxLzTCRnTTnIX+Ybad9KoZWrwISmmMI3PQ=="], "@cornerstonejs/ai": ["@cornerstonejs/ai@4.20.0", "", { "dependencies": { "@babel/runtime-corejs2": "7.26.10", "buffer": "6.0.3", "dcmjs": "0.49.4", "gl-matrix": "3.4.3", "lodash.clonedeep": "4.5.0", "ndarray": "1.0.19", "onnxruntime-common": "1.17.1", "onnxruntime-web": "1.17.1" }, "peerDependencies": { "@cornerstonejs/core": "4.20.0", "@cornerstonejs/tools": "4.20.0" } }, "sha512-65wl8mXy/N6DUjLlNZvKLgWTGWVu4kWDupxNU5p7Pd3ZxU6RnbG6d3pcM30D/bcQEuUWLEQBDodtBeJEsxJxoA=="],
"@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="], "@cornerstonejs/calculate-suv": ["@cornerstonejs/calculate-suv@1.1.0", "", {}, "sha512-Q9XraiDJif9aMFArD2iEuDO/HXbcRVCqB7KfaHgDrdTTjgDFovS91Psbdim7crypRSvE6dh/+HKeFNHdvNkA6w=="],
@ -1254,15 +1255,15 @@
"@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="], "@cornerstonejs/codec-openjph": ["@cornerstonejs/codec-openjph@2.4.7", "", {}, "sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w=="],
"@cornerstonejs/core": ["@cornerstonejs/core@4.19.0", "", { "dependencies": { "@kitware/vtk.js": "34.15.1", "comlink": "4.4.2", "gl-matrix": "3.4.3", "loglevel": "1.9.2" } }, "sha512-t8v1JStDS+TJc50UA82BOCD0D9U4SD0VHuofRCXfPVd/LfDcLyUmcmoDuEeHTMCHGFm16j+nc3a/tqBiC0ZOxA=="], "@cornerstonejs/core": ["@cornerstonejs/core@4.20.0", "", { "dependencies": { "@kitware/vtk.js": "34.15.1", "comlink": "4.4.2", "gl-matrix": "3.4.3", "loglevel": "1.9.2" } }, "sha512-rcrctEIcyyAwQ6zcVt1wbYoTPyH6YNLNBOBB/aLeJZfLqGAfca+Cgf5K+KSGFJX1gZOdQZmYGmAF9P58B9PNVw=="],
"@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@4.19.0", "", { "dependencies": { "@cornerstonejs/codec-charls": "1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjph": "2.4.7", "comlink": "4.4.2", "dicom-parser": "1.8.21", "jpeg-lossless-decoder-js": "2.1.2", "pako": "2.1.0", "uuid": "9.0.1" }, "peerDependencies": { "@cornerstonejs/core": "4.19.0" } }, "sha512-aW7tNpYAnLZcrx6x9pPs2lDm/K00/1vzlca7CBZ8ihTZcHb2qPpzEsIPZ3F48udzy/Onl/pcw8IDEWTGWDnRzg=="], "@cornerstonejs/dicom-image-loader": ["@cornerstonejs/dicom-image-loader@4.20.0", "", { "dependencies": { "@cornerstonejs/codec-charls": "1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjph": "2.4.7", "comlink": "4.4.2", "dicom-parser": "1.8.21", "jpeg-lossless-decoder-js": "2.1.2", "pako": "2.1.0", "uuid": "9.0.1" }, "peerDependencies": { "@cornerstonejs/core": "4.20.0" } }, "sha512-A7N2ekKEMEEKbw0QecbmgtL89LMpLZDkyzt2a97cZM9qyUdhdETyzL8m6guVtP10Ga5l9Aq9mFt2ifGtwTw0wA=="],
"@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@4.19.0", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "4.19.0", "@cornerstonejs/tools": "4.19.0", "@kitware/vtk.js": "34.15.1" } }, "sha512-nQgZhNACFsi1frS7kpFf8+wywweHZFQjYRZ27OuaILUhcd4c5Rcz+4t3mfnzdqTuxtg3+ewPG+7oKyCFIUMrgw=="], "@cornerstonejs/labelmap-interpolation": ["@cornerstonejs/labelmap-interpolation@4.20.0", "", { "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165" }, "peerDependencies": { "@cornerstonejs/core": "4.20.0", "@cornerstonejs/tools": "4.20.0", "@kitware/vtk.js": "34.15.1" } }, "sha512-VDUkvRZPCk98NkBFH0qFNEfjQoyYp2Th2+0KAJtDPeA0aqSPhugh14uJQ5uKEkDzZFUG/TUpeaHi1kldx6C5Gw=="],
"@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@4.19.0", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "4.19.0", "@cornerstonejs/tools": "4.19.0", "@kitware/vtk.js": "34.15.1" } }, "sha512-WmB938EDefT21YBYDC2rZReJyyI0fS9QsIoc9FkJOEky6GdDWbpFRulqz1X0XGbsr2jbAQUh9p1KJaraEtXkIw=="], "@cornerstonejs/polymorphic-segmentation": ["@cornerstonejs/polymorphic-segmentation@4.20.0", "", { "dependencies": { "@icr/polyseg-wasm": "0.4.0" }, "peerDependencies": { "@cornerstonejs/core": "4.20.0", "@cornerstonejs/tools": "4.20.0", "@kitware/vtk.js": "34.15.1" } }, "sha512-tsMqAd/GrBeEFB2WU2K+YsGXeaP/1yD635yO1udpG2dloRaJrvFiK1dFMFCBLK3Xd768ZS1nq/MZEel00+vYxA=="],
"@cornerstonejs/tools": ["@cornerstonejs/tools@4.19.0", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "4.4.2", "lodash.get": "4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "4.19.0", "@kitware/vtk.js": "34.15.1", "@types/d3-array": "3.2.1", "@types/d3-interpolate": "3.0.4", "d3-array": "3.2.4", "d3-interpolate": "3.0.1", "gl-matrix": "3.4.3" } }, "sha512-yDK3I3U4tYDUWhZh3/NNBWWwqBd5X5qAfkn6tba5KMwKMLtjSm11s+Wg5O4BWOooW/XRflwj28VtE+Kgivlsag=="], "@cornerstonejs/tools": ["@cornerstonejs/tools@4.20.0", "", { "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "4.4.2", "lodash.get": "4.4.2" }, "peerDependencies": { "@cornerstonejs/core": "4.20.0", "@kitware/vtk.js": "34.15.1", "@types/d3-array": "3.2.1", "@types/d3-interpolate": "3.0.4", "d3-array": "3.2.4", "d3-interpolate": "3.0.1", "gl-matrix": "3.4.3" } }, "sha512-kphlW17eohBRk8MXEgdufM2t8MC5JqdlWUCfXu5rfg0KS1ut6iSWGAxFS+12noPR5qDzfVEYnD+4NkL37jg8pw=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],

View File

@ -46,8 +46,8 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.19.0", "@cornerstonejs/adapters": "4.20.0",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@kitware/vtk.js": "34.15.1" "@kitware/vtk.js": "34.15.1"
} }
} }

View File

@ -46,8 +46,8 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.19.0", "@cornerstonejs/adapters": "4.20.0",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@kitware/vtk.js": "34.15.1" "@kitware/vtk.js": "34.15.1"
} }
} }

View File

@ -46,9 +46,9 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.19.0", "@cornerstonejs/adapters": "4.20.0",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"classnames": "2.5.1" "classnames": "2.5.1"
} }
} }

View File

@ -42,8 +42,8 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"classnames": "2.5.1" "classnames": "2.5.1"
} }
} }

View File

@ -40,7 +40,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7", "@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.19.0", "@cornerstonejs/dicom-image-loader": "4.20.0",
"@ohif/core": "3.13.0-beta.41", "@ohif/core": "3.13.0-beta.41",
"@ohif/ui": "3.13.0-beta.41", "@ohif/ui": "3.13.0-beta.41",
"dcmjs": "0.49.4", "dcmjs": "0.49.4",
@ -53,12 +53,12 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/adapters": "4.19.0", "@cornerstonejs/adapters": "4.20.0",
"@cornerstonejs/ai": "4.19.0", "@cornerstonejs/ai": "4.20.0",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/labelmap-interpolation": "4.19.0", "@cornerstonejs/labelmap-interpolation": "4.20.0",
"@cornerstonejs/polymorphic-segmentation": "4.19.0", "@cornerstonejs/polymorphic-segmentation": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"@icr/polyseg-wasm": "0.4.0", "@icr/polyseg-wasm": "0.4.0",
"@itk-wasm/morphological-contour-interpolation": "1.1.0", "@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "34.15.1", "@kitware/vtk.js": "34.15.1",

View File

@ -32,8 +32,8 @@
"start": "yarn run dev" "start": "yarn run dev"
}, },
"peerDependencies": { "peerDependencies": {
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"@ohif/core": "3.13.0-beta.41", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.41", "@ohif/extension-default": "3.13.0-beta.41",

View File

@ -42,8 +42,8 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"@ohif/core": "3.13.0-beta.41", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.41", "@ohif/extension-cornerstone": "3.13.0-beta.41",
"@ohif/extension-default": "3.13.0-beta.41", "@ohif/extension-default": "3.13.0-beta.41",

View File

@ -34,8 +34,8 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.28.2", "@babel/runtime": "7.28.2",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/tools": "4.19.0", "@cornerstonejs/tools": "4.20.0",
"@ohif/core": "3.13.0-beta.41", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41", "@ohif/extension-cornerstone-dicom-sr": "3.13.0-beta.41",
"@ohif/extension-ultrasound-pleura-bline": "3.13.0-beta.41", "@ohif/extension-ultrasound-pleura-bline": "3.13.0-beta.41",

View File

@ -76,7 +76,11 @@
"lerna:cache": "./netlify-lerna-cache.sh", "lerna:cache": "./netlify-lerna-cache.sh",
"lerna:restore": "./netlify-lerna-restore.sh", "lerna:restore": "./netlify-lerna-restore.sh",
"lerna:customVersion": "node version.mjs", "lerna:customVersion": "node version.mjs",
"link-list": "npm ls --depth=0 --link=true" "link-list": "npm ls --depth=0 --link=true",
"cs3d:checkout": "cd libs/@cornerstonejs && git fetch && git checkout",
"cs3d:build": "cd libs/@cornerstonejs && yarn install && yarn run build:esm",
"cs3d:link": "node libs/@cornerstonejs/scripts/link-ohif-cornerstone-node-modules.mjs .",
"cs3d:unlink": "node libs/@cornerstonejs/scripts/unlink-ohif-cornerstone-node-modules.mjs ."
}, },
"dependencies": { "dependencies": {
"execa": "8.0.1" "execa": "8.0.1"

View File

@ -54,7 +54,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7", "@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/dicom-image-loader": "4.19.0", "@cornerstonejs/dicom-image-loader": "4.20.0",
"@emotion/serialize": "1.3.3", "@emotion/serialize": "1.3.3",
"@ohif/core": "3.13.0-beta.41", "@ohif/core": "3.13.0-beta.41",
"@ohif/extension-cornerstone": "3.13.0-beta.41", "@ohif/extension-cornerstone": "3.13.0-beta.41",

View File

@ -37,8 +37,8 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2", "@cornerstonejs/codec-libjpeg-turbo-8bit": "1.2.2",
"@cornerstonejs/codec-openjpeg": "1.3.0", "@cornerstonejs/codec-openjpeg": "1.3.0",
"@cornerstonejs/codec-openjph": "2.4.7", "@cornerstonejs/codec-openjph": "2.4.7",
"@cornerstonejs/core": "4.19.0", "@cornerstonejs/core": "4.20.0",
"@cornerstonejs/dicom-image-loader": "4.19.0", "@cornerstonejs/dicom-image-loader": "4.20.0",
"@ohif/ui": "3.13.0-beta.41", "@ohif/ui": "3.13.0-beta.41",
"cornerstone-math": "0.1.9", "cornerstone-math": "0.1.9",
"dicom-parser": "1.8.21" "dicom-parser": "1.8.21"

View File

@ -0,0 +1,141 @@
---
sidebar_position: 10
sidebar_label: CS3D Integration Testing
title: Cornerstone3D Integration Testing
summary: How to test OHIF against specific Cornerstone3D branches or versions, both locally and in CI.
---
# Cornerstone3D Integration Testing
OHIF can be tested against a specific Cornerstone3D (CS3D) branch or published version.
This is useful for:
- Validating OHIF changes against unreleased CS3D features
- Upgrading CS3D versions with automated testing
- Debugging issues that span both repositories
## CI Workflow
The **Playwright Tests** workflow in GitHub Actions has built-in CS3D integration support.
### Triggering via Label
1. Open a PR targeting `master` or `release/*`
2. Add the **`ohif-integration`** label
3. The workflow will automatically test against the latest `4.x` CS3D release from npm
To target a specific CS3D branch or version, add a `CS3D_REF` line anywhere in the PR
description body:
```
CS3D_REF: main
```
or
```
CS3D_REF: 4.18.2
```
The same branch and version formats from the table below are supported. If no `CS3D_REF`
line is found in the PR body, it defaults to `4.x`.
### Triggering via workflow_dispatch
Use **Actions > Playwright Tests > Run workflow** and set the `cs3d_ref` input:
| Input | Behavior |
|-------|----------|
| `4.x` (default) | Install latest published 4.x release |
| `4.18.2` | Install exact version 4.18.2 |
| `4.18.2-beta.3` | Install exact prerelease |
| `4.17.x` | Install latest 4.17.x release |
| `main` | Clone and build CS3D `main` branch from source |
| `myorg:feat/foo` | Clone from `github.com/myorg/cornerstone3D.git`, branch `feat/foo` |
### Branch vs Version Behavior
**Version path** (e.g., `4.x`, `4.18.2`):
- Installs OHIF dependencies normally
- Updates all `@cornerstonejs/*` versions in workspace package.json files
- Re-installs to fetch the specified versions from npm
- Runs all tests
- Builds and deploys a Netlify preview
- The tested version is logged in workflow annotations
**Branch path** (e.g., `main`, `myorg:feat/foo`):
- Clones CS3D into `libs/@cornerstonejs`
- Builds CS3D from source (`build:esm`)
- Links built packages into OHIF's `node_modules`
- Runs all tests
- Builds and deploys a Netlify preview
- **Intentionally fails** at the end to prevent merging — re-run with a published version before merge
### Version Commit on Merge
The CS3D version update is **not committed** during the PR workflow. The tested version is
recorded in workflow annotations (`::notice::` messages). To apply the version update:
1. After the PR is approved and the version test passes, note the tested version from the logs
2. Run `node .scripts/cs3d-set-version.mjs <version>` locally or as part of the merge process
3. Commit the updated package.json files
## Local Development
### Setting Up a CS3D Worktree
For local development, use the CS3D repo's `link:cs3d` script to create a git worktree.
This shares a single git repository across multiple OHIF checkouts:
```bash
# From your cornerstone3D repo:
bun run link:cs3d /path/to/ohif main
# or with a specific branch:
bun run link:cs3d /path/to/ohif origin:feat/my-feature
```
This creates `libs/@cornerstonejs` as a worktree and generates `link-cs3d.js` / `unlink-cs3d.js`
helper scripts. See the [Local Linking](./link.md) docs for more on the linking workflow.
### Package Scripts
Once `libs/@cornerstonejs` exists (via worktree or clone):
```bash
# Switch branches in the existing worktree
yarn cs3d:checkout <branch-name>
# Install dependencies and build ESM
yarn cs3d:build
# Symlink CS3D packages into node_modules/@cornerstonejs
yarn cs3d:link
# Remove symlinks and restore packages from npm registry
yarn cs3d:unlink
```
:::note
`cs3d:checkout` requires `libs/@cornerstonejs` to already exist. It runs
`git fetch && git checkout` inside that directory.
:::
### Wildcard Versions
The `cs3d-resolve-version.mjs` script resolves patterns like `4.x` or `4.17.x`:
```bash
node .scripts/cs3d-resolve-version.mjs 4.x
# prints: 4.19.1 (or whatever the latest 4.x release is)
```
### Updating All CS3D Versions
```bash
node .scripts/cs3d-set-version.mjs 4.19.0
# Updates all @cornerstonejs/* dependencies across the workspace
```
This updates the 8 main CS3D packages (adapters, ai, core, dicom-image-loader,
labelmap-interpolation, nifti-volume-loader, polymorphic-segmentation, tools)
but not codec packages.

View File

@ -19,7 +19,7 @@ test.describe('3D four up Test', async () => {
await attemptAction(() => reduce3DViewportSize(page), 10, 100); await attemptAction(() => reduce3DViewportSize(page), 10, 100);
await page.waitForTimeout(5000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot( await checkForScreenshot(
page, page,

View File

@ -17,7 +17,7 @@ test.describe('3D main Test', async () => {
test('should render 3D main correctly.', async ({ page, mainToolbarPageObject }) => { test('should render 3D main correctly.', async ({ page, mainToolbarPageObject }) => {
await mainToolbarPageObject.layoutSelection.threeDMain.click(); await mainToolbarPageObject.layoutSelection.threeDMain.click();
await attemptAction(() => reduce3DViewportSize(page), 10, 100); await attemptAction(() => reduce3DViewportSize(page), 10, 100);
await page.waitForTimeout(5000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot(page, page, screenShotPaths.threeDMain.threeDMainDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.threeDMain.threeDMainDisplayedCorrectly);
}); });
}); });

View File

@ -17,7 +17,7 @@ test.describe('3D only Test', async () => {
test('should render 3D only correctly.', async ({ page, mainToolbarPageObject }) => { test('should render 3D only correctly.', async ({ page, mainToolbarPageObject }) => {
await mainToolbarPageObject.layoutSelection.threeDOnly.click(); await mainToolbarPageObject.layoutSelection.threeDOnly.click();
await attemptAction(() => reduce3DViewportSize(page), 10, 100); await attemptAction(() => reduce3DViewportSize(page), 10, 100);
await page.waitForTimeout(5000); await mainToolbarPageObject.waitForVolumeLoad();
// Use a 4 percent diff pixel ratio to account for slight color differences in the 3D viewport // Use a 4 percent diff pixel ratio to account for slight color differences in the 3D viewport
await checkForScreenshot({ await checkForScreenshot({
page, page,

View File

@ -18,7 +18,7 @@ test.describe('3D primary Test', async () => {
await mainToolbarPageObject.layoutSelection.threeDPrimary.click(); await mainToolbarPageObject.layoutSelection.threeDPrimary.click();
await attemptAction(() => reduce3DViewportSize(page), 10, 100); await attemptAction(() => reduce3DViewportSize(page), 10, 100);
await page.waitForTimeout(5000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot( await checkForScreenshot(
page, page,
page, page,

View File

@ -28,7 +28,7 @@ test('should display the arrow tool and allow free-form text to be entered', asy
await DOMOverlayPageObject.viewport.measurementTracking.confirm.click(); await DOMOverlayPageObject.viewport.measurementTracking.confirm.click();
await page.waitForTimeout(2000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot({ await checkForScreenshot({
page, page,
@ -42,7 +42,7 @@ test('should display the arrow tool and allow free-form text to be entered', asy
await DOMOverlayPageObject.dialog.input.fillAndSave('Neil Peart was the drummer for Rush'); await DOMOverlayPageObject.dialog.input.fillAndSave('Neil Peart was the drummer for Rush');
await page.waitForTimeout(2000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot({ await checkForScreenshot({
page, page,
@ -56,7 +56,7 @@ test('should display the arrow tool and allow free-form text to be entered', asy
.nthMeasurement(0) .nthMeasurement(0)
.actions.rename('Drummer annotation arrow'); .actions.rename('Drummer annotation arrow');
await page.waitForTimeout(2000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot({ await checkForScreenshot({
page, page,

View File

@ -39,6 +39,7 @@ test('should display the long series name properly within the series select butt
const mode = 'viewer'; const mode = 'viewer';
await visitStudy(page, studyInstanceUID, mode, 2000); await visitStudy(page, studyInstanceUID, mode, 2000);
await mainToolbarPageObject.waitForVolumeLoad();
await mainToolbarPageObject.moreTools.tagBrowser.click(); await mainToolbarPageObject.moreTools.tagBrowser.click();
const dicomTagBrowser = DOMOverlayPageObject.dialog.dicomTagBrowser; const dicomTagBrowser = DOMOverlayPageObject.dialog.dicomTagBrowser;

View File

@ -8,5 +8,6 @@ test.beforeEach(async ({ page }) => {
test('should invert the image', async ({ page, mainToolbarPageObject }) => { test('should invert the image', async ({ page, mainToolbarPageObject }) => {
await mainToolbarPageObject.moreTools.invert.click(); await mainToolbarPageObject.moreTools.invert.click();
await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot(page, page, screenShotPaths.invert.invertDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.invert.invertDisplayedCorrectly);
}); });

View File

@ -14,8 +14,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
}) => { }) => {
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
// Wait 5 seconds for MPR to load. This is necessary in particular when screen shots are added or replaced. await mainToolbarPageObject.waitForVolumeLoad();
await page.waitForTimeout(10000);
await checkForScreenshot( await checkForScreenshot(
page, page,
@ -35,8 +34,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
await assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); await assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
// Wait 5 seconds for RT to load. This is necessary in particular when screen shots are added or replaced. await mainToolbarPageObject.waitForVolumeLoad();
await page.waitForTimeout(5000);
await checkForScreenshot({ await checkForScreenshot({
page, page,

View File

@ -10,6 +10,7 @@ test.beforeEach(async ({ page }) => {
test('should display multiple segmentation overlays (both SEG and RT)', async ({ test('should display multiple segmentation overlays (both SEG and RT)', async ({
page, page,
mainToolbarPageObject,
rightPanelPageObject, rightPanelPageObject,
viewportPageObject, viewportPageObject,
}) => { }) => {
@ -20,24 +21,21 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
await dataOverlayPageObject.toggle(); await dataOverlayPageObject.toggle();
await dataOverlayPageObject.addSegmentation('2d-tta_nnU-Net_Segmentation'); await dataOverlayPageObject.addSegmentation('2d-tta_nnU-Net_Segmentation');
// A short wait after each overlay is selected to ensure it loads. await mainToolbarPageObject.waitForVolumeLoad();
await page.waitForTimeout(5000);
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await dataOverlayPageObject.addSegmentation('Segmentation'); await dataOverlayPageObject.addSegmentation('Segmentation');
// A short wait after each overlay is selected to ensure it loads. await mainToolbarPageObject.waitForVolumeLoad();
await page.waitForTimeout(5000);
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
await dataOverlayPageObject.addSegmentation('3d_lowres-tta_nnU-Net_Segmentation'); await dataOverlayPageObject.addSegmentation('3d_lowres-tta_nnU-Net_Segmentation');
// A short wait after each overlay is selected to ensure it loads. await mainToolbarPageObject.waitForVolumeLoad();
await page.waitForTimeout(5000);
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });
@ -61,7 +59,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
// Navigate to image 56. // Navigate to image 56.
await press({ page, key: 'ArrowDown', nTimes: 55 }); await press({ page, key: 'ArrowDown', nTimes: 55 });
await page.waitForTimeout(5000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot({ await checkForScreenshot({
page, page,
@ -73,8 +71,7 @@ test('should display multiple segmentation overlays (both SEG and RT)', async ({
await dataOverlayPageObject.addSegmentation('Series 3 - RTSTRUCT'); await dataOverlayPageObject.addSegmentation('Series 3 - RTSTRUCT');
// A short wait after each overlay is selected to ensure it loads. await mainToolbarPageObject.waitForVolumeLoad();
await page.waitForTimeout(5000);
// Adding an overlay should not show the LOAD button. // Adding an overlay should not show the LOAD button.
assertNumberOfModalityLoadBadges({ page, expectedCount: 0 }); assertNumberOfModalityLoadBadges({ page, expectedCount: 0 });

View File

@ -24,7 +24,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
// Hide the overlay menu. // Hide the overlay menu.
await dataOverlayPageObject.toggle(); await dataOverlayPageObject.toggle();
await page.waitForTimeout(5000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot( await checkForScreenshot(
page, page,
@ -34,7 +34,7 @@ test('should launch MPR with unhydrated RTSTRUCT chosen from the data overlay me
await mainToolbarPageObject.layoutSelection.MPR.click(); await mainToolbarPageObject.layoutSelection.MPR.click();
await page.waitForTimeout(5000); await mainToolbarPageObject.waitForVolumeLoad();
await checkForScreenshot( await checkForScreenshot(
page, page,

View File

@ -1,5 +1,4 @@
import { test } from 'playwright-test-coverage'; import { test, visitStudy, checkForScreenshot, screenShotPaths } from './utils';
import { visitStudy, checkForScreenshot, screenShotPaths } from './utils';
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
const studyInstanceUID = const studyInstanceUID =
@ -8,7 +7,8 @@ test.beforeEach(async ({ page }) => {
await visitStudy(page, studyInstanceUID, mode, 10000); await visitStudy(page, studyInstanceUID, mode, 10000);
}); });
test('should properly display MPR for MR', async ({ page }) => { test('should properly display MPR for MR', async ({ page, mainToolbarPageObject }) => {
await mainToolbarPageObject.waitForVolumeLoad();
await page.getByTestId('side-panel-header-right').click(); await page.getByTestId('side-panel-header-right').click();
// await page.getByTestId('study-browser-thumbnail-no-image').dblclick(); // await page.getByTestId('study-browser-thumbnail-no-image').dblclick();
await checkForScreenshot(page, page, screenShotPaths.mpr2.mprDisplayedCorrectly); await checkForScreenshot(page, page, screenShotPaths.mpr2.mprDisplayedCorrectly);

View File

@ -7,6 +7,11 @@ export class MainToolbarPageObject {
this.page = page; this.page = page;
} }
async waitForVolumeLoad(): Promise<void> {
await this.page.waitForTimeout(2000);
await this.page.waitForLoadState('networkidle', { timeout: 120000 });
}
get crosshairs() { get crosshairs() {
const button = this.page.getByTestId('Crosshairs'); const button = this.page.getByTestId('Crosshairs');
return { return {
@ -18,6 +23,7 @@ export class MainToolbarPageObject {
} }
get layoutSelection() { get layoutSelection() {
const page = this.page; const page = this.page;
const self = this;
const button = page.getByTestId('Layout'); const button = page.getByTestId('Layout');
const layoutSelection = { const layoutSelection = {
@ -46,6 +52,7 @@ export class MainToolbarPageObject {
async click() { async click() {
await layoutSelection.click(); await layoutSelection.click();
await button.click(); await button.click();
await self.waitForVolumeLoad();
}, },
}; };
}, },
@ -56,6 +63,7 @@ export class MainToolbarPageObject {
async click() { async click() {
await layoutSelection.click(); await layoutSelection.click();
await button.click(); await button.click();
await self.waitForVolumeLoad();
}, },
}; };
}, },
@ -66,6 +74,7 @@ export class MainToolbarPageObject {
async click() { async click() {
await layoutSelection.click(); await layoutSelection.click();
await button.click(); await button.click();
await self.waitForVolumeLoad();
}, },
}; };
}, },
@ -76,6 +85,7 @@ export class MainToolbarPageObject {
async click() { async click() {
await layoutSelection.click(); await layoutSelection.click();
await button.click(); await button.click();
await self.waitForVolumeLoad();
}, },
}; };
}, },
@ -86,6 +96,7 @@ export class MainToolbarPageObject {
async click() { async click() {
await layoutSelection.click(); await layoutSelection.click();
await button.click(); await button.click();
await self.waitForVolumeLoad();
}, },
}; };
}, },

View File

@ -1,8 +1,12 @@
import { execa } from 'execa'; import { execa } from 'execa';
import semver from 'semver'; import semver from 'semver';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { spawnSync } from 'child_process';
async function run() { async function run() {
// Log build context for diagnosing GitHub build issues (OHIF/CS3D branch and version)
spawnSync('node', ['.scripts/log-build-context.mjs'], { stdio: 'inherit', cwd: process.cwd() });
const { stdout: branchName } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD']); const { stdout: branchName } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
console.log('Current branch:', branchName); console.log('Current branch:', branchName);

View File

@ -1946,10 +1946,10 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@cornerstonejs/adapters@4.19.0": "@cornerstonejs/adapters@4.20.0":
version "4.19.0" version "4.20.0"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-4.19.0.tgz#6e1035a0617ddcb6938eaf6100ef5f86e7df398b" resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-4.20.0.tgz#94e21f2955d87d2c0ba47ebedaad77ee621f7ecf"
integrity sha512-321AA/3CNwe4bE7NHtKz1agTr6ckvxga1iM8ohIDPHL5GLc0Ooj4kRJMjEatTe+WmeDa4lug3VhV2FxmqCPjTg== integrity sha512-OxplEaHadms6mZ5fX8o9zkMVw7rSjxP6+KRovBAqLXlXsM9hj7MgencWG2JX6SR2bUXvHVusiV9cdECcDMNvsg==
dependencies: dependencies:
"@babel/runtime-corejs2" "7.26.10" "@babel/runtime-corejs2" "7.26.10"
buffer "6.0.3" buffer "6.0.3"
@ -1957,10 +1957,10 @@
gl-matrix "3.4.3" gl-matrix "3.4.3"
ndarray "1.0.19" ndarray "1.0.19"
"@cornerstonejs/ai@4.19.0": "@cornerstonejs/ai@4.20.0":
version "4.19.0" version "4.20.0"
resolved "https://registry.yarnpkg.com/@cornerstonejs/ai/-/ai-4.19.0.tgz#04d44c620632c095fda5987c7a5d07e2c26a9389" resolved "https://registry.yarnpkg.com/@cornerstonejs/ai/-/ai-4.20.0.tgz#9ee423cc99c2122360c8359f643a8e470717fcf5"
integrity sha512-BWIbvUDLzmB0SEZhvFmhrhGEXdXplZkY5qEUfXCsbVcrUs9KsOrFrxLzTCRnTTnIX+Ybad9KoZWrwISmmMI3PQ== integrity sha512-65wl8mXy/N6DUjLlNZvKLgWTGWVu4kWDupxNU5p7Pd3ZxU6RnbG6d3pcM30D/bcQEuUWLEQBDodtBeJEsxJxoA==
dependencies: dependencies:
"@babel/runtime-corejs2" "7.26.10" "@babel/runtime-corejs2" "7.26.10"
buffer "6.0.3" buffer "6.0.3"
@ -1996,20 +1996,20 @@
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.7.tgz#784394d05d8cf735640e9c3baa206535ee8e0376" resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.7.tgz#784394d05d8cf735640e9c3baa206535ee8e0376"
integrity sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w== integrity sha512-qvP4q4JDib7mi9r7LqKOwqz7YZ8gjtDX4ZCezeYf8+eb7MBXCz5uXAMeVF3yz9Axw4XiIMdB/pqXkm8tqCl13w==
"@cornerstonejs/core@4.19.0": "@cornerstonejs/core@4.20.0":
version "4.19.0" version "4.20.0"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-4.19.0.tgz#45406e3ae5dfb07727b090f9e33b234c035b31c9" resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-4.20.0.tgz#9381f113879dd9fc2847c8e4d9e624602751bbef"
integrity sha512-t8v1JStDS+TJc50UA82BOCD0D9U4SD0VHuofRCXfPVd/LfDcLyUmcmoDuEeHTMCHGFm16j+nc3a/tqBiC0ZOxA== integrity sha512-rcrctEIcyyAwQ6zcVt1wbYoTPyH6YNLNBOBB/aLeJZfLqGAfca+Cgf5K+KSGFJX1gZOdQZmYGmAF9P58B9PNVw==
dependencies: dependencies:
"@kitware/vtk.js" "34.15.1" "@kitware/vtk.js" "34.15.1"
comlink "4.4.2" comlink "4.4.2"
gl-matrix "3.4.3" gl-matrix "3.4.3"
loglevel "1.9.2" loglevel "1.9.2"
"@cornerstonejs/dicom-image-loader@4.19.0": "@cornerstonejs/dicom-image-loader@4.20.0":
version "4.19.0" version "4.20.0"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-4.19.0.tgz#426d65626d1c68208ab22d85763da528a16c118e" resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-4.20.0.tgz#bf6425f2829268311b3fbd6a36bb584bdc431fc6"
integrity sha512-aW7tNpYAnLZcrx6x9pPs2lDm/K00/1vzlca7CBZ8ihTZcHb2qPpzEsIPZ3F48udzy/Onl/pcw8IDEWTGWDnRzg== integrity sha512-A7N2ekKEMEEKbw0QecbmgtL89LMpLZDkyzt2a97cZM9qyUdhdETyzL8m6guVtP10Ga5l9Aq9mFt2ifGtwTw0wA==
dependencies: dependencies:
"@cornerstonejs/codec-charls" "1.2.3" "@cornerstonejs/codec-charls" "1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "1.2.2" "@cornerstonejs/codec-libjpeg-turbo-8bit" "1.2.2"
@ -2021,25 +2021,25 @@
pako "2.1.0" pako "2.1.0"
uuid "9.0.1" uuid "9.0.1"
"@cornerstonejs/labelmap-interpolation@4.19.0": "@cornerstonejs/labelmap-interpolation@4.20.0":
version "4.19.0" version "4.20.0"
resolved "https://registry.yarnpkg.com/@cornerstonejs/labelmap-interpolation/-/labelmap-interpolation-4.19.0.tgz#101cb6f7f7eb64ee9a2933cfe2ef71af66abfa05" resolved "https://registry.yarnpkg.com/@cornerstonejs/labelmap-interpolation/-/labelmap-interpolation-4.20.0.tgz#152fea1a574f825bed2cd0a69013ebb4405dcda7"
integrity sha512-nQgZhNACFsi1frS7kpFf8+wywweHZFQjYRZ27OuaILUhcd4c5Rcz+4t3mfnzdqTuxtg3+ewPG+7oKyCFIUMrgw== integrity sha512-VDUkvRZPCk98NkBFH0qFNEfjQoyYp2Th2+0KAJtDPeA0aqSPhugh14uJQ5uKEkDzZFUG/TUpeaHi1kldx6C5Gw==
dependencies: dependencies:
"@itk-wasm/morphological-contour-interpolation" "1.1.0" "@itk-wasm/morphological-contour-interpolation" "1.1.0"
itk-wasm "1.0.0-b.165" itk-wasm "1.0.0-b.165"
"@cornerstonejs/polymorphic-segmentation@4.19.0": "@cornerstonejs/polymorphic-segmentation@4.20.0":
version "4.19.0" version "4.20.0"
resolved "https://registry.yarnpkg.com/@cornerstonejs/polymorphic-segmentation/-/polymorphic-segmentation-4.19.0.tgz#79491ee8293c53008973286b59bc38b88426ebba" resolved "https://registry.yarnpkg.com/@cornerstonejs/polymorphic-segmentation/-/polymorphic-segmentation-4.20.0.tgz#d4981f9df051217cddf6a69730386339fe69b5d2"
integrity sha512-WmB938EDefT21YBYDC2rZReJyyI0fS9QsIoc9FkJOEky6GdDWbpFRulqz1X0XGbsr2jbAQUh9p1KJaraEtXkIw== integrity sha512-tsMqAd/GrBeEFB2WU2K+YsGXeaP/1yD635yO1udpG2dloRaJrvFiK1dFMFCBLK3Xd768ZS1nq/MZEel00+vYxA==
dependencies: dependencies:
"@icr/polyseg-wasm" "0.4.0" "@icr/polyseg-wasm" "0.4.0"
"@cornerstonejs/tools@4.19.0": "@cornerstonejs/tools@4.20.0":
version "4.19.0" version "4.20.0"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-4.19.0.tgz#1db6bd62988c6fd4fb2497bedadffdbe16109c04" resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-4.20.0.tgz#58d834f80513689c354e052b9641516a62d24ad8"
integrity sha512-yDK3I3U4tYDUWhZh3/NNBWWwqBd5X5qAfkn6tba5KMwKMLtjSm11s+Wg5O4BWOooW/XRflwj28VtE+Kgivlsag== integrity sha512-kphlW17eohBRk8MXEgdufM2t8MC5JqdlWUCfXu5rfg0KS1ut6iSWGAxFS+12noPR5qDzfVEYnD+4NkL37jg8pw==
dependencies: dependencies:
"@types/offscreencanvas" "2019.7.3" "@types/offscreencanvas" "2019.7.3"
comlink "4.4.2" comlink "4.4.2"