Compare commits
87
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f412dc254b | ||
|
|
c0ed128fb7 | ||
|
|
c829270c80 | ||
|
|
a16d612a8a | ||
|
|
5f94ebe81a | ||
|
|
8218a9d034 | ||
|
|
4b94f18f7c | ||
|
|
8e7e9ca02f | ||
|
|
cd4e6408bc | ||
|
|
c1fced0573 | ||
|
|
e41019f2b3 | ||
|
|
383f45823c | ||
|
|
6337d2e9cb | ||
|
|
e987710908 | ||
|
|
7dbe5e37b0 | ||
|
|
ff974e2a03 | ||
|
|
cccf74d6ea | ||
|
|
4be8495335 | ||
|
|
1cb8d328a4 | ||
|
|
2c8dda01ae | ||
|
|
4e185e4b21 | ||
|
|
4732e8093e | ||
|
|
984471e972 | ||
|
|
25cb71b3f6 | ||
|
|
0e75f6d62c | ||
|
|
d75b3e25b6 | ||
|
|
0bb9b17636 | ||
|
|
b21ee9bc5b | ||
|
|
7ed0f6d53b | ||
|
|
98f8187dfe | ||
|
|
4643d6dc40 | ||
|
|
258b353269 | ||
|
|
0f39496290 | ||
|
|
325cb3f7d5 | ||
|
|
088b563bf6 | ||
|
|
09ac43f297 | ||
|
|
d59828ab22 | ||
|
|
5f04b0f3be | ||
|
|
bdb847a6eb | ||
|
|
0052bf1ae3 | ||
|
|
41815ee2af | ||
|
|
56fdb82812 | ||
|
|
fcde5ddbce | ||
|
|
7818f406d8 | ||
|
|
329d5b6237 | ||
|
|
3b8faa8a76 | ||
|
|
59136e5435 | ||
|
|
88fccda491 | ||
|
|
5688a2b4b8 | ||
|
|
f02ce22ae3 | ||
|
|
ff5a152270 | ||
|
|
082014a694 | ||
|
|
194b4971c2 | ||
|
|
d6c7b9c23c | ||
|
|
b82465344c | ||
|
|
f8cb810b92 | ||
|
|
dbda78df15 | ||
|
|
66a16cd22f | ||
|
|
5a9fcd4bf0 | ||
|
|
69cddff628 | ||
|
|
08c14e9ba2 | ||
|
|
7fd522ab68 | ||
|
|
df74557866 | ||
|
|
69063a41b7 | ||
|
|
41fb597085 | ||
|
|
a3a35471a9 | ||
|
|
a0bcac15cd | ||
|
|
ac7875128c | ||
|
|
bd02205ef9 | ||
|
|
9535422361 | ||
|
|
a13380407b | ||
|
|
1a0d7a930c | ||
|
|
91117f40f8 | ||
|
|
a41ed3bade | ||
|
|
f12f2bfec5 | ||
|
|
4a87302f06 | ||
|
|
5dae147155 | ||
|
|
4952dab010 | ||
|
|
5425308e55 | ||
|
|
c50298f394 | ||
|
|
a82ebeebb2 | ||
|
|
ed94df0dfe | ||
|
|
4926a9532c | ||
|
|
7a36eba67c | ||
|
|
8a08d26f01 | ||
|
|
de598cdc1f | ||
|
|
a9b180c5d4 |
No files matched your search
@@ -0,0 +1,108 @@
|
||||
name: Build and Deploy Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
env:
|
||||
ACTIONS_STEP_DEBUG: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-deploy-docs:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
# Need permissions to read actions and pull requests
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20 # Or your desired Node version
|
||||
|
||||
- name: Install root dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
# Removed Playwright tests and coverage generation steps
|
||||
|
||||
- name: Find PR and associated workflow run
|
||||
id: find_pr_run
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
MERGE_COMMIT_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
# Find the PR associated with the merge commit SHA
|
||||
# Note: This relies on the merge commit being directly pushed to main
|
||||
PR_DATA=$(gh pr list --state merged --search "$MERGE_COMMIT_SHA" --json number,headRefOid --jq '.[0]')
|
||||
if [ -z "$PR_DATA" ]; then
|
||||
echo "Could not find merged PR for commit $MERGE_COMMIT_SHA."
|
||||
# Decide how to handle - fail, or maybe generate coverage now?
|
||||
# For now, let's fail.
|
||||
exit 1
|
||||
fi
|
||||
PR_HEAD_SHA=$(echo "$PR_DATA" | jq -r '.headRefOid')
|
||||
PR_NUMBER=$(echo "$PR_DATA" | jq -r '.number')
|
||||
echo "Found PR Number: $PR_NUMBER"
|
||||
echo "Found PR Head SHA: $PR_HEAD_SHA"
|
||||
|
||||
# Find the latest workflow run ID for the playwright workflow on the PR head commit
|
||||
# Uses the workflow file name 'playwright.yml'
|
||||
# Remove the --status success flag to find any run
|
||||
RUN_ID=$(gh run list --workflow playwright.yml --commit "$PR_HEAD_SHA" --event pull_request --json databaseId --jq '.[0].databaseId')
|
||||
|
||||
if [ -z "$RUN_ID" ]; then
|
||||
echo "Could not find any 'playwright.yml' run for PR $PR_NUMBER (Head SHA: $PR_HEAD_SHA)."
|
||||
# Decide how to handle - maybe try finding the artifact from the merge commit run if that exists?
|
||||
# For now, let's fail.
|
||||
exit 1
|
||||
fi
|
||||
echo "Found Run ID: $RUN_ID"
|
||||
echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download coverage artifact from PR run
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
mkdir -p ./coverage-artifact
|
||||
gh run download ${{ steps.find_pr_run.outputs.run_id }} -n coverage-report-pr --dir ./coverage-artifact
|
||||
# Check if download was successful (e.g., check if files exist)
|
||||
if [ ! -f ./coverage-artifact/base.css ]; then
|
||||
echo "Failed to download or find expected files in artifact 'coverage-report-pr' from run ${{ steps.find_pr_run.outputs.run_id }}."
|
||||
exit 1
|
||||
fi
|
||||
echo "Artifact downloaded successfully."
|
||||
|
||||
- name: Install docs dependencies
|
||||
run: cd platform/docs && bun install
|
||||
|
||||
- name: Copy coverage to docs static directory
|
||||
run: |
|
||||
# Copy files from the downloaded artifact directory
|
||||
mkdir -p platform/docs/static/coverage
|
||||
cp -r ./coverage-artifact/* platform/docs/static/coverage/
|
||||
# Copy specific asset files from the downloaded artifact root to static root
|
||||
cp ./coverage-artifact/base.css platform/docs/static/
|
||||
cp ./coverage-artifact/block-navigation.js platform/docs/static/
|
||||
cp ./coverage-artifact/prettify.css platform/docs/static/
|
||||
cp ./coverage-artifact/prettify.js platform/docs/static/
|
||||
cp ./coverage-artifact/favicon.png platform/docs/static/
|
||||
cp ./coverage-artifact/sort-arrow-sprite.png platform/docs/static/
|
||||
cp ./coverage-artifact/sorter.js platform/docs/static/
|
||||
|
||||
- name: Build docs
|
||||
run: cd platform/docs && bun run build
|
||||
|
||||
- name: Deploy to Netlify
|
||||
run: |
|
||||
cd platform/docs
|
||||
npx netlify-cli deploy --dir=./build --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
||||
@@ -0,0 +1,98 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '15 1 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||
# or others). This is typically only required for manual builds.
|
||||
# - name: Setup runtime (example)
|
||||
# uses: actions/setup-example@v1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -1,75 +1,41 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
branches: [master]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
playwright-tests:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.51.1-noble
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
shardTotal: [10]
|
||||
node-version: [20]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install unzip
|
||||
run: apt-get update && apt-get install -y unzip
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Set up Git safe directory
|
||||
run: git config --global --add safe.directory /__w/Viewers/Viewers
|
||||
# - name: Install Playwright browsers
|
||||
# run: bun x playwright install-deps chromium
|
||||
- name: Run Playwright tests
|
||||
run: |
|
||||
NODE_OPTIONS="--max_old_space_size=8192" TEST_ENV=true bun x playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
export NODE_OPTIONS="--max_old_space_size=10192"
|
||||
bun run test:e2e:coverage
|
||||
- name: create the coverage report
|
||||
run: |
|
||||
bun nyc report --reporter=lcov --reporter=text
|
||||
- name: Upload the coverage report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: blob-report-${{ matrix.shardIndex }}
|
||||
path: blob-report
|
||||
retention-days: 1
|
||||
|
||||
merge-reports:
|
||||
if: ${{ !cancelled() }}
|
||||
needs: [playwright-tests]
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.48.1-focal
|
||||
steps:
|
||||
- name: Install unzip
|
||||
run: apt-get update && apt-get install -y unzip
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Set up Git safe directory
|
||||
run: git config --global --add safe.directory /__w/Viewers/Viewers
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: all-blob-reports
|
||||
pattern: blob-report-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Merge into HTML Report
|
||||
run: bun x playwright merge-reports --reporter html ./all-blob-reports
|
||||
|
||||
- name: Upload HTML report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: html-report--attempt-${{ github.run_attempt }}
|
||||
path: playwright-report
|
||||
retention-days: 14
|
||||
name: coverage-report-pr
|
||||
path: coverage
|
||||
retention-days: 3
|
||||
+4
-1
@@ -1,7 +1,7 @@
|
||||
# Packages
|
||||
node_modules
|
||||
.cursor/
|
||||
|
||||
.nyc_output/
|
||||
# Output
|
||||
build
|
||||
dist
|
||||
@@ -13,6 +13,7 @@ coverage/
|
||||
.yarn/
|
||||
.nx/
|
||||
addOns/yarn.lock
|
||||
playwright-report/
|
||||
|
||||
# YALC (for Erik)
|
||||
.yalc
|
||||
@@ -57,3 +58,5 @@ tests/test-results/
|
||||
tests/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
**/.claude/settings.local.json
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "@istanbuljs/nyc-config-typescript",
|
||||
"instrument": true,
|
||||
"sourceMap": true,
|
||||
"cache": false,
|
||||
"all": true,
|
||||
"include": [
|
||||
"platform/*/src/**/*.ts",
|
||||
"platform/*/src/**/*.js",
|
||||
"extensions/*/src/**/*.ts",
|
||||
"extensions/*/src/**/*.js",
|
||||
"modes/*/src/**/*.ts",
|
||||
"modes/*/src/**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/test/**",
|
||||
"**/tests/**",
|
||||
"**/examples/**",
|
||||
"**/stories/**",
|
||||
"platform/docs/**"
|
||||
]
|
||||
}
|
||||
Vendored
+3
-1
@@ -109,6 +109,8 @@
|
||||
"wado",
|
||||
"wadors",
|
||||
"wadouri",
|
||||
"workerpool"
|
||||
"workerpool",
|
||||
"Colorbar",
|
||||
"Colorbars"
|
||||
]
|
||||
}
|
||||
@@ -26,6 +26,7 @@ const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
const QUICK_BUILD = process.env.QUICK_BUILD;
|
||||
const BUILD_NUM = process.env.CIRCLE_BUILD_NUM || '0';
|
||||
const IS_COVERAGE = process.env.COVERAGE === 'true';
|
||||
|
||||
// read from ../version.txt
|
||||
const VERSION_NUMBER = fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || '';
|
||||
@@ -100,14 +101,30 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||
...(isProdBuild
|
||||
? []
|
||||
: [
|
||||
{
|
||||
test: /\.[jt]sx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: isProdBuild ? [] : ['react-refresh/babel'],
|
||||
},
|
||||
},
|
||||
...(IS_COVERAGE
|
||||
? [
|
||||
{
|
||||
test: /\.[jt]sx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-typescript', '@babel/preset-react'],
|
||||
plugins: ['istanbul'],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
test: /\.[jt]sx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: isProdBuild ? [] : ['react-refresh/babel'],
|
||||
},
|
||||
},
|
||||
]),
|
||||
]),
|
||||
{
|
||||
test: /\.svg?$/,
|
||||
|
||||
+248
@@ -3,6 +3,254 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add locale files for Russian language, translated texts in locale json files to Russian language. ([#4984](https://github.com/OHIF/Viewers/issues/4984)) ([c829270](https://github.com/OHIF/Viewers/commit/c829270c80c0899132aff0294b8e7386475dc71c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** add Arabic translations ([#5013](https://github.com/OHIF/Viewers/issues/5013)) ([8218a9d](https://github.com/OHIF/Viewers/commit/8218a9d0346fe9a5473420e26a0b8ab389f4ef58))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **data overlay:** add foreground background control plus orientation change in the UI and add RTDOSE support ([#5007](https://github.com/OHIF/Viewers/issues/5007)) ([cd4e640](https://github.com/OHIF/Viewers/commit/cd4e6408bc2964e5ee27427cdc328751eaf4d088))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve menu modals customizations ([#5011](https://github.com/OHIF/Viewers/issues/5011)) ([7dbe5e3](https://github.com/OHIF/Viewers/commit/7dbe5e37b06056fdd5262a131417013d112c3d30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **referencelines:** and bring back the notification for worker updates but disallow duplication ([#5005](https://github.com/OHIF/Viewers/issues/5005)) ([98f8187](https://github.com/OHIF/Viewers/commit/98f8187dfeb8aec6eca145da75c855aec0da4e1d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **template:** Update mode template to match current implementation (OHI-1833) ([#4998](https://github.com/OHIF/Viewers/issues/4998)) ([0f39496](https://github.com/OHIF/Viewers/commit/0f394962904808a140221dbae063ca5dcf0df36b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docs:** Fix SSR build errors by dynamically importing UI components and resolving other issues ([#4991](https://github.com/OHIF/Viewers/issues/4991)) ([09ac43f](https://github.com/OHIF/Viewers/commit/09ac43f297fa81101d3a3eaa9b5cfef8e01ea214))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Improve unsupported displayset messages ([#4979](https://github.com/OHIF/Viewers/issues/4979)) ([3b8faa8](https://github.com/OHIF/Viewers/commit/3b8faa8a76dde5088815431a947fec07db4a3c7f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dicomWebClient:** use public getter for activeDataSourceName instead of accessing private property ([#4973](https://github.com/OHIF/Viewers/issues/4973)) ([5688a2b](https://github.com/OHIF/Viewers/commit/5688a2b4b8f872163ff6492bb80d12cade4385cd))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ExtensionManager:** getDataSources method ([#4959](https://github.com/OHIF/Viewers/issues/4959)) ([66a16cd](https://github.com/OHIF/Viewers/commit/66a16cd22f6cedc6e4287dd1304e1f87de4c0d08))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* github runner ([#4961](https://github.com/OHIF/Viewers/issues/4961)) ([9535422](https://github.com/OHIF/Viewers/commit/953542236116aea6f4dce6275b8cdbd4490f4520))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* making onnxruntime work with rsbuild ([#4957](https://github.com/OHIF/Viewers/issues/4957)) ([4a87302](https://github.com/OHIF/Viewers/commit/4a87302f06378649680370f10dd942d31c1c61fb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add back study browser scrollbar ([#4942](https://github.com/OHIF/Viewers/issues/4942)) ([ed94df0](https://github.com/OHIF/Viewers/commit/ed94df0dfe007748e51de959d9dd4101485d8df4))
|
||||
* enable measurements panel scroll ([#4943](https://github.com/OHIF/Viewers/issues/4943)) ([7a36eba](https://github.com/OHIF/Viewers/commit/7a36eba67c6365f3f8cabb53a31ae1eee96069e8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* expose scroll thumbnail into view ([#4941](https://github.com/OHIF/Viewers/issues/4941)) ([4926a95](https://github.com/OHIF/Viewers/commit/4926a9532c89011ef932fad1dbae643806e780a8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make datasource selection work ([#4944](https://github.com/OHIF/Viewers/issues/4944)) ([a9b180c](https://github.com/OHIF/Viewers/commit/a9b180c5d427ec5a6823991cf0c6604be7af1cc2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
+200
@@ -3,6 +3,206 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@externals/devDependencies",
|
||||
"description": "External dev dependencies - put dev build dependencies here",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"engines": {
|
||||
|
||||
@@ -3,6 +3,206 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@externals/dicom-microscopy-viewer",
|
||||
"description": "External reference to dicom-microscopy-viewer",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dicom-microscopy-viewer": "^0.46.1"
|
||||
|
||||
@@ -24,6 +24,7 @@ module.exports = {
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: [
|
||||
'babel-plugin-istanbul',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-transform-regenerator',
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
ce0d152f92328998eb4f8994278663c720ee5dc0
|
||||
c829270c80c0899132aff0294b8e7386475dc71c
|
||||
@@ -3,6 +3,209 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **data overlay:** add foreground background control plus orientation change in the UI and add RTDOSE support ([#5007](https://github.com/OHIF/Viewers/issues/5007)) ([cd4e640](https://github.com/OHIF/Viewers/commit/cd4e6408bc2964e5ee27427cdc328751eaf4d088))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-pmap",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"description": "DICOM Parametric Map read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -33,10 +33,10 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.11.0-beta.1",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.1",
|
||||
"@ohif/extension-default": "3.11.0-beta.1",
|
||||
"@ohif/i18n": "3.11.0-beta.1",
|
||||
"@ohif/core": "3.11.0-beta.26",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.26",
|
||||
"@ohif/extension-default": "3.11.0-beta.26",
|
||||
"@ohif/i18n": "3.11.0-beta.26",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.9.5",
|
||||
"@cornerstonejs/core": "^3.9.5",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { utils } from '@ohif/core';
|
||||
import { utils, Types as OhifTypes } from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { metaData, cache, utilities as csUtils, volumeLoader } from '@cornerstonejs/core';
|
||||
import { adaptersPMAP } from '@cornerstonejs/adapters';
|
||||
import { SOPClassHandlerId } from './id';
|
||||
import { dicomLoaderService } from '@ohif/extension-cornerstone';
|
||||
|
||||
const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
|
||||
import { dicomLoaderService, VOLUME_LOADER_SCHEME } from '@ohif/extension-cornerstone';
|
||||
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.30'];
|
||||
|
||||
function _getDisplaySetsFromSeries(
|
||||
@@ -57,7 +56,8 @@ function _getDisplaySetsFromSeries(
|
||||
wadoRoot,
|
||||
wadoUriRoot,
|
||||
wadoUri,
|
||||
isOverlayDisplaySet: true,
|
||||
supportsWindowLevel: true,
|
||||
label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('PMAP')}`,
|
||||
};
|
||||
|
||||
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||
@@ -227,7 +227,8 @@ async function _loadParametricMap({ displaySet, headers }: withAppTypes) {
|
||||
return derivedVolume;
|
||||
}
|
||||
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
|
||||
const { servicesManager, extensionManager } = params;
|
||||
const getDisplaySetsFromSeries = instances => {
|
||||
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,209 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **data overlay:** add foreground background control plus orientation change in the UI and add RTDOSE support ([#5007](https://github.com/OHIF/Viewers/issues/5007)) ([cd4e640](https://github.com/OHIF/Viewers/commit/cd4e6408bc2964e5ee27427cdc328751eaf4d088))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"description": "DICOM RT read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -33,10 +33,10 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.11.0-beta.1",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.1",
|
||||
"@ohif/extension-default": "3.11.0-beta.1",
|
||||
"@ohif/i18n": "3.11.0-beta.1",
|
||||
"@ohif/core": "3.11.0-beta.26",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.26",
|
||||
"@ohif/extension-default": "3.11.0-beta.26",
|
||||
"@ohif/i18n": "3.11.0-beta.26",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
@@ -27,7 +27,7 @@ const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
|
||||
// it will be in the correct position zoom and pan
|
||||
commandsManager.runCommand('updateStoredPositionPresentation', {
|
||||
viewportId,
|
||||
displaySetInstanceUID: referencedDisplaySet.displaySetInstanceUID,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { utils } from '@ohif/core';
|
||||
import { utils, Types as OhifTypes } from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
|
||||
import { SOPClassHandlerId } from './id';
|
||||
import loadRTStruct from './loadRTStruct';
|
||||
@@ -53,6 +54,7 @@ function _getDisplaySetsFromSeries(
|
||||
wadoUriRoot,
|
||||
wadoUri,
|
||||
isOverlayDisplaySet: true,
|
||||
label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('RTSTRUCT')}`,
|
||||
};
|
||||
|
||||
let referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||
@@ -94,12 +96,19 @@ function _getDisplaySetsFromSeries(
|
||||
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||
}
|
||||
|
||||
displaySet.load = ({ headers }) => _load(displaySet, servicesManager, extensionManager, headers);
|
||||
displaySet.load = ({ headers, createSegmentation = true }) =>
|
||||
_load(displaySet, servicesManager, extensionManager, headers, createSegmentation);
|
||||
|
||||
return [displaySet];
|
||||
}
|
||||
|
||||
function _load(rtDisplaySet, servicesManager: AppTypes.ServicesManager, extensionManager, headers) {
|
||||
function _load(
|
||||
rtDisplaySet,
|
||||
servicesManager: AppTypes.ServicesManager,
|
||||
extensionManager,
|
||||
headers,
|
||||
createSegmentation = true
|
||||
) {
|
||||
const { SOPInstanceUID } = rtDisplaySet;
|
||||
const { segmentationService } = servicesManager.services;
|
||||
if (
|
||||
@@ -121,16 +130,21 @@ function _load(rtDisplaySet, servicesManager: AppTypes.ServicesManager, extensio
|
||||
rtDisplaySet.structureSet = structureSet;
|
||||
}
|
||||
|
||||
segmentationService
|
||||
.createSegmentationForRTDisplaySet(rtDisplaySet)
|
||||
.then(() => {
|
||||
rtDisplaySet.loading = false;
|
||||
resolve();
|
||||
})
|
||||
.catch(error => {
|
||||
rtDisplaySet.loading = false;
|
||||
reject(error);
|
||||
});
|
||||
if (createSegmentation) {
|
||||
segmentationService
|
||||
.createSegmentationForRTDisplaySet(rtDisplaySet)
|
||||
.then(() => {
|
||||
rtDisplaySet.loading = false;
|
||||
resolve();
|
||||
})
|
||||
.catch(error => {
|
||||
rtDisplaySet.loading = false;
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
rtDisplaySet.loading = false;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
return loadPromises[SOPInstanceUID];
|
||||
@@ -171,20 +185,14 @@ function _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence(
|
||||
return ReferencedSeriesSequence;
|
||||
}
|
||||
|
||||
function _segmentationExistsInCache(
|
||||
rtDisplaySet,
|
||||
segmentationService: AppTypes.SegmentationService
|
||||
) {
|
||||
function _segmentationExistsInCache() {
|
||||
// Todo: fix this
|
||||
return false;
|
||||
// This should be abstracted with the CornerstoneCacheService
|
||||
const rtContourId = rtDisplaySet.displaySetInstanceUID;
|
||||
const contour = segmentationService.getContour(rtContourId);
|
||||
|
||||
return contour !== undefined;
|
||||
}
|
||||
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
|
||||
const { servicesManager, extensionManager } = params;
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'dicom-rt',
|
||||
|
||||
@@ -323,7 +323,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
onStatusClick,
|
||||
}),
|
||||
indexPriority: -100,
|
||||
location: viewportActionCornersService.LOCATIONS.topLeft,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
{
|
||||
viewportId,
|
||||
|
||||
@@ -3,6 +3,209 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **data overlay:** add foreground background control plus orientation change in the UI and add RTDOSE support ([#5007](https://github.com/OHIF/Viewers/issues/5007)) ([cd4e640](https://github.com/OHIF/Viewers/commit/cd4e6408bc2964e5ee27427cdc328751eaf4d088))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"description": "DICOM SEG read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -33,10 +33,10 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.11.0-beta.1",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.1",
|
||||
"@ohif/extension-default": "3.11.0-beta.1",
|
||||
"@ohif/i18n": "3.11.0-beta.1",
|
||||
"@ohif/core": "3.11.0-beta.26",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.26",
|
||||
"@ohif/extension-default": "3.11.0-beta.26",
|
||||
"@ohif/i18n": "3.11.0-beta.26",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.9.5",
|
||||
"@cornerstonejs/core": "^3.9.5",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { utils } from '@ohif/core';
|
||||
import { metaData, triggerEvent, eventTarget } from '@cornerstonejs/core';
|
||||
import { utils, Types as OhifTypes } from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { metaData, eventTarget } from '@cornerstonejs/core';
|
||||
import { CONSTANTS, segmentation as cstSegmentation } from '@cornerstonejs/tools';
|
||||
import { adaptersSEG, Enums } from '@cornerstonejs/adapters';
|
||||
|
||||
@@ -57,6 +58,7 @@ function _getDisplaySetsFromSeries(
|
||||
wadoUriRoot,
|
||||
wadoUri,
|
||||
isOverlayDisplaySet: true,
|
||||
label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('SEG')}`,
|
||||
};
|
||||
|
||||
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||
@@ -232,7 +234,8 @@ function _segmentationExists(segDisplaySet) {
|
||||
return cstSegmentation.state.getSegmentation(segDisplaySet.displaySetInstanceUID);
|
||||
}
|
||||
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
|
||||
const { servicesManager, extensionManager } = params;
|
||||
const getDisplaySetsFromSeries = instances => {
|
||||
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||
};
|
||||
|
||||
@@ -157,7 +157,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
// it will be in the correct position zoom and pan
|
||||
commandsManager.runCommand('updateStoredPositionPresentation', {
|
||||
viewportId,
|
||||
displaySetInstanceUID: referencedDisplaySet.displaySetInstanceUID,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
|
||||
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
@@ -304,7 +304,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
onStatusClick,
|
||||
}),
|
||||
indexPriority: -100,
|
||||
location: viewportActionCornersService.LOCATIONS.topLeft,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
{
|
||||
viewportId,
|
||||
|
||||
@@ -3,6 +3,209 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **data overlay:** add foreground background control plus orientation change in the UI and add RTDOSE support ([#5007](https://github.com/OHIF/Viewers/issues/5007)) ([cd4e640](https://github.com/OHIF/Viewers/commit/cd4e6408bc2964e5ee27427cdc328751eaf4d088))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-sr",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"description": "OHIF extension for an SR Cornerstone Viewport",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -34,10 +34,10 @@
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.11.0-beta.1",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.1",
|
||||
"@ohif/extension-measurement-tracking": "3.11.0-beta.1",
|
||||
"@ohif/ui": "3.11.0-beta.1",
|
||||
"@ohif/core": "3.11.0-beta.26",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.26",
|
||||
"@ohif/extension-measurement-tracking": "3.11.0-beta.26",
|
||||
"@ohif/ui": "3.11.0-beta.26",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.9.5",
|
||||
"@cornerstonejs/core": "^3.9.5",
|
||||
"@cornerstonejs/tools": "^3.9.5",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -273,7 +273,7 @@ function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
||||
servicesManager,
|
||||
}),
|
||||
indexPriority: -100,
|
||||
location: viewportActionCornersService.LOCATIONS.topLeft,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
{
|
||||
viewportId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { utils, classes, DisplaySetService, Types } from '@ohif/core';
|
||||
import { utils, classes, DisplaySetService, Types as OhifTypes } from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
|
||||
@@ -122,6 +123,7 @@ function _getDisplaySetsFromSeries(
|
||||
sopClassUids,
|
||||
instance,
|
||||
addInstances,
|
||||
label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('SR')}`,
|
||||
};
|
||||
|
||||
displaySet.load = () => _load(displaySet, servicesManager, extensionManager);
|
||||
@@ -354,7 +356,8 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
|
||||
* @param {Object} options.extensionManager - The extension manager.
|
||||
* @returns {Array} An array containing the SOP class handler module.
|
||||
*/
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
function getSopClassHandlerModule(params: OhifTypes.Extensions.ExtensionParams) {
|
||||
const { servicesManager, extensionManager } = params;
|
||||
const getDisplaySetsFromSeries = instances => {
|
||||
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,209 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **data overlay:** add foreground background control plus orientation change in the UI and add RTDOSE support ([#5007](https://github.com/OHIF/Viewers/issues/5007)) ([cd4e640](https://github.com/OHIF/Viewers/commit/cd4e6408bc2964e5ee27427cdc328751eaf4d088))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dynamic-volume",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"description": "OHIF extension for 4D volumes data",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,11 +29,11 @@
|
||||
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.11.0-beta.1",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.1",
|
||||
"@ohif/extension-default": "3.11.0-beta.1",
|
||||
"@ohif/i18n": "3.11.0-beta.1",
|
||||
"@ohif/ui": "3.11.0-beta.1",
|
||||
"@ohif/core": "3.11.0-beta.26",
|
||||
"@ohif/extension-cornerstone": "3.11.0-beta.26",
|
||||
"@ohif/extension-default": "3.11.0-beta.26",
|
||||
"@ohif/i18n": "3.11.0-beta.26",
|
||||
"@ohif/ui": "3.11.0-beta.26",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -42,8 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^3.9.5",
|
||||
"@cornerstonejs/tools": "^3.9.5",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,212 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **data overlay:** add foreground background control plus orientation change in the UI and add RTDOSE support ([#5007](https://github.com/OHIF/Viewers/issues/5007)) ([cd4e640](https://github.com/OHIF/Viewers/commit/cd4e6408bc2964e5ee27427cdc328751eaf4d088))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **referencelines:** and bring back the notification for worker updates but disallow duplication ([#5005](https://github.com/OHIF/Viewers/issues/5005)) ([98f8187](https://github.com/OHIF/Viewers/commit/98f8187dfeb8aec6eca145da75c855aec0da4e1d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -38,9 +38,9 @@
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.9.5",
|
||||
"@ohif/core": "3.11.0-beta.1",
|
||||
"@ohif/ui": "3.11.0-beta.1",
|
||||
"@cornerstonejs/dicom-image-loader": "^3.11.6",
|
||||
"@ohif/core": "3.11.0-beta.26",
|
||||
"@ohif/ui": "3.11.0-beta.26",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -51,12 +51,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^3.9.5",
|
||||
"@cornerstonejs/ai": "^3.9.5",
|
||||
"@cornerstonejs/core": "^3.9.5",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.9.5",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.9.5",
|
||||
"@cornerstonejs/tools": "^3.9.5",
|
||||
"@cornerstonejs/adapters": "^3.11.6",
|
||||
"@cornerstonejs/ai": "^3.11.6",
|
||||
"@cornerstonejs/core": "^3.11.6",
|
||||
"@cornerstonejs/labelmap-interpolation": "^3.11.6",
|
||||
"@cornerstonejs/polymorphic-segmentation": "^3.11.6",
|
||||
"@cornerstonejs/tools": "^3.11.6",
|
||||
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
|
||||
"@kitware/vtk.js": "32.12.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React, { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
import * as cs3DTools from '@cornerstonejs/tools';
|
||||
import { Enums, eventTarget, getEnabledElement } from '@cornerstonejs/core';
|
||||
import { MeasurementService } from '@ohif/core';
|
||||
import { AllInOneMenu } from '@ohif/ui-next';
|
||||
import { useViewportDialog } from '@ohif/ui-next';
|
||||
import type { Types as csTypes } from '@cornerstonejs/core';
|
||||
|
||||
@@ -15,12 +13,11 @@ import CinePlayer from '../components/CinePlayer';
|
||||
import type { Types } from '@ohif/core';
|
||||
|
||||
import OHIFViewportActionCorners from '../components/OHIFViewportActionCorners';
|
||||
import { getWindowLevelActionMenu } from '../components/WindowLevelActionMenu/getWindowLevelActionMenu';
|
||||
import { getViewportDataOverlaySettingsMenu } from '../components/ViewportDataOverlaySettingMenu';
|
||||
import { getViewportPresentations } from '../utils/presentations/getViewportPresentations';
|
||||
import { useSynchronizersStore } from '../stores/useSynchronizersStore';
|
||||
import ActiveViewportBehavior from '../utils/ActiveViewportBehavior';
|
||||
import { WITH_NAVIGATION } from '../services/ViewportService/CornerstoneViewportService';
|
||||
import { useViewportActionCorners } from '../hooks';
|
||||
|
||||
const STACK = 'stack';
|
||||
|
||||
@@ -110,7 +107,23 @@ const OHIFCornerstoneViewport = React.memo(
|
||||
cornerstoneViewportService.resize();
|
||||
setImageScrollBarHeight();
|
||||
}
|
||||
}, [elementRef]);
|
||||
}, [elementRef, cornerstoneViewportService, setImageScrollBarHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = elementRef.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver(onResize);
|
||||
resizeObserver.observe(element);
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
resizeObserver.unobserve(element);
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [onResize]);
|
||||
|
||||
const cleanUpServices = useCallback(
|
||||
viewportInfo => {
|
||||
@@ -306,52 +319,14 @@ const OHIFCornerstoneViewport = React.memo(
|
||||
};
|
||||
}, [displaySets, elementRef, viewportId, isJumpToMeasurementDisabled, servicesManager]);
|
||||
|
||||
// Set up the window level action menu in the viewport action corners.
|
||||
useEffect(() => {
|
||||
const windowLevelActionMenu = customizationService.getCustomization(
|
||||
'viewportActionMenu.windowLevelActionMenu'
|
||||
);
|
||||
const segmentationOverlay = customizationService.getCustomization(
|
||||
'viewportActionMenu.segmentationOverlay'
|
||||
);
|
||||
|
||||
if (windowLevelActionMenu?.enabled) {
|
||||
viewportActionCornersService.addComponent({
|
||||
viewportId,
|
||||
id: 'windowLevelActionMenu',
|
||||
component: getWindowLevelActionMenu({
|
||||
viewportId,
|
||||
element: elementRef.current,
|
||||
displaySets,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
location: windowLevelActionMenu.location,
|
||||
verticalDirection: AllInOneMenu.VerticalDirection.TopToBottom,
|
||||
horizontalDirection: AllInOneMenu.HorizontalDirection.RightToLeft,
|
||||
}),
|
||||
location: windowLevelActionMenu.location,
|
||||
});
|
||||
}
|
||||
|
||||
if (segmentationOverlay?.enabled) {
|
||||
viewportActionCornersService.addComponent({
|
||||
viewportId,
|
||||
id: 'segmentation',
|
||||
component: getViewportDataOverlaySettingsMenu({
|
||||
viewportId,
|
||||
element: elementRef.current,
|
||||
displaySets,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
location: segmentationOverlay.location,
|
||||
}),
|
||||
location: segmentationOverlay.location,
|
||||
});
|
||||
}
|
||||
}, [displaySets, viewportId, viewportActionCornersService, servicesManager, commandsManager]);
|
||||
|
||||
const { ref: resizeRef } = useResizeDetector({
|
||||
onResize,
|
||||
// Set up the window level action menu in the viewport action corners using the custom hook
|
||||
useViewportActionCorners({
|
||||
viewportId,
|
||||
elementRef,
|
||||
displaySets,
|
||||
viewportActionCornersService,
|
||||
customizationService,
|
||||
commandsManager,
|
||||
});
|
||||
|
||||
const Notification = customizationService.getCustomization('ui.notificationComponent');
|
||||
@@ -364,10 +339,7 @@ const OHIFCornerstoneViewport = React.memo(
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
onContextMenu={e => e.preventDefault()}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
ref={el => {
|
||||
resizeRef.current = el;
|
||||
elementRef.current = el;
|
||||
}}
|
||||
ref={elementRef}
|
||||
></div>
|
||||
<CornerstoneOverlays
|
||||
viewportId={viewportId}
|
||||
@@ -516,22 +488,18 @@ OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';
|
||||
|
||||
function areEqual(prevProps, nextProps) {
|
||||
if (nextProps.needsRerendering) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: needsRerendering');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prevProps.displaySets.length !== nextProps.displaySets.length) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: displaySets length change');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prevProps.viewportOptions.orientation !== nextProps.viewportOptions.orientation) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: orientation change');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prevProps.viewportOptions.toolGroupId !== nextProps.viewportOptions.toolGroupId) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: toolGroupId change');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -539,12 +507,10 @@ function areEqual(prevProps, nextProps) {
|
||||
nextProps.viewportOptions.viewportType &&
|
||||
prevProps.viewportOptions.viewportType !== nextProps.viewportOptions.viewportType
|
||||
) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: viewportType change');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nextProps.viewportOptions.needsRerendering) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: viewportOptions.needsRerendering');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -552,7 +518,6 @@ function areEqual(prevProps, nextProps) {
|
||||
const nextDisplaySets = nextProps.displaySets;
|
||||
|
||||
if (prevDisplaySets.length !== nextDisplaySets.length) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: displaySets length mismatch');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -565,13 +530,11 @@ function areEqual(prevProps, nextProps) {
|
||||
);
|
||||
|
||||
if (!foundDisplaySet) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: displaySet not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// check they contain the same image
|
||||
if (foundDisplaySet.images?.length !== prevDisplaySet.images?.length) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: images length mismatch');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -579,7 +542,6 @@ function areEqual(prevProps, nextProps) {
|
||||
if (foundDisplaySet.images?.length) {
|
||||
for (let j = 0; j < foundDisplaySet.images.length; j++) {
|
||||
if (foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId) {
|
||||
console.debug('OHIFCornerstoneViewport: Rerender caused by: imageId mismatch');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ function CornerstoneImageScrollbar({
|
||||
}
|
||||
const { viewportType } = viewportData;
|
||||
const eventId =
|
||||
(viewportType === Enums.ViewportType.STACK && Enums.Events.STACK_VIEWPORT_SCROLL) ||
|
||||
(viewportType === Enums.ViewportType.STACK && Enums.Events.STACK_NEW_IMAGE) ||
|
||||
(viewportType === Enums.ViewportType.ORTHOGRAPHIC && Enums.Events.VOLUME_NEW_IMAGE) ||
|
||||
Enums.Events.IMAGE_RENDERED;
|
||||
|
||||
@@ -72,11 +72,11 @@ function CornerstoneImageScrollbar({
|
||||
if (!viewport || viewport instanceof VolumeViewport3D) {
|
||||
return;
|
||||
}
|
||||
const { imageIndex, newImageIdIndex = imageIndex } = event.detail;
|
||||
const { imageIndex, newImageIdIndex = imageIndex, imageIdIndex } = event.detail;
|
||||
const numberOfSlices = viewport.getNumberOfSlices();
|
||||
// find the index of imageId in the imageIds
|
||||
setImageSliceData({
|
||||
imageIndex: newImageIdIndex,
|
||||
imageIndex: newImageIdIndex ?? imageIdIndex,
|
||||
numberOfSlices,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
StackViewport,
|
||||
VolumeViewport,
|
||||
utilities as csUtils,
|
||||
Enums as CoreEnums,
|
||||
Types as CoreTypes,
|
||||
BaseVolumeViewport,
|
||||
} from '@cornerstonejs/core';
|
||||
@@ -248,7 +249,7 @@ function commandsModule({
|
||||
},
|
||||
updateStoredPositionPresentation: ({
|
||||
viewportId,
|
||||
displaySetInstanceUID,
|
||||
displaySetInstanceUIDs,
|
||||
referencedImageId,
|
||||
options,
|
||||
}) => {
|
||||
@@ -256,11 +257,24 @@ function commandsModule({
|
||||
const { positionPresentationStore, setPositionPresentation, getPositionPresentationId } =
|
||||
usePositionPresentationStore.getState();
|
||||
|
||||
// Look inside positionPresentationStore and find the key that includes the displaySetInstanceUID
|
||||
// Look inside positionPresentationStore and find the key that includes ALL the displaySetInstanceUIDs
|
||||
// and the value has viewportId as activeViewportId.
|
||||
const previousReferencedDisplaySetStoreKey = Object.entries(positionPresentationStore).find(
|
||||
([key, value]) => key.includes(displaySetInstanceUID) && value.viewportId === viewportId
|
||||
)?.[0];
|
||||
let previousReferencedDisplaySetStoreKey;
|
||||
|
||||
if (
|
||||
displaySetInstanceUIDs &&
|
||||
Array.isArray(displaySetInstanceUIDs) &&
|
||||
displaySetInstanceUIDs.length > 0
|
||||
) {
|
||||
previousReferencedDisplaySetStoreKey = Object.entries(positionPresentationStore).find(
|
||||
([key, value]) => {
|
||||
return (
|
||||
displaySetInstanceUIDs.every(uid => key.includes(uid)) &&
|
||||
value.viewportId === viewportId
|
||||
);
|
||||
}
|
||||
)?.[0];
|
||||
}
|
||||
|
||||
// Create presentation data with referencedImageId and options if provided
|
||||
const presentationData = referencedImageId
|
||||
@@ -282,7 +296,7 @@ function commandsModule({
|
||||
// so we need to grab the positionPresentationId directly from the store,
|
||||
// Todo: this is really hacky, we should have a better way for this
|
||||
const positionPresentationId = getPositionPresentationId({
|
||||
displaySetInstanceUIDs: [displaySetInstanceUID],
|
||||
displaySetInstanceUIDs,
|
||||
viewportId,
|
||||
});
|
||||
|
||||
@@ -719,7 +733,7 @@ function commandsModule({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!toolGroup.hasTool(toolName)) {
|
||||
if (!toolGroup?.hasTool(toolName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1019,7 +1033,7 @@ function commandsModule({
|
||||
|
||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||
|
||||
if (!toolGroup.hasTool(toolName)) {
|
||||
if (!toolGroup?.hasTool(toolName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1189,6 +1203,9 @@ function commandsModule({
|
||||
},
|
||||
/**
|
||||
* Creates a labelmap for the active viewport
|
||||
*
|
||||
* The created labelmap will be registered as a display set and also added
|
||||
* as a segmentation representation to the viewport.
|
||||
*/
|
||||
createLabelmapForViewport: async ({ viewportId, options = {} }) => {
|
||||
const { viewportGridService, displaySetService, segmentationService } =
|
||||
@@ -1209,6 +1226,7 @@ function commandsModule({
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
// This will create the segmentation and register it as a display set
|
||||
const generatedSegmentationId = await segmentationService.createLabelmapForDisplaySet(
|
||||
displaySet,
|
||||
{
|
||||
@@ -1225,6 +1243,7 @@ function commandsModule({
|
||||
}
|
||||
);
|
||||
|
||||
// Also add the segmentation representation to the viewport
|
||||
await segmentationService.addSegmentationRepresentation(viewportId, {
|
||||
segmentationId,
|
||||
type: Enums.SegmentationRepresentations.Labelmap,
|
||||
@@ -1741,6 +1760,30 @@ function commandsModule({
|
||||
});
|
||||
});
|
||||
},
|
||||
setViewportOrientation: ({ viewportId, orientation }) => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport || viewport.type !== CoreEnums.ViewportType.ORTHOGRAPHIC) {
|
||||
console.warn('Orientation can only be set on volume viewports');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get display sets for this viewport to verify at least one is reconstructable
|
||||
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
const displaySets = displaySetUIDs.map(uid => displaySetService.getDisplaySetByUID(uid));
|
||||
|
||||
if (!displaySets.some(ds => ds.isReconstructable)) {
|
||||
console.warn('Cannot change orientation: No reconstructable display sets in viewport');
|
||||
return;
|
||||
}
|
||||
|
||||
viewport.setOrientation(orientation);
|
||||
viewport.render();
|
||||
|
||||
// update the orientation in the viewport info
|
||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||
viewportInfo.setOrientation(orientation);
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -2021,6 +2064,7 @@ function commandsModule({
|
||||
decreaseBrushSize: actions.decreaseBrushSize,
|
||||
addNewSegment: actions.addNewSegment,
|
||||
loadSegmentationDisplaySetsForViewport: actions.loadSegmentationDisplaySetsForViewport,
|
||||
setViewportOrientation: actions.setViewportOrientation,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useViewportActionCornersContext } from '../contextProviders/ViewportActionCornersProvider';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
|
||||
export type OHIFViewportActionCornersProps = {
|
||||
viewportId: string;
|
||||
@@ -9,8 +10,13 @@ export type OHIFViewportActionCornersProps = {
|
||||
function OHIFViewportActionCorners({ viewportId }: OHIFViewportActionCornersProps) {
|
||||
const { servicesManager } = useSystem();
|
||||
const [viewportActionCornersState] = useViewportActionCornersContext();
|
||||
|
||||
const [viewportGrid] = useViewportGrid();
|
||||
const isActiveViewport = viewportGrid.activeViewportId === viewportId;
|
||||
|
||||
const ViewportActionCorners =
|
||||
servicesManager.services.customizationService.getCustomization('ui.viewportActionCorner');
|
||||
|
||||
if (!viewportActionCornersState[viewportId]) {
|
||||
return null;
|
||||
}
|
||||
@@ -18,7 +24,8 @@ function OHIFViewportActionCorners({ viewportId }: OHIFViewportActionCornersProp
|
||||
return (
|
||||
<ViewportActionCorners
|
||||
cornerComponents={viewportActionCornersState[viewportId]}
|
||||
></ViewportActionCorners>
|
||||
isActiveViewport={isActiveViewport}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+478
@@ -0,0 +1,478 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Icons,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
Switch,
|
||||
} from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
import { useViewportDisplaySets } from '../../hooks/useViewportDisplaySets';
|
||||
|
||||
function ViewportDataOverlayMenu({ viewportId }: withAppTypes<{ viewportId: string }>) {
|
||||
const { commandsManager, servicesManager } = useSystem();
|
||||
const [pendingForegrounds, setPendingForegrounds] = useState<string[]>([]);
|
||||
const [pendingSegmentations, setPendingSegmentations] = useState<string[]>([]);
|
||||
const [thresholdOpacityEnabled, setThresholdOpacityEnabled] = useState(false);
|
||||
|
||||
const { hangingProtocolService } = servicesManager.services;
|
||||
|
||||
const {
|
||||
backgroundDisplaySet,
|
||||
potentialOverlayDisplaySets,
|
||||
potentialForegroundDisplaySets,
|
||||
potentialBackgroundDisplaySets,
|
||||
overlayDisplaySets,
|
||||
foregroundDisplaySets,
|
||||
} = useViewportDisplaySets(viewportId);
|
||||
|
||||
/**
|
||||
* Change the background display set
|
||||
*/
|
||||
const handleBackgroundSelection = (newBackgroundDisplaySet: AppTypes.DisplaySet) => {
|
||||
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
viewportId,
|
||||
newBackgroundDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
commandsManager.run('setDisplaySetsForViewports', {
|
||||
viewportsToUpdate: updatedViewports,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle threshold and opacity toggle
|
||||
*/
|
||||
const handleThresholdOpacityToggle = (checked: boolean) => {
|
||||
setThresholdOpacityEnabled(checked);
|
||||
|
||||
if (foregroundDisplaySets.length > 0) {
|
||||
// Example implementation of threshold/opacity adjustment
|
||||
commandsManager.runCommand('setForegroundThresholdOpacity', {
|
||||
viewportId,
|
||||
enabled: checked,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Replace a display set layer with a new one
|
||||
*/
|
||||
const handleReplaceDisplaySetLayer = (
|
||||
currentDisplaySetInstanceUID: string,
|
||||
newDisplaySetInstanceUID: string
|
||||
) => {
|
||||
// Remove current display set
|
||||
commandsManager.runCommand('removeDisplaySetLayer', {
|
||||
viewportId,
|
||||
displaySetInstanceUID: currentDisplaySetInstanceUID,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
commandsManager.runCommand('addDisplaySetAsLayer', {
|
||||
viewportId,
|
||||
displaySetInstanceUID: newDisplaySetInstanceUID,
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a display set layer
|
||||
*/
|
||||
const handleRemoveDisplaySetLayer = (displaySetInstanceUID: string) => {
|
||||
commandsManager.runCommand('removeDisplaySetLayer', {
|
||||
viewportId,
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a display set as a layer
|
||||
*/
|
||||
const handleAddDisplaySetAsLayer = (displaySetInstanceUID: string) => {
|
||||
commandsManager.runCommand('addDisplaySetAsLayer', {
|
||||
viewportId,
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle overlay display set selection change
|
||||
*/
|
||||
const handleOverlaySelectionChange = (
|
||||
currentDisplaySet: AppTypes.DisplaySet,
|
||||
newDisplaySetInstanceUID: string
|
||||
) => {
|
||||
if (newDisplaySetInstanceUID === currentDisplaySet.displaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the selected display set
|
||||
const selectedDisplaySet = potentialOverlayDisplaySets.find(
|
||||
ds => ds.displaySetInstanceUID === newDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
if (selectedDisplaySet) {
|
||||
handleReplaceDisplaySetLayer(
|
||||
currentDisplaySet.displaySetInstanceUID,
|
||||
selectedDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle foreground display set selection change
|
||||
*/
|
||||
const handleForegroundSelectionChange = (
|
||||
currentDisplaySet: AppTypes.DisplaySet,
|
||||
newDisplaySetInstanceUID: string
|
||||
) => {
|
||||
if (newDisplaySetInstanceUID === currentDisplaySet.displaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the selected display set
|
||||
const selectedDisplaySet = potentialForegroundDisplaySets.find(
|
||||
ds => ds.displaySetInstanceUID === newDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
if (selectedDisplaySet) {
|
||||
handleReplaceDisplaySetLayer(
|
||||
currentDisplaySet.displaySetInstanceUID,
|
||||
selectedDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle pending segmentation selection
|
||||
*/
|
||||
const handlePendingSegmentationSelection = (pendingId: string, displaySetInstanceUID: string) => {
|
||||
const selectedDisplaySet = potentialOverlayDisplaySets.find(
|
||||
ds => ds.displaySetInstanceUID === displaySetInstanceUID
|
||||
);
|
||||
|
||||
if (selectedDisplaySet) {
|
||||
handleAddDisplaySetAsLayer(selectedDisplaySet.displaySetInstanceUID);
|
||||
// Remove this pending segmentation from the list
|
||||
setPendingSegmentations(pendingSegmentations.filter(id => id !== pendingId));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle pending foreground selection
|
||||
*/
|
||||
const handlePendingForegroundSelection = (pendingId: string, displaySetInstanceUID: string) => {
|
||||
const selectedDisplaySet = potentialForegroundDisplaySets.find(
|
||||
ds => ds.displaySetInstanceUID === displaySetInstanceUID
|
||||
);
|
||||
|
||||
if (selectedDisplaySet) {
|
||||
handleAddDisplaySetAsLayer(selectedDisplaySet.displaySetInstanceUID);
|
||||
// Remove this pending foreground from the list
|
||||
setPendingForegrounds(pendingForegrounds.filter(id => id !== pendingId));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-popover flex h-full w-[275px] flex-col rounded rounded-md p-1.5">
|
||||
{/* Top buttons row */}
|
||||
<div className={`flex`}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-primary flex items-center p-1"
|
||||
onClick={() => {
|
||||
// Add a new pending foreground slot with a unique ID
|
||||
setPendingForegrounds([...pendingForegrounds, `pending-${Date.now()}`]);
|
||||
}}
|
||||
disabled={potentialForegroundDisplaySets.length === 0}
|
||||
>
|
||||
<Icons.Plus className="h-4 w-4" />
|
||||
Foreground
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-primary ml-2 flex items-center"
|
||||
disabled={potentialOverlayDisplaySets.length === 0}
|
||||
onClick={() => {
|
||||
setPendingSegmentations([...pendingSegmentations, `seg-${Date.now()}`]);
|
||||
}}
|
||||
>
|
||||
<Icons.Plus className="h-4 w-4" />
|
||||
Segmentation
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
{/* Overlays Segmentation section */}
|
||||
<div className="my-2 ml-1">
|
||||
{overlayDisplaySets.map((displaySet, index) => (
|
||||
<div
|
||||
key={displaySet.displaySetInstanceUID}
|
||||
className="mb-1 flex items-center"
|
||||
>
|
||||
<Icons.LayerSegmentation className="text-muted-foreground mr-1 h-6 w-6 flex-shrink-0" />
|
||||
<Select
|
||||
value={displaySet.displaySetInstanceUID}
|
||||
onValueChange={value => handleOverlaySelectionChange(displaySet, value)}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue>{displaySet.label?.toUpperCase()}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{/* Include both potential overlays and the current overlay */}
|
||||
<SelectItem
|
||||
key={displaySet.displaySetInstanceUID}
|
||||
value={displaySet.displaySetInstanceUID}
|
||||
>
|
||||
{displaySet.label}
|
||||
</SelectItem>
|
||||
{potentialOverlayDisplaySets.map(item => (
|
||||
<SelectItem
|
||||
key={item.displaySetInstanceUID}
|
||||
value={item.displaySetInstanceUID}
|
||||
>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-2 flex-shrink-0"
|
||||
>
|
||||
<Icons.More className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleRemoveDisplaySetLayer(displaySet.displaySetInstanceUID)}
|
||||
>
|
||||
Remove
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{pendingSegmentations.map(pendingId => (
|
||||
<div
|
||||
key={pendingId}
|
||||
className="mb-2 flex items-center"
|
||||
>
|
||||
<Icons.LayerSegmentation className="text-muted-foreground mr-1 h-6 w-6 flex-shrink-0" />
|
||||
<Select
|
||||
value=""
|
||||
onValueChange={value => handlePendingSegmentationSelection(pendingId, value)}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue placeholder="SELECT A SEGMENTATION" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{potentialOverlayDisplaySets.map(item => (
|
||||
<SelectItem
|
||||
key={item.displaySetInstanceUID}
|
||||
value={item.displaySetInstanceUID}
|
||||
>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-2 flex-shrink-0"
|
||||
>
|
||||
<Icons.More className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
// Remove this pending segmentation
|
||||
setPendingSegmentations(pendingSegmentations.filter(id => id !== pendingId));
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Foregrounds section */}
|
||||
<div className="my-2 px-1">
|
||||
{foregroundDisplaySets.map((displaySet, index) => (
|
||||
<div
|
||||
key={displaySet.displaySetInstanceUID}
|
||||
className="mb-1 flex items-center"
|
||||
>
|
||||
<Icons.LayerForeground className="text-muted-foreground mr-1 h-6 w-6 flex-shrink-0" />
|
||||
<Select
|
||||
value={displaySet.displaySetInstanceUID}
|
||||
onValueChange={value => handleForegroundSelectionChange(displaySet, value)}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue>{displaySet.label?.toUpperCase()}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{/* Include both potential foregrounds and the current foreground */}
|
||||
<SelectItem
|
||||
key={displaySet.displaySetInstanceUID}
|
||||
value={displaySet.displaySetInstanceUID}
|
||||
>
|
||||
{displaySet.label}
|
||||
</SelectItem>
|
||||
{potentialForegroundDisplaySets.map(item => (
|
||||
<SelectItem
|
||||
key={item.displaySetInstanceUID}
|
||||
value={item.displaySetInstanceUID}
|
||||
>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-2 flex-shrink-0"
|
||||
>
|
||||
<Icons.More className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleRemoveDisplaySetLayer(displaySet.displaySetInstanceUID)}
|
||||
>
|
||||
Remove
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{pendingForegrounds.map(pendingId => (
|
||||
<div
|
||||
key={pendingId}
|
||||
className="mb-2 flex items-center"
|
||||
>
|
||||
<Icons.LayerForeground className="text-muted-foreground mr-1 h-6 w-6 flex-shrink-0" />
|
||||
<Select
|
||||
value=""
|
||||
onValueChange={value => handlePendingForegroundSelection(pendingId, value)}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue placeholder="SELECT A FOREGROUND" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{potentialForegroundDisplaySets.map(item => (
|
||||
<SelectItem
|
||||
key={item.displaySetInstanceUID}
|
||||
value={item.displaySetInstanceUID}
|
||||
>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-2 flex-shrink-0"
|
||||
>
|
||||
<Icons.More className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
// Remove this pending foreground
|
||||
setPendingForegrounds(pendingForegrounds.filter(id => id !== pendingId));
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* Background section */}
|
||||
<div className="mt-1 mb-1 flex items-center px-1">
|
||||
<Icons.LayerBackground className="text-muted-foreground mr-1 h-6 w-6 flex-shrink-0" />
|
||||
<Select
|
||||
value={backgroundDisplaySet.displaySetInstanceUID}
|
||||
onValueChange={value => {
|
||||
const selectedDisplaySet = potentialBackgroundDisplaySets.find(
|
||||
ds => ds.displaySetInstanceUID === value
|
||||
);
|
||||
if (selectedDisplaySet) {
|
||||
handleBackgroundSelection(selectedDisplaySet);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue>
|
||||
{(
|
||||
backgroundDisplaySet.SeriesDescription ||
|
||||
backgroundDisplaySet.label ||
|
||||
'background'
|
||||
).toUpperCase()}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{potentialBackgroundDisplaySets.map(displaySet => (
|
||||
<SelectItem
|
||||
key={displaySet.displaySetInstanceUID}
|
||||
value={displaySet.displaySetInstanceUID}
|
||||
>
|
||||
{displaySet.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
{/* {foregroundDisplaySets.length > 0 && (
|
||||
<div className="mt-1 ml-7">
|
||||
<div className="flex items-center">
|
||||
<Switch
|
||||
id="threshold-opacity-switch"
|
||||
className="mr-2"
|
||||
checked={thresholdOpacityEnabled}
|
||||
onCheckedChange={handleThresholdOpacityToggle}
|
||||
/>
|
||||
<label
|
||||
htmlFor="threshold-opacity-switch"
|
||||
className="text-muted-foreground cursor-pointer text-sm"
|
||||
onClick={() => setThresholdOpacityEnabled(!thresholdOpacityEnabled)}
|
||||
>
|
||||
Control threshold & opacity
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewportDataOverlayMenu;
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Icons,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
useViewportGrid,
|
||||
} from '@ohif/ui-next';
|
||||
import ViewportDataOverlayMenu from './ViewportDataOverlayMenu';
|
||||
import classNames from 'classnames';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export function ViewportDataOverlayMenuWrapper({
|
||||
viewportId,
|
||||
displaySets,
|
||||
location,
|
||||
}: withAppTypes<{
|
||||
viewportId: string;
|
||||
element: HTMLElement;
|
||||
}>): ReactNode {
|
||||
const { servicesManager } = useSystem();
|
||||
const { viewportActionCornersService } = servicesManager.services;
|
||||
const [viewportGrid] = useViewportGrid();
|
||||
|
||||
const { align, side } = viewportActionCornersService.getAlignAndSide(location);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
asChild
|
||||
className="flex items-center justify-center"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn(
|
||||
viewportGrid.activeViewportId === viewportId
|
||||
? 'visible'
|
||||
: 'invisible group-hover/pane:visible'
|
||||
)}
|
||||
>
|
||||
<Icons.ViewportViews className={classNames('text-highlight')} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="border-none bg-transparent p-0 shadow-none"
|
||||
side={side}
|
||||
align={align}
|
||||
alignOffset={0}
|
||||
sideOffset={5}
|
||||
>
|
||||
<ViewportDataOverlayMenu
|
||||
className="w-full"
|
||||
viewportId={viewportId}
|
||||
displaySets={displaySets}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
-136
@@ -1,136 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Icons, Separator } from '@ohif/ui-next';
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
|
||||
function ViewportSegmentationMenu({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
}: withAppTypes<{ viewportId: string }>) {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const [activeSegmentations, setActiveSegmentations] = useState([]);
|
||||
const [availableSegmentations, setAvailableSegmentations] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const updateSegmentations = () => {
|
||||
const active = segmentationService.getSegmentationRepresentations(viewportId);
|
||||
setActiveSegmentations(active);
|
||||
|
||||
const all = segmentationService.getSegmentations();
|
||||
const available = all.filter(
|
||||
seg => !active.some(activeSeg => activeSeg.segmentationId === seg.segmentationId)
|
||||
);
|
||||
setAvailableSegmentations(available);
|
||||
};
|
||||
|
||||
updateSegmentations();
|
||||
|
||||
const subscriptions = [
|
||||
segmentationService.EVENTS.SEGMENTATION_MODIFIED,
|
||||
segmentationService.EVENTS.SEGMENTATION_REMOVED,
|
||||
segmentationService.EVENTS.SEGMENTATION_REPRESENTATION_MODIFIED,
|
||||
].map(event => segmentationService.subscribe(event, updateSegmentations));
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
};
|
||||
}, [segmentationService, viewportId]);
|
||||
|
||||
const toggleSegmentationRepresentationVisibility = (
|
||||
segmentationId,
|
||||
type = SegmentationRepresentations.Labelmap
|
||||
) => {
|
||||
segmentationService.toggleSegmentationRepresentationVisibility(viewportId, {
|
||||
segmentationId,
|
||||
type,
|
||||
});
|
||||
};
|
||||
|
||||
const addSegmentationToViewport = segmentationId => {
|
||||
segmentationService.addSegmentationRepresentation(viewportId, { segmentationId });
|
||||
};
|
||||
|
||||
const removeSegmentationFromViewport = segmentationId => {
|
||||
segmentationService.removeSegmentationRepresentations(viewportId, {
|
||||
segmentationId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-muted flex h-full w-[262px] flex-col rounded p-3">
|
||||
<span className="text-muted-foreground mb-2 text-xs font-semibold">Current Viewport</span>
|
||||
<ul className="space-y-1">
|
||||
{activeSegmentations.map(segmentation => (
|
||||
<li
|
||||
key={segmentation.id}
|
||||
className="flex items-center text-sm"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground mr-2"
|
||||
onClick={() => removeSegmentationFromViewport(segmentation.segmentationId)}
|
||||
>
|
||||
<Icons.Minus className="h-6 w-6" />
|
||||
</Button>
|
||||
<span className="text-foreground flex-grow">{segmentation.label}</span>
|
||||
{segmentation.visible ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground"
|
||||
onClick={() =>
|
||||
toggleSegmentationRepresentationVisibility(
|
||||
segmentation.segmentationId,
|
||||
segmentation.type
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icons.Hide className="h-6 w-6" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground"
|
||||
onClick={() =>
|
||||
toggleSegmentationRepresentationVisibility(
|
||||
segmentation.segmentationId,
|
||||
segmentation.type
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icons.Show className="h-6 w-6" />
|
||||
</Button>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{availableSegmentations.length > 0 && (
|
||||
<>
|
||||
<Separator className="bg-input mb-3" />
|
||||
<span className="text-muted-foreground mb-2 text-xs font-semibold">Available</span>
|
||||
<ul className="space-y-1">
|
||||
{availableSegmentations.map(({ segmentationId, label }) => (
|
||||
<li
|
||||
key={segmentationId}
|
||||
className="flex items-center text-sm"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground mr-2"
|
||||
onClick={() => addSegmentationToViewport(segmentationId)}
|
||||
>
|
||||
<Icons.Plus className="h-6 w-6" />
|
||||
</Button>
|
||||
<span className="text-foreground/60">{label}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewportSegmentationMenu;
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import { Button, Icons, Popover, PopoverContent, PopoverTrigger } from '@ohif/ui-next';
|
||||
import ViewportSegmentationMenu from './ViewportSegmentationMenu';
|
||||
import classNames from 'classnames';
|
||||
import { useSegmentations } from '../../hooks/useSegmentations';
|
||||
|
||||
export function ViewportSegmentationMenuWrapper({
|
||||
viewportId,
|
||||
displaySets,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
location,
|
||||
}: withAppTypes<{
|
||||
viewportId: string;
|
||||
element: HTMLElement;
|
||||
}>): ReactNode {
|
||||
const { viewportActionCornersService, viewportGridService } = servicesManager.services;
|
||||
|
||||
const segmentations = useSegmentations({ servicesManager });
|
||||
|
||||
const activeViewportId = viewportGridService.getActiveViewportId();
|
||||
const isActiveViewport = viewportId === activeViewportId;
|
||||
|
||||
const { align, side } = getAlignAndSide(viewportActionCornersService, location);
|
||||
|
||||
if (!segmentations?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
asChild
|
||||
className="flex items-center justify-center"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.ViewportViews
|
||||
className={classNames(
|
||||
'text-highlight',
|
||||
isActiveViewport ? 'visible' : 'invisible group-hover/pane:visible'
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="border-none bg-transparent p-0 shadow-none"
|
||||
side={side}
|
||||
align={align}
|
||||
alignOffset={-15}
|
||||
sideOffset={5}
|
||||
>
|
||||
<ViewportSegmentationMenu
|
||||
className="w-full"
|
||||
viewportId={viewportId}
|
||||
displaySets={displaySets}
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
const getAlignAndSide = (viewportActionCornersService, location) => {
|
||||
const ViewportActionCornersLocations = viewportActionCornersService.LOCATIONS;
|
||||
|
||||
switch (location) {
|
||||
case ViewportActionCornersLocations.topLeft:
|
||||
return { align: 'start', side: 'bottom' };
|
||||
case ViewportActionCornersLocations.topRight:
|
||||
return { align: 'end', side: 'bottom' };
|
||||
case ViewportActionCornersLocations.bottomLeft:
|
||||
return { align: 'start', side: 'top' };
|
||||
case ViewportActionCornersLocations.bottomRight:
|
||||
return { align: 'end', side: 'top' };
|
||||
default:
|
||||
console.debug('Unknown location, defaulting to bottom-start');
|
||||
return { align: 'start', side: 'bottom' };
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { ViewportSegmentationMenuWrapper } from './ViewportSegmentationMenuWrapper';
|
||||
import { ViewportDataOverlayMenuWrapper } from './ViewportDataOverlayMenuWrapper';
|
||||
|
||||
export function getViewportDataOverlaySettingsMenu(
|
||||
props: withAppTypes<{
|
||||
@@ -7,5 +7,5 @@ export function getViewportDataOverlaySettingsMenu(
|
||||
element: HTMLElement;
|
||||
}>
|
||||
): ReactNode {
|
||||
return <ViewportSegmentationMenuWrapper {...props} />;
|
||||
return <ViewportDataOverlayMenuWrapper {...props} />;
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { utilities as csUtils } from '@cornerstonejs/core';
|
||||
|
||||
export const DEFAULT_COLORMAP = 'hsv';
|
||||
export const DEFAULT_OPACITY = 0.5;
|
||||
export const DEFAULT_OPACITY_PERCENT = DEFAULT_OPACITY * 100;
|
||||
export const DERIVED_OVERLAY_MODALITIES = ['SEG', 'RTSTRUCT'];
|
||||
|
||||
/**
|
||||
* Get modality-specific color and opacity settings from the customization service
|
||||
*/
|
||||
export function getModalityOverlayColormap(customizationService, modality) {
|
||||
const modalityOverlayDefaultColorMaps = customizationService?.getCustomization(
|
||||
'cornerstone.modalityOverlayDefaultColorMaps'
|
||||
) || { defaultSettings: {} };
|
||||
|
||||
return (
|
||||
modalityOverlayDefaultColorMaps.defaultSettings[modality] || {
|
||||
colormap: DEFAULT_COLORMAP,
|
||||
opacity: DEFAULT_OPACITY,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies display sets that can be used as overlays for a specific viewport.
|
||||
*
|
||||
* "Enhanced" display sets are those that:
|
||||
* 1. Are not already in the viewport
|
||||
* 2. Are evaluated for their ability to be overlaid onto the background display set
|
||||
* 3. Have an "isOverlayable" flag indicating if they're compatible with the viewport
|
||||
*
|
||||
* A display set is considered overlayable when:
|
||||
* - The background display set is reconstructable
|
||||
* - The display set is not unsupported
|
||||
* - The Frame of Reference matches the background display set
|
||||
* - For non-derived modalities: background can be a volume and display set is either multiframe or valid volume
|
||||
*
|
||||
* @returns {Object} Object containing:
|
||||
* - viewportDisplaySets: Display sets already in the viewport
|
||||
* - enhancedDisplaySets: Other display sets with overlayability assessment
|
||||
*/
|
||||
export function getEnhancedDisplaySets({ viewportId, services }) {
|
||||
const { displaySetService, viewportGridService } = services;
|
||||
const displaySetsUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
const allDisplaySets = displaySetService.getActiveDisplaySets();
|
||||
|
||||
const otherDisplaySets = allDisplaySets.filter(
|
||||
displaySet => !displaySetsUIDs.includes(displaySet.displaySetInstanceUID)
|
||||
);
|
||||
|
||||
const viewportDisplaySets = displaySetsUIDs.map(displaySetUID =>
|
||||
displaySetService.getDisplaySetByUID(displaySetUID)
|
||||
);
|
||||
|
||||
const backgroundCanBeVolume = csUtils.isValidVolume(viewportDisplaySets[0].imageIds);
|
||||
const backgroundDisplaySet = viewportDisplaySets[0];
|
||||
|
||||
const enhancedDisplaySets = otherDisplaySets.map(displaySet => {
|
||||
if (!backgroundDisplaySet.isReconstructable) {
|
||||
return {
|
||||
...displaySet,
|
||||
isOverlayable: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (displaySet.unsupported) {
|
||||
return {
|
||||
...displaySet,
|
||||
isOverlayable: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if Frame of Reference matches
|
||||
if (
|
||||
displaySet.FrameOfReferenceUID &&
|
||||
displaySet.FrameOfReferenceUID !== backgroundDisplaySet.FrameOfReferenceUID
|
||||
) {
|
||||
return {
|
||||
...displaySet,
|
||||
isOverlayable: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Special handling for derived modalities
|
||||
if (!DERIVED_OVERLAY_MODALITIES.includes(displaySet.Modality)) {
|
||||
if (!backgroundCanBeVolume) {
|
||||
return {
|
||||
...displaySet,
|
||||
isOverlayable: false,
|
||||
};
|
||||
}
|
||||
|
||||
const imageIds = displaySet.imageIds || displaySet.images?.map(image => image.imageId);
|
||||
const isMultiframe = displaySet.isMultiFrame;
|
||||
|
||||
if (!isMultiframe && imageIds?.length > 0 && !csUtils.isValidVolume(imageIds)) {
|
||||
return {
|
||||
...displaySet,
|
||||
isOverlayable: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...displaySet,
|
||||
isOverlayable: true,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
viewportDisplaySets,
|
||||
enhancedDisplaySets,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort function: puts disabled items (isOverlayable: false) at the end
|
||||
*/
|
||||
export const sortByOverlayable = (a, b) => {
|
||||
if (a.isOverlayable === b.isOverlayable) {
|
||||
return 0;
|
||||
}
|
||||
return a.isOverlayable ? -1 : 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create display set options based on modality and opacity settings
|
||||
*/
|
||||
export function createColormapOverlayDisplaySetOptions(displaySet, opacity, customizationService) {
|
||||
if (displaySet.Modality === 'SEG') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const modalitySettings = getModalityOverlayColormap(customizationService, displaySet.Modality);
|
||||
return {
|
||||
colormap: {
|
||||
name: modalitySettings.colormap || DEFAULT_COLORMAP,
|
||||
opacity: opacity / 100, // Convert from percentage to 0-1 range
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get segmentations that can be added as overlays to the viewport
|
||||
*
|
||||
* Note: This function is deprecated as we now use display sets for segmentations
|
||||
*/
|
||||
export function getAvailableSegmentations(segmentationService) {
|
||||
const segmentations = segmentationService.getSegmentations() || [];
|
||||
return segmentations.map(segmentation => ({
|
||||
segmentationId: segmentation.segmentationId,
|
||||
label: segmentation.label || 'Segmentation',
|
||||
segments: segmentation.segments,
|
||||
frameOfReferenceUID: segmentation.frameOfReferenceUID,
|
||||
}));
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
import { Icons, useViewportGrid } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { Enums } from '@cornerstonejs/core';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
Button,
|
||||
} from '@ohif/ui-next';
|
||||
|
||||
function ViewportOrientationMenu({ location }: withAppTypes<{ location?: string }>) {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const [viewportGridState, viewportGridService] = useViewportGrid();
|
||||
const { cornerstoneViewportService, displaySetService } = servicesManager.services;
|
||||
|
||||
const viewportId = viewportGridState.activeViewportId;
|
||||
|
||||
const handleOrientationChange = (orientation: string) => {
|
||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||
const currentViewportType = viewportInfo?.getViewportType();
|
||||
|
||||
// Get the displaySets in this viewport
|
||||
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
const displaySets = displaySetUIDs
|
||||
.map(uid => displaySetService.getDisplaySetByUID(uid))
|
||||
.filter(Boolean);
|
||||
|
||||
if (!displaySets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if at least one displaySet is reconstructable
|
||||
const hasReconstructableDisplaySet = displaySets.some(ds => ds.isReconstructable);
|
||||
|
||||
if (!hasReconstructableDisplaySet) {
|
||||
console.warn('Cannot change orientation: No reconstructable display sets in viewport');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the orientation enum based on the selected orientation
|
||||
let orientationEnum;
|
||||
switch (orientation.toLowerCase()) {
|
||||
case 'axial':
|
||||
orientationEnum = Enums.OrientationAxis.AXIAL;
|
||||
break;
|
||||
case 'sagittal':
|
||||
orientationEnum = Enums.OrientationAxis.SAGITTAL;
|
||||
break;
|
||||
case 'coronal':
|
||||
orientationEnum = Enums.OrientationAxis.CORONAL;
|
||||
break;
|
||||
default:
|
||||
orientationEnum = Enums.OrientationAxis.ACQUISITION;
|
||||
}
|
||||
|
||||
// If viewport is not already a volume type, we need to convert it
|
||||
if (currentViewportType !== Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
// Configure the viewport to be a volume viewport with current display sets
|
||||
const updatedViewport = {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: displaySetUIDs,
|
||||
viewportOptions: {
|
||||
viewportType: Enums.ViewportType.ORTHOGRAPHIC,
|
||||
orientation: orientationEnum,
|
||||
},
|
||||
displaySetOptions: displaySetUIDs.map(() => ({})),
|
||||
};
|
||||
|
||||
// Update the viewport to be a volume viewport
|
||||
commandsManager.run('setDisplaySetsForViewports', {
|
||||
viewportsToUpdate: [updatedViewport],
|
||||
});
|
||||
} else {
|
||||
// Set the viewport orientation
|
||||
commandsManager.runCommand('setViewportOrientation', {
|
||||
viewportId,
|
||||
orientation: orientationEnum,
|
||||
});
|
||||
}
|
||||
};
|
||||
const { viewportActionCornersService } = servicesManager.services;
|
||||
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
const displaySets = displaySetUIDs
|
||||
.map(uid => displaySetService.getDisplaySetByUID(uid))
|
||||
.filter(Boolean);
|
||||
|
||||
if (!displaySets.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasReconstructableDisplaySet = displaySets.some(ds => ds.isReconstructable);
|
||||
|
||||
// Get proper alignment and side based on the location
|
||||
let align = 'center';
|
||||
let side = 'bottom';
|
||||
|
||||
if (location !== undefined) {
|
||||
const positioning = viewportActionCornersService.getAlignAndSide(location);
|
||||
align = positioning.align;
|
||||
side = positioning.side;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-highlight"
|
||||
disabled={!hasReconstructableDisplaySet}
|
||||
>
|
||||
<Icons.OrientationSwitch />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="min-w-[160px]"
|
||||
align={align}
|
||||
side={side}
|
||||
sideOffset={5}
|
||||
>
|
||||
<DropdownMenuLabel className="-ml-1">Orientation</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={() => handleOrientationChange('axial')}>Axial</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleOrientationChange('sagittal')}>
|
||||
Sagittal
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleOrientationChange('coronal')}>
|
||||
Coronal
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleOrientationChange('acquisition')}>
|
||||
Acquisition
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewportOrientationMenu;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
import { cn } from '@ohif/ui-next';
|
||||
import ViewportOrientationMenu from './ViewportOrientationMenu';
|
||||
|
||||
export function ViewportOrientationMenuWrapper({
|
||||
viewportId,
|
||||
location,
|
||||
}: withAppTypes<{
|
||||
viewportId: string;
|
||||
element: HTMLElement;
|
||||
location: string;
|
||||
}>): ReactNode {
|
||||
const [viewportGrid] = useViewportGrid();
|
||||
const isActiveViewport = viewportId === viewportGrid.activeViewportId;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex justify-end',
|
||||
isActiveViewport ? 'visible' : 'invisible group-hover/pane:visible'
|
||||
)}
|
||||
>
|
||||
<ViewportOrientationMenu location={location} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { ViewportOrientationMenuWrapper } from './ViewportOrientationMenuWrapper';
|
||||
|
||||
export function getViewportOrientationMenu(
|
||||
props: withAppTypes<{
|
||||
viewportId: string;
|
||||
element: HTMLElement;
|
||||
location: string;
|
||||
}>
|
||||
): ReactNode {
|
||||
return <ViewportOrientationMenuWrapper {...props} />;
|
||||
}
|
||||
|
||||
export * from './ViewportOrientationMenu';
|
||||
@@ -1,18 +1,18 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import { Switch } from '@ohif/ui-next';
|
||||
import { StackViewport, VolumeViewport } from '@cornerstonejs/core';
|
||||
import { StackViewport } from '@cornerstonejs/core';
|
||||
import { ColorbarProps } from '../../types/Colorbar';
|
||||
import { utilities } from '@cornerstonejs/core';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export function setViewportColorbar(
|
||||
viewportId,
|
||||
displaySets,
|
||||
commandsManager,
|
||||
servicesManager: AppTypes.ServicesManager,
|
||||
colorbarOptions
|
||||
) {
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
const { cornerstoneViewportService, viewportGridService } = servicesManager.services;
|
||||
const displaySetInstanceUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
|
||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||
const backgroundColor = viewportInfo.getViewportOptions().background;
|
||||
@@ -32,36 +32,18 @@ export function setViewportColorbar(
|
||||
};
|
||||
}
|
||||
|
||||
const displaySetInstanceUIDs = [];
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
displaySetInstanceUIDs.push(viewportId);
|
||||
}
|
||||
|
||||
if (viewport instanceof VolumeViewport) {
|
||||
displaySets.forEach(ds => {
|
||||
displaySetInstanceUIDs.push(ds.displaySetInstanceUID);
|
||||
});
|
||||
}
|
||||
|
||||
commandsManager.run({
|
||||
commandName: 'toggleViewportColorbar',
|
||||
commandOptions: {
|
||||
viewportId,
|
||||
options: colorbarOptions,
|
||||
displaySetInstanceUIDs,
|
||||
},
|
||||
context: 'CORNERSTONE',
|
||||
commandsManager.run('toggleViewportColorbar', {
|
||||
viewportId,
|
||||
options: colorbarOptions,
|
||||
displaySetInstanceUIDs,
|
||||
});
|
||||
}
|
||||
|
||||
export function Colorbar({
|
||||
viewportId,
|
||||
displaySets,
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
colorbarProperties,
|
||||
}: withAppTypes<ColorbarProps>): ReactElement {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { colorbarService } = servicesManager.services;
|
||||
const {
|
||||
width: colorbarWidth,
|
||||
@@ -73,7 +55,7 @@ export function Colorbar({
|
||||
const [showColorbar, setShowColorbar] = useState(colorbarService.hasColorbar(viewportId));
|
||||
|
||||
const onSetColorbar = useCallback(() => {
|
||||
setViewportColorbar(viewportId, displaySets, commandsManager, servicesManager, {
|
||||
setViewportColorbar(viewportId, commandsManager, servicesManager, {
|
||||
viewportId,
|
||||
colormaps,
|
||||
ticks: {
|
||||
|
||||
@@ -3,14 +3,10 @@ import { AllInOneMenu, Switch, Tabs, TabsList, TabsTrigger } from '@ohif/ui-next
|
||||
|
||||
import { StackViewport, Types } from '@cornerstonejs/core';
|
||||
import { ColormapProps } from '../../types/Colormap';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export function Colormap({
|
||||
colormaps,
|
||||
viewportId,
|
||||
displaySets,
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
}: ColormapProps): ReactElement {
|
||||
export function Colormap({ colormaps, viewportId, displaySets }: ColormapProps): ReactElement {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
|
||||
const [activeDisplaySet, setActiveDisplaySet] = useState(displaySets[0]);
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React, { ReactElement, useState, useEffect, useCallback } from 'react';
|
||||
import { VolumeLightingProps } from '../../types/ViewportPresets';
|
||||
import { Numeric } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export function VolumeLighting({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
viewportId,
|
||||
hasShade,
|
||||
}: VolumeLightingProps): ReactElement {
|
||||
export function VolumeLighting({ viewportId, hasShade }: VolumeLightingProps): ReactElement {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
const [lightingValues, setLightingValues] = useState({
|
||||
ambient: null,
|
||||
|
||||
+2
-14
@@ -5,43 +5,31 @@ import { VolumeRenderingQuality } from './VolumeRenderingQuality';
|
||||
import { VolumeShift } from './VolumeShift';
|
||||
import { VolumeLighting } from './VolumeLighting';
|
||||
import { VolumeShade } from './VolumeShade';
|
||||
|
||||
export function VolumeRenderingOptions({
|
||||
viewportId,
|
||||
commandsManager,
|
||||
volumeRenderingQualityRange,
|
||||
servicesManager,
|
||||
}: VolumeRenderingOptionsProps): ReactElement {
|
||||
const [hasShade, setShade] = useState(false);
|
||||
return (
|
||||
<AllInOneMenu.ItemPanel>
|
||||
<VolumeRenderingQuality
|
||||
viewportId={viewportId}
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
volumeRenderingQualityRange={volumeRenderingQualityRange}
|
||||
/>
|
||||
|
||||
<VolumeShift
|
||||
viewportId={viewportId}
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
<VolumeShift viewportId={viewportId} />
|
||||
<div className="mt-2 flex h-8 !h-[20px] w-full flex-shrink-0 items-center justify-start px-2 text-base">
|
||||
<div className="text-muted-foreground text-sm">Lighting</div>
|
||||
</div>
|
||||
<div className="bg-background mt-1 mb-1 h-px w-full"></div>
|
||||
<div className="hover:bg-accent flex h-8 w-full flex-shrink-0 items-center px-2 text-base hover:rounded">
|
||||
<VolumeShade
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
viewportId={viewportId}
|
||||
onClickShade={setShade}
|
||||
/>
|
||||
</div>
|
||||
<VolumeLighting
|
||||
viewportId={viewportId}
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
hasShade={hasShade}
|
||||
/>
|
||||
</AllInOneMenu.ItemPanel>
|
||||
|
||||
+2
-3
@@ -3,13 +3,13 @@ import { Icons } from '@ohif/ui-next';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { VolumeRenderingPresetsProps } from '../../types/ViewportPresets';
|
||||
import { VolumeRenderingPresetsContent } from './VolumeRenderingPresetsContent';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export function VolumeRenderingPresets({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
volumeRenderingPresets,
|
||||
}: VolumeRenderingPresetsProps): ReactElement {
|
||||
const { servicesManager } = useSystem();
|
||||
const { uiDialogService } = servicesManager.services;
|
||||
|
||||
const onClickPresets = () => {
|
||||
@@ -21,7 +21,6 @@ export function VolumeRenderingPresets({
|
||||
contentProps: {
|
||||
presets: volumeRenderingPresets,
|
||||
viewportId,
|
||||
commandsManager,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
+2
-2
@@ -1,13 +1,13 @@
|
||||
import React, { ReactElement, useCallback, useState, useEffect } from 'react';
|
||||
import { VolumeRenderingQualityProps } from '../../types/ViewportPresets';
|
||||
import { Numeric } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export function VolumeRenderingQuality({
|
||||
volumeRenderingQualityRange,
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
viewportId,
|
||||
}: VolumeRenderingQualityProps): ReactElement {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
const { min, max, step } = volumeRenderingQualityRange;
|
||||
const [quality, setQuality] = useState(null);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import { Switch } from '@ohif/ui-next';
|
||||
import { VolumeShadeProps } from '../../types/ViewportPresets';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export function VolumeShade({
|
||||
commandsManager,
|
||||
viewportId,
|
||||
servicesManager,
|
||||
onClickShade = bool => {},
|
||||
}: VolumeShadeProps): ReactElement {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
const [shade, setShade] = useState(true);
|
||||
const [key, setKey] = useState(0);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { VolumeShiftProps } from '../../types/ViewportPresets';
|
||||
import { Numeric } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export function VolumeShift({
|
||||
viewportId,
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
}: VolumeShiftProps): ReactElement {
|
||||
export function VolumeShift({ viewportId }: VolumeShiftProps): ReactElement {
|
||||
const { servicesManager, commandsManager } = useSystem();
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
const [minShift, setMinShift] = useState<number | null>(null);
|
||||
const [maxShift, setMaxShift] = useState<number | null>(null);
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import React, { ReactElement, useCallback } from 'react';
|
||||
import { AllInOneMenu } from '@ohif/ui-next';
|
||||
import { WindowLevelPreset } from '../../types/WindowLevel';
|
||||
import { CommandsManager } from '@ohif/core';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type WindowLevelProps = {
|
||||
viewportId: string;
|
||||
presets: Array<Record<string, Array<WindowLevelPreset>>>;
|
||||
commandsManager: CommandsManager;
|
||||
};
|
||||
|
||||
export function WindowLevel({
|
||||
viewportId,
|
||||
commandsManager,
|
||||
presets,
|
||||
}: WindowLevelProps): ReactElement {
|
||||
export function WindowLevel({ viewportId, presets }: WindowLevelProps): ReactElement {
|
||||
const { commandsManager } = useSystem();
|
||||
const { t } = useTranslation('WindowLevelActionMenu');
|
||||
|
||||
const onSetWindowLevel = useCallback(
|
||||
|
||||
+6
-17
@@ -15,8 +15,7 @@ import { VolumeRenderingOptions } from './VolumeRenderingOptions';
|
||||
import { ViewportPreset } from '../../types/ViewportPresets';
|
||||
import { VolumeViewport3D } from '@cornerstonejs/core';
|
||||
import { utilities } from '@cornerstonejs/core';
|
||||
|
||||
export const nonWLModalities = ['SR', 'SEG', 'SM', 'RTSTRUCT', 'RTPLAN', 'RTDOSE'];
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
export type WindowLevelActionMenuProps = {
|
||||
viewportId: string;
|
||||
@@ -34,13 +33,12 @@ export function WindowLevelActionMenu({
|
||||
presets,
|
||||
verticalDirection,
|
||||
horizontalDirection,
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
colorbarProperties,
|
||||
displaySets,
|
||||
volumeRenderingPresets,
|
||||
volumeRenderingQualityRange,
|
||||
}: withAppTypes<WindowLevelActionMenuProps>): ReactElement {
|
||||
const { commandsManager, servicesManager } = useSystem();
|
||||
const {
|
||||
colormaps,
|
||||
colorbarContainerPosition,
|
||||
@@ -48,6 +46,7 @@ export function WindowLevelActionMenu({
|
||||
colorbarTickPosition,
|
||||
width: colorbarWidth,
|
||||
} = colorbarProperties;
|
||||
|
||||
const { colorbarService, cornerstoneViewportService } = servicesManager.services;
|
||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
@@ -64,7 +63,7 @@ export function WindowLevelActionMenu({
|
||||
const [is3DVolume, setIs3DVolume] = useState(false);
|
||||
|
||||
const onSetColorbar = useCallback(() => {
|
||||
setViewportColorbar(viewportId, displaySets, commandsManager, servicesManager, {
|
||||
setViewportColorbar(viewportId, commandsManager, servicesManager, {
|
||||
colormaps,
|
||||
ticks: {
|
||||
position: colorbarTickPosition,
|
||||
@@ -117,7 +116,6 @@ export function WindowLevelActionMenu({
|
||||
verticalDirection={verticalDirection}
|
||||
horizontalDirection={horizontalDirection}
|
||||
iconClassName={classNames(
|
||||
// Visible on hover and for the active viewport
|
||||
activeViewportId === viewportId ? 'visible' : 'invisible group-hover/pane:visible',
|
||||
'flex shrink-0 cursor-pointer rounded active:text-foreground text-highlight',
|
||||
isLight ? ' hover:bg-primary/30' : 'hover:bg-primary/30'
|
||||
@@ -132,9 +130,7 @@ export function WindowLevelActionMenu({
|
||||
{!is3DVolume && (
|
||||
<Colorbar
|
||||
viewportId={viewportId}
|
||||
displaySets={displaySets.filter(ds => !nonWLModalities.includes(ds.Modality))}
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
displaySets={displaySets.filter(ds => ds.supportsWindowLevel === true)}
|
||||
colorbarProperties={colorbarProperties}
|
||||
/>
|
||||
)}
|
||||
@@ -150,9 +146,7 @@ export function WindowLevelActionMenu({
|
||||
className="flex h-full w-full flex-col"
|
||||
colormaps={colormaps}
|
||||
viewportId={viewportId}
|
||||
displaySets={displaySets.filter(ds => !nonWLModalities.includes(ds.Modality))}
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
displaySets={displaySets.filter(ds => ds.supportsWindowLevel === true)}
|
||||
/>
|
||||
</AllInOneMenu.SubMenu>
|
||||
)}
|
||||
@@ -165,7 +159,6 @@ export function WindowLevelActionMenu({
|
||||
>
|
||||
<WindowLevel
|
||||
viewportId={viewportId}
|
||||
commandsManager={commandsManager}
|
||||
presets={presets}
|
||||
/>
|
||||
</AllInOneMenu.SubMenu>
|
||||
@@ -173,9 +166,7 @@ export function WindowLevelActionMenu({
|
||||
|
||||
{volumeRenderingPresets && is3DVolume && (
|
||||
<VolumeRenderingPresets
|
||||
servicesManager={servicesManager}
|
||||
viewportId={viewportId}
|
||||
commandsManager={commandsManager}
|
||||
volumeRenderingPresets={volumeRenderingPresets}
|
||||
/>
|
||||
)}
|
||||
@@ -184,9 +175,7 @@ export function WindowLevelActionMenu({
|
||||
<AllInOneMenu.SubMenu itemLabel="Rendering Options">
|
||||
<VolumeRenderingOptions
|
||||
viewportId={viewportId}
|
||||
commandsManager={commandsManager}
|
||||
volumeRenderingQualityRange={volumeRenderingQualityRange}
|
||||
servicesManager={servicesManager}
|
||||
/>
|
||||
</AllInOneMenu.SubMenu>
|
||||
)}
|
||||
|
||||
+10
-14
@@ -1,12 +1,12 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { nonWLModalities } from './WindowLevelActionMenu';
|
||||
import { WindowLevelActionMenuProps } from './WindowLevelActionMenu';
|
||||
import { useSystem } from '@ohif/core';
|
||||
import { WindowLevelActionMenu as WindowLevelActionMenuComponent } from './WindowLevelActionMenu';
|
||||
|
||||
export function getWindowLevelActionMenu({
|
||||
function WindowLevelActionMenu({
|
||||
viewportId,
|
||||
element,
|
||||
displaySets,
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
verticalDirection,
|
||||
horizontalDirection,
|
||||
}: withAppTypes<{
|
||||
@@ -14,31 +14,25 @@ export function getWindowLevelActionMenu({
|
||||
element: HTMLElement;
|
||||
displaySets: AppTypes.DisplaySet[];
|
||||
}>): ReactNode {
|
||||
const { servicesManager } = useSystem();
|
||||
const { customizationService } = servicesManager.services;
|
||||
|
||||
const presets = customizationService.getCustomization('cornerstone.windowLevelPresets');
|
||||
const colorbarProperties = customizationService.getCustomization('cornerstone.colorbar');
|
||||
const { volumeRenderingPresets, volumeRenderingQualityRange } =
|
||||
customizationService.getCustomization('cornerstone.3dVolumeRendering');
|
||||
const WindowLevelActionMenu = customizationService.getCustomization(
|
||||
'viewportActionMenu.windowLevelActionMenu'
|
||||
);
|
||||
const displaySetPresets = displaySets
|
||||
.filter(displaySet => presets[displaySet.Modality])
|
||||
.map(displaySet => {
|
||||
return { [displaySet.Modality]: presets[displaySet.Modality] };
|
||||
});
|
||||
|
||||
const modalities = displaySets
|
||||
.map(displaySet => displaySet.Modality)
|
||||
.filter(modality => !nonWLModalities.includes(modality));
|
||||
const modalities = displaySets.map(displaySet => displaySet.supportsWindowLevel);
|
||||
|
||||
if (modalities.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const WindowLevelActionMenuComponent = WindowLevelActionMenu?.component;
|
||||
|
||||
return (
|
||||
<WindowLevelActionMenuComponent
|
||||
viewportId={viewportId}
|
||||
@@ -46,8 +40,6 @@ export function getWindowLevelActionMenu({
|
||||
presets={displaySetPresets}
|
||||
verticalDirection={verticalDirection}
|
||||
horizontalDirection={horizontalDirection}
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
colorbarProperties={colorbarProperties}
|
||||
displaySets={displaySets}
|
||||
volumeRenderingPresets={volumeRenderingPresets}
|
||||
@@ -55,3 +47,7 @@ export function getWindowLevelActionMenu({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function getWindowLevelActionMenu(props: WindowLevelActionMenuProps) {
|
||||
return <WindowLevelActionMenu {...props} />;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Volume loader schemes
|
||||
export const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
|
||||
export const DYNAMIC_VOLUME_LOADER_SCHEME = 'cornerstoneStreamingDynamicImageVolume';
|
||||
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
'cornerstone.modalityOverlayDefaultColorMaps': {
|
||||
defaultSettings: {
|
||||
PT: {
|
||||
colormap: 'hsv',
|
||||
// Note: Right now, there is a nonlinear relationship between the opacity value
|
||||
// below and how it will get applied to the image. The limitation is in rendering.
|
||||
// We are working on this and will remove this note when it's fixed.
|
||||
// But don't expect 0.5 to be 50% opacity, but rather close to that.
|
||||
opacity: 0.5,
|
||||
},
|
||||
RTDOSE: {
|
||||
colormap: 'Isodose',
|
||||
// Note: Right now, there is a nonlinear relationship between the opacity value
|
||||
// below and how it will get applied to the image. The limitation is in rendering.
|
||||
// We are working on this and will remove this note when it's fixed.
|
||||
// But don't expect 0.5 to be 50% opacity, but rather close to that.
|
||||
|
||||
opacity: 0.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,14 +1,55 @@
|
||||
import viewportActionCornersService from '../services/ViewportActionCornersService/ViewportActionCornersService';
|
||||
import { WindowLevelActionMenu } from '../components/WindowLevelActionMenu/WindowLevelActionMenu';
|
||||
import { getWindowLevelActionMenu } from '../components/WindowLevelActionMenu/getWindowLevelActionMenu';
|
||||
import { getViewportDataOverlaySettingsMenu } from '../components/ViewportDataOverlaySettingMenu';
|
||||
import { getViewportOrientationMenu } from '../components/ViewportOrientationMenu';
|
||||
import { AllInOneMenu } from '@ohif/ui-next';
|
||||
|
||||
// Generate component renderer functions for each component type
|
||||
const createOrientationMenu = ({ viewportId, element, location }) => {
|
||||
return getViewportOrientationMenu({
|
||||
viewportId,
|
||||
element,
|
||||
location,
|
||||
});
|
||||
};
|
||||
|
||||
const createDataOverlay = ({ viewportId, element, displaySets, location }) => {
|
||||
return getViewportDataOverlaySettingsMenu({
|
||||
viewportId,
|
||||
element,
|
||||
displaySets,
|
||||
location,
|
||||
});
|
||||
};
|
||||
|
||||
const createWindowLevelMenu = ({ viewportId, element, displaySets }) => {
|
||||
return getWindowLevelActionMenu({
|
||||
viewportId,
|
||||
element,
|
||||
displaySets,
|
||||
verticalDirection: AllInOneMenu.VerticalDirection.TopToBottom,
|
||||
horizontalDirection: AllInOneMenu.HorizontalDirection.LeftToRight,
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
'viewportActionMenu.windowLevelActionMenu': {
|
||||
enabled: true,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
component: WindowLevelActionMenu,
|
||||
},
|
||||
'viewportActionMenu.segmentationOverlay': {
|
||||
enabled: true,
|
||||
location: viewportActionCornersService.LOCATIONS.topRight,
|
||||
},
|
||||
'viewportActionMenu.topLeft': [
|
||||
{
|
||||
id: 'orientationMenu',
|
||||
enabled: true,
|
||||
component: createOrientationMenu,
|
||||
},
|
||||
{
|
||||
id: 'dataOverlay',
|
||||
enabled: true,
|
||||
component: createDataOverlay,
|
||||
},
|
||||
{
|
||||
id: 'windowLevelActionMenu',
|
||||
enabled: true,
|
||||
component: createWindowLevelMenu,
|
||||
},
|
||||
],
|
||||
'viewportActionMenu.topRight': [],
|
||||
'viewportActionMenu.bottomLeft': [],
|
||||
'viewportActionMenu.bottomRight': [],
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import viewportClickCommandsCustomization from './customizations/viewportClickCo
|
||||
import measurementsCustomization from './customizations/measurementsCustomization';
|
||||
import volumeRenderingCustomization from './customizations/volumeRenderingCustomization';
|
||||
import colorbarCustomization from './customizations/colorbarCustomization';
|
||||
import modalityColorMapCustomization from './customizations/modalityColorMapCustomization';
|
||||
import windowLevelPresetsCustomization from './customizations/windowLevelPresetsCustomization';
|
||||
import miscCustomization from './customizations/miscCustomization';
|
||||
import captureViewportModalCustomization from './customizations/captureViewportModalCustomization';
|
||||
@@ -25,6 +26,7 @@ function getCustomizationModule({ commandsManager, servicesManager }) {
|
||||
...measurementsCustomization,
|
||||
...volumeRenderingCustomization,
|
||||
...colorbarCustomization,
|
||||
...modalityColorMapCustomization,
|
||||
...windowLevelPresetsCustomization,
|
||||
...miscCustomization,
|
||||
...captureViewportModalCustomization,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { utilities as csUtils, Enums as csEnums } from '@cornerstonejs/core';
|
||||
import dcmjs from 'dcmjs';
|
||||
import { dicomWebUtils } from '@ohif/extension-default';
|
||||
@@ -112,6 +113,7 @@ function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager)
|
||||
instances,
|
||||
othersFrameOfReferenceUID,
|
||||
imageIds: instances.map(instance => instance.imageId),
|
||||
label: SeriesDescription || `${i18n.t('Series')} ${SeriesNumber} - ${i18n.t('SM')}`,
|
||||
};
|
||||
// The microscopy viewer directly accesses the metadata already loaded, and
|
||||
// uses the DICOMweb client library directly for loading, so it has to be
|
||||
|
||||
@@ -233,7 +233,7 @@ function _evaluateToggle({
|
||||
}
|
||||
const toolName = toolbarService.getToolNameForButton(button);
|
||||
|
||||
if (!toolGroup.hasTool(toolName)) {
|
||||
if (!toolGroup?.hasTool(toolName)) {
|
||||
return getDisabledState(disabledText);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as useViewportActionCorners } from './useViewportActionCorners';
|
||||
export * from './useViewportActionCorners';
|
||||
export * from './useActiveViewportSegmentationRepresentations';
|
||||
export * from './useMeasurements';
|
||||
export * from './useSegmentations';
|
||||
@@ -84,7 +84,7 @@ function mapSegmentationToDisplay(segmentation, customizationService) {
|
||||
* @param options.debounceTime - Debounce time in milliseconds for updates.
|
||||
* @returns An array of segmentation data.
|
||||
*/
|
||||
export function useSegmentations(options: {
|
||||
export function useSegmentations(options?: {
|
||||
subscribeToDataModified?: boolean;
|
||||
debounceTime?: number;
|
||||
}): SegmentationData[] {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { ViewportActionCornersLocations } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core/src';
|
||||
|
||||
/**
|
||||
* Viewport action corner location type
|
||||
*/
|
||||
export interface ViewportActionCornerLocation {
|
||||
type: 'VIEWPORT';
|
||||
id: string;
|
||||
corner?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport action corner service interface
|
||||
*/
|
||||
export interface ViewportActionCornerService {
|
||||
addComponent: (params: {
|
||||
viewportId: string;
|
||||
id: string;
|
||||
component: React.ReactNode;
|
||||
location: ViewportActionCornersLocations | string;
|
||||
indexPriority?: number;
|
||||
}) => void;
|
||||
clear: (viewportId: string) => void;
|
||||
getAlignAndSide: (location: ViewportActionCornersLocations | string) => {
|
||||
align: 'start' | 'center' | 'end';
|
||||
side: 'top' | 'right' | 'bottom' | 'left';
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization service interface
|
||||
*/
|
||||
export interface CustomizationService {
|
||||
getCustomization: (name: string) => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the useViewportActionCorners hook
|
||||
*/
|
||||
interface UseViewportActionCornersProps {
|
||||
viewportId: string;
|
||||
elementRef: React.MutableRefObject<HTMLDivElement>;
|
||||
displaySets: AppTypes.DisplaySet[];
|
||||
}
|
||||
|
||||
// Map of customization keys to their corresponding enum values
|
||||
const locationMap = {
|
||||
'viewportActionMenu.topLeft': ViewportActionCornersLocations.topLeft,
|
||||
'viewportActionMenu.topRight': ViewportActionCornersLocations.topRight,
|
||||
'viewportActionMenu.bottomLeft': ViewportActionCornersLocations.bottomLeft,
|
||||
'viewportActionMenu.bottomRight': ViewportActionCornersLocations.bottomRight,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to manage viewport action corners for a Cornerstone viewport
|
||||
*/
|
||||
export function useViewportActionCorners({
|
||||
viewportId,
|
||||
elementRef,
|
||||
displaySets,
|
||||
}: UseViewportActionCornersProps): void {
|
||||
const { servicesManager } = useSystem();
|
||||
const { viewportActionCornersService, customizationService } = servicesManager.services;
|
||||
|
||||
useEffect(() => {
|
||||
// Process each location
|
||||
Object.entries(locationMap).forEach(([locationKey, locationValue]) => {
|
||||
const items = customizationService.getCustomization(locationKey);
|
||||
|
||||
if (!items || !items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach(item => {
|
||||
if (!item.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const componentId = item.id;
|
||||
|
||||
if (item.component) {
|
||||
// Use the component renderer provided directly in the item
|
||||
const component = item.component({
|
||||
viewportId,
|
||||
element: elementRef.current,
|
||||
displaySets,
|
||||
location: locationValue,
|
||||
});
|
||||
|
||||
viewportActionCornersService.addComponent({
|
||||
viewportId,
|
||||
id: componentId,
|
||||
component,
|
||||
location: locationValue,
|
||||
});
|
||||
} else if (item.component) {
|
||||
// Handle static components
|
||||
viewportActionCornersService.addComponent({
|
||||
viewportId,
|
||||
id: item.id,
|
||||
component: item.component,
|
||||
location: locationValue,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [displaySets, viewportId, viewportActionCornersService, elementRef]);
|
||||
}
|
||||
|
||||
export default useViewportActionCorners;
|
||||
@@ -0,0 +1,247 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSystem, utils } from '@ohif/core';
|
||||
import { useViewportGrid } from '@ohif/ui-next';
|
||||
import {
|
||||
getEnhancedDisplaySets,
|
||||
sortByOverlayable,
|
||||
DERIVED_OVERLAY_MODALITIES,
|
||||
} from '../components/ViewportDataOverlaySettingMenu/utils';
|
||||
|
||||
const sortByPriority = (a, b) => {
|
||||
if (utils.isLowPriorityModality(a.Modality)) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the useViewportDisplaySets hook
|
||||
*/
|
||||
export type UseViewportDisplaySetsOptions = {
|
||||
/**
|
||||
* Whether to include background display set
|
||||
*/
|
||||
includeBackground?: boolean;
|
||||
/**
|
||||
* Whether to include foreground display sets
|
||||
*/
|
||||
includeForeground?: boolean;
|
||||
/**
|
||||
* Whether to include overlay display sets
|
||||
*/
|
||||
includeOverlay?: boolean;
|
||||
/**
|
||||
* Whether to include potential overlay display sets
|
||||
*/
|
||||
includePotentialOverlay?: boolean;
|
||||
/**
|
||||
* Whether to include potential foreground display sets
|
||||
*/
|
||||
includePotentialForeground?: boolean;
|
||||
/**
|
||||
* Whether to include potential background display sets
|
||||
*/
|
||||
includePotentialBackground?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return type for useViewportDisplaySets
|
||||
*/
|
||||
export type ViewportDisplaySets = {
|
||||
/**
|
||||
* The primary display set for the viewport (base image)
|
||||
*/
|
||||
backgroundDisplaySet?: any;
|
||||
/**
|
||||
* Display sets currently shown with background (non-overlay layers)
|
||||
*/
|
||||
foregroundDisplaySets?: any[];
|
||||
/**
|
||||
* Segmentation display sets currently applied as overlays
|
||||
*/
|
||||
overlayDisplaySets?: any[];
|
||||
/**
|
||||
* Display sets that could be toggled on as overlays (derived modalities)
|
||||
*/
|
||||
potentialOverlayDisplaySets?: any[];
|
||||
/**
|
||||
* Display sets that could be added as foreground layers
|
||||
*/
|
||||
potentialForegroundDisplaySets?: any[];
|
||||
/**
|
||||
* Display sets that could replace the current background
|
||||
*/
|
||||
potentialBackgroundDisplaySets?: any[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to provide display sets and overlay information for a viewport based on options.
|
||||
*
|
||||
* @param viewportId - The viewport ID to get display sets for
|
||||
* @param options - Options to control which display sets to compute
|
||||
* @returns Object containing requested display set collections based on options
|
||||
*/
|
||||
export function useViewportDisplaySets(
|
||||
viewportId: string,
|
||||
options?: UseViewportDisplaySetsOptions
|
||||
): ViewportDisplaySets {
|
||||
const { servicesManager } = useSystem();
|
||||
const { displaySetService, segmentationService } = servicesManager.services;
|
||||
|
||||
// Note: this is very important we should use the useViewportGrid hook here,
|
||||
// since if the viewport displaySet is changed we should re-run this hook
|
||||
// to get the latest displaySets
|
||||
const [_, viewportGridService] = useViewportGrid();
|
||||
|
||||
// Apply defaults - include everything if no options specified
|
||||
const {
|
||||
includeBackground = true,
|
||||
includeForeground = true,
|
||||
includeOverlay = true,
|
||||
includePotentialOverlay = true,
|
||||
includePotentialForeground = true,
|
||||
includePotentialBackground = true,
|
||||
} = options || {};
|
||||
|
||||
// Get all available display sets (only if needed)
|
||||
const needsAllDisplaySets = includePotentialBackground;
|
||||
const allDisplaySets = useMemo(
|
||||
() => (needsAllDisplaySets ? displaySetService.getActiveDisplaySets() : []),
|
||||
[displaySetService, needsAllDisplaySets]
|
||||
);
|
||||
|
||||
// Get all available segmentations (only if needed)
|
||||
const needsSegmentations = includeOverlay;
|
||||
const segmentationRepresentations = useMemo(
|
||||
() =>
|
||||
needsSegmentations ? segmentationService.getSegmentationRepresentations(viewportId) : [],
|
||||
[segmentationService, viewportId, needsSegmentations]
|
||||
);
|
||||
|
||||
const overlayDisplaySets = useMemo(() => {
|
||||
if (!includeOverlay) {
|
||||
return [];
|
||||
}
|
||||
return segmentationRepresentations.map(repr => {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(repr.segmentationId);
|
||||
return displaySet;
|
||||
});
|
||||
}, [includeOverlay, segmentationRepresentations, displaySetService]);
|
||||
|
||||
const overlayDisplaySetUIDs = useMemo(
|
||||
() => overlayDisplaySets.map(ds => ds.displaySetInstanceUID),
|
||||
[overlayDisplaySets]
|
||||
);
|
||||
|
||||
// Get enhanced display sets (only if needed)
|
||||
const needsEnhancedDisplaySets =
|
||||
includeBackground || includeForeground || includePotentialOverlay || includePotentialForeground;
|
||||
|
||||
const { viewportDisplaySets, enhancedDisplaySets } = useMemo(() => {
|
||||
if (!needsEnhancedDisplaySets) {
|
||||
return { viewportDisplaySets: [], enhancedDisplaySets: [] };
|
||||
}
|
||||
return getEnhancedDisplaySets({
|
||||
viewportId,
|
||||
services: { displaySetService, viewportGridService },
|
||||
});
|
||||
}, [viewportId, displaySetService, viewportGridService, needsEnhancedDisplaySets]);
|
||||
|
||||
const backgroundDisplaySet = useMemo(
|
||||
() => (includeBackground ? viewportDisplaySets[0] : undefined),
|
||||
[includeBackground, viewportDisplaySets]
|
||||
);
|
||||
|
||||
const foregroundDisplaySets = useMemo(() => {
|
||||
if (!includeForeground || !backgroundDisplaySet) {
|
||||
return [];
|
||||
}
|
||||
return viewportDisplaySets.filter(
|
||||
ds =>
|
||||
!DERIVED_OVERLAY_MODALITIES.includes(ds.Modality) &&
|
||||
ds.displaySetInstanceUID !== backgroundDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
}, [includeForeground, viewportDisplaySets, backgroundDisplaySet]);
|
||||
|
||||
const foregroundDisplaySetUIDs = useMemo(
|
||||
() => foregroundDisplaySets.map(ds => ds.displaySetInstanceUID),
|
||||
[foregroundDisplaySets]
|
||||
);
|
||||
|
||||
const potentialOverlayDisplaySets = useMemo(() => {
|
||||
if (!includePotentialOverlay) {
|
||||
return [];
|
||||
}
|
||||
return enhancedDisplaySets
|
||||
.filter(
|
||||
ds =>
|
||||
DERIVED_OVERLAY_MODALITIES.includes(ds.Modality) &&
|
||||
!overlayDisplaySetUIDs.includes(ds.displaySetInstanceUID) &&
|
||||
ds.isOverlayable
|
||||
)
|
||||
.sort(sortByOverlayable);
|
||||
}, [includePotentialOverlay, enhancedDisplaySets, overlayDisplaySetUIDs]);
|
||||
|
||||
const potentialForegroundDisplaySets = useMemo(() => {
|
||||
if (!includePotentialForeground) {
|
||||
return [];
|
||||
}
|
||||
return enhancedDisplaySets
|
||||
.filter(
|
||||
ds =>
|
||||
!DERIVED_OVERLAY_MODALITIES.includes(ds.Modality) &&
|
||||
!foregroundDisplaySetUIDs.includes(ds.displaySetInstanceUID) &&
|
||||
ds.isOverlayable
|
||||
)
|
||||
.sort(sortByPriority);
|
||||
}, [includePotentialForeground, enhancedDisplaySets, foregroundDisplaySetUIDs]);
|
||||
|
||||
const potentialBackgroundDisplaySets = useMemo(() => {
|
||||
if (!includePotentialBackground || !backgroundDisplaySet) {
|
||||
return [];
|
||||
}
|
||||
return allDisplaySets
|
||||
.filter(
|
||||
ds =>
|
||||
!DERIVED_OVERLAY_MODALITIES.includes(ds.Modality) &&
|
||||
ds.displaySetInstanceUID !== backgroundDisplaySet.displaySetInstanceUID &&
|
||||
!overlayDisplaySetUIDs.includes(ds.displaySetInstanceUID) &&
|
||||
!foregroundDisplaySetUIDs.includes(ds.displaySetInstanceUID)
|
||||
)
|
||||
.sort(sortByPriority);
|
||||
}, [
|
||||
includePotentialBackground,
|
||||
allDisplaySets,
|
||||
backgroundDisplaySet,
|
||||
overlayDisplaySetUIDs,
|
||||
foregroundDisplaySetUIDs,
|
||||
]);
|
||||
|
||||
const result: ViewportDisplaySets = {};
|
||||
|
||||
if (includeBackground) {
|
||||
result.backgroundDisplaySet = backgroundDisplaySet;
|
||||
}
|
||||
|
||||
if (includeForeground) {
|
||||
result.foregroundDisplaySets = foregroundDisplaySets;
|
||||
}
|
||||
|
||||
if (includeOverlay) {
|
||||
result.overlayDisplaySets = overlayDisplaySets;
|
||||
}
|
||||
|
||||
if (includePotentialOverlay) {
|
||||
result.potentialOverlayDisplaySets = potentialOverlayDisplaySets;
|
||||
}
|
||||
|
||||
if (includePotentialForeground) {
|
||||
result.potentialForegroundDisplaySets = potentialForegroundDisplaySets;
|
||||
}
|
||||
|
||||
if (includePotentialBackground) {
|
||||
result.potentialBackgroundDisplaySets = potentialBackgroundDisplaySets;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -269,4 +269,7 @@ export {
|
||||
CornerstoneViewportDownloadForm,
|
||||
utils,
|
||||
};
|
||||
|
||||
// Export constants
|
||||
export { VOLUME_LOADER_SCHEME, DYNAMIC_VOLUME_LOADER_SCHEME } from './constants';
|
||||
export default cornerstoneExtension;
|
||||
@@ -96,6 +96,8 @@ export default async function init({
|
||||
viewportGridService,
|
||||
segmentationService,
|
||||
measurementService,
|
||||
colorbarService,
|
||||
displaySetService,
|
||||
} = servicesManager.services;
|
||||
|
||||
window.services = servicesManager.services;
|
||||
@@ -232,16 +234,6 @@ export default async function init({
|
||||
}
|
||||
);
|
||||
|
||||
// resize the cornerstone viewport service when the grid size changes
|
||||
// IMPORTANT: this should happen outside of the OHIFCornerstoneViewport
|
||||
// since it will trigger a rerender of each viewport and each resizing
|
||||
// the offscreen canvas which would result in a performance hit, this should
|
||||
// done only once per grid resize here. Doing it once here, allows us to reduce
|
||||
// the refreshRage(in ms) to 10 from 50. I tried with even 1 or 5 ms it worked fine
|
||||
viewportGridService.subscribe(viewportGridService.EVENTS.GRID_SIZE_CHANGED, () => {
|
||||
cornerstoneViewportService.resize(true);
|
||||
});
|
||||
|
||||
initContextMenu({
|
||||
cornerstoneViewportService,
|
||||
customizationService,
|
||||
@@ -265,8 +257,17 @@ export default async function init({
|
||||
eventTarget.addEventListener(EVENTS.IMAGE_LOAD_FAILED, imageLoadFailedHandler);
|
||||
eventTarget.addEventListener(EVENTS.IMAGE_LOAD_ERROR, imageLoadFailedHandler);
|
||||
|
||||
const getDisplaySetFromVolumeId = (volumeId: string) => {
|
||||
const allDisplaySets = displaySetService.getActiveDisplaySets();
|
||||
const volume = cornerstone.cache.getVolume(volumeId);
|
||||
const imageIds = volume.imageIds;
|
||||
return allDisplaySets.find(ds => ds.imageIds?.some(id => imageIds.includes(id)));
|
||||
};
|
||||
|
||||
function elementEnabledHandler(evt) {
|
||||
const { element } = evt.detail;
|
||||
const { viewport } = getEnabledElement(element);
|
||||
initViewTiming({ element });
|
||||
|
||||
element.addEventListener(EVENTS.CAMERA_RESET, evt => {
|
||||
const { element } = evt.detail;
|
||||
@@ -278,7 +279,10 @@ export default async function init({
|
||||
commandsManager.runCommand('resetCrosshairs', { viewportId });
|
||||
});
|
||||
|
||||
initViewTiming({ element });
|
||||
// limitation: currently supporting only volume viewports with fusion
|
||||
if (viewport.type !== cornerstone.Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
eventTarget.addEventListener(EVENTS.ELEMENT_ENABLED, elementEnabledHandler.bind(null));
|
||||
@@ -289,58 +293,136 @@ export default async function init({
|
||||
eventTarget.addEventListenerDebounced(
|
||||
EVENTS.ERROR_EVENT,
|
||||
({ detail }) => {
|
||||
// Create a stable ID for deduplication based on error type and message
|
||||
const errorId = `cornerstone-error-${detail.type}-${detail.message.substring(0, 50)}`;
|
||||
|
||||
uiNotificationService.show({
|
||||
title: detail.type,
|
||||
message: detail.message,
|
||||
type: 'error',
|
||||
id: errorId,
|
||||
allowDuplicates: false, // Prevent duplicate error notifications
|
||||
deduplicationInterval: 30000, // 30 seconds deduplication window
|
||||
});
|
||||
},
|
||||
100
|
||||
);
|
||||
|
||||
// Subscribe to actor events to dynamically update colorbars
|
||||
|
||||
// Call this function when initializing
|
||||
initializeWebWorkerProgressHandler(servicesManager.services.uiNotificationService);
|
||||
|
||||
const { unsubscribe: unsubscribeSegmentationCreated } = segmentationService.subscribe(
|
||||
segmentationService.EVENTS.SEGMENTATION_ADDED,
|
||||
evt => {
|
||||
const { segmentationId } = evt;
|
||||
const displaySet = displaySetService.getDisplaySetByUID(segmentationId);
|
||||
if (displaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
const label = segmentation.cachedStats.info;
|
||||
const imageIds = segmentation.representationData.Labelmap.imageIds;
|
||||
|
||||
// Create a display set for the segmentation
|
||||
const segmentationDisplaySet = {
|
||||
displaySetInstanceUID: segmentationId,
|
||||
SOPClassUID: '1.2.840.10008.5.1.4.1.1.66.4',
|
||||
SOPClassHandlerId: '@ohif/extension-cornerstone-dicom-seg.sopClassHandlerModule.dicom-seg',
|
||||
SeriesDescription: label,
|
||||
Modality: 'SEG',
|
||||
numImageFrames: imageIds.length,
|
||||
imageIds,
|
||||
isOverlayDisplaySet: true,
|
||||
label,
|
||||
madeInClient: true,
|
||||
segmentationId: segmentationId,
|
||||
isDerived: true,
|
||||
};
|
||||
|
||||
displaySetService.addDisplaySets(segmentationDisplaySet);
|
||||
}
|
||||
);
|
||||
|
||||
const unsubscriptions = [
|
||||
unsubscribeSegmentationDataModifiedHandler,
|
||||
unsubscribeSegmentationModifiedHandler,
|
||||
unsubscribeSegmentationCreated,
|
||||
];
|
||||
|
||||
return { unsubscriptions };
|
||||
}
|
||||
|
||||
function initializeWebWorkerProgressHandler(uiNotificationService) {
|
||||
const activeToasts = new Map();
|
||||
// Use a single map to track all active worker tasks
|
||||
const activeWorkerTasks = new Map();
|
||||
|
||||
// eventTarget.addEventListener(EVENTS.WEB_WORKER_PROGRESS, ({ detail }) => {
|
||||
// const { progress, type, id } = detail;
|
||||
// Create a normalized task key that doesn't include the random ID
|
||||
// This helps us identify and deduplicate the same type of task
|
||||
const getNormalizedTaskKey = type => {
|
||||
return `worker-task-${type.toLowerCase().replace(/\s+/g, '-')}`;
|
||||
};
|
||||
|
||||
// const cacheKey = `${type}-${id}`;
|
||||
// if (progress === 0 && !activeToasts.has(cacheKey)) {
|
||||
// const progressPromise = new Promise((resolve, reject) => {
|
||||
// activeToasts.set(cacheKey, { resolve, reject });
|
||||
// });
|
||||
eventTarget.addEventListener(EVENTS.WEB_WORKER_PROGRESS, ({ detail }) => {
|
||||
const { progress, type, id } = detail;
|
||||
|
||||
// uiNotificationService.show({
|
||||
// id: cacheKey,
|
||||
// title: `${type}`,
|
||||
// message: `${type}: ${progress}%`,
|
||||
// autoClose: false,
|
||||
// promise: progressPromise,
|
||||
// promiseMessages: {
|
||||
// loading: `Computing...`,
|
||||
// success: `Completed successfully`,
|
||||
// error: 'Web Worker failed',
|
||||
// },
|
||||
// });
|
||||
// } else {
|
||||
// if (progress === 100) {
|
||||
// const { resolve } = activeToasts.get(cacheKey);
|
||||
// resolve({ progress, type });
|
||||
// activeToasts.delete(cacheKey);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// Skip notifications for compute statistics
|
||||
if (type === cornerstoneTools.Enums.WorkerTypes.COMPUTE_STATISTICS) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedKey = getNormalizedTaskKey(type);
|
||||
|
||||
if (progress === 0) {
|
||||
// Check if we're already tracking a task of this type
|
||||
if (!activeWorkerTasks.has(normalizedKey)) {
|
||||
const progressPromise = new Promise((resolve, reject) => {
|
||||
activeWorkerTasks.set(normalizedKey, {
|
||||
resolve,
|
||||
reject,
|
||||
originalId: id,
|
||||
type,
|
||||
});
|
||||
});
|
||||
|
||||
uiNotificationService.show({
|
||||
id: normalizedKey, // Use the normalized key as ID for better deduplication
|
||||
title: `${type}`,
|
||||
message: `Computing...`,
|
||||
autoClose: false,
|
||||
allowDuplicates: false,
|
||||
deduplicationInterval: 60000, // 60 seconds - prevent frequent notifications of same type
|
||||
promise: progressPromise,
|
||||
promiseMessages: {
|
||||
loading: `Computing...`,
|
||||
success: `Completed successfully`,
|
||||
error: 'Web Worker failed',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Already tracking this type of task, just let it continue
|
||||
console.debug(`Already tracking a "${type}" task, skipping duplicate notification`);
|
||||
}
|
||||
}
|
||||
// Task completed
|
||||
else if (progress === 100) {
|
||||
// Check if we have this task type in our tracking map
|
||||
const taskData = activeWorkerTasks.get(normalizedKey);
|
||||
|
||||
if (taskData) {
|
||||
// Resolve the promise to update the notification
|
||||
const { resolve } = taskData;
|
||||
resolve({ progress, type });
|
||||
|
||||
// Remove from tracking
|
||||
activeWorkerTasks.delete(normalizedKey);
|
||||
|
||||
console.debug(`Worker task "${type}" completed successfully`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,6 +48,8 @@ import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool';
|
||||
|
||||
export default function initCornerstoneTools(configuration = {}) {
|
||||
CrosshairsTool.isAnnotation = false;
|
||||
LabelmapSlicePropagationTool.isAnnotation = false;
|
||||
MarkerLabelmapTool.isAnnotation = false;
|
||||
ReferenceLinesTool.isAnnotation = false;
|
||||
AdvancedMagnifyTool.isAnnotation = false;
|
||||
PlanarFreehandContourSegmentationTool.isAnnotation = false;
|
||||
|
||||
@@ -38,14 +38,16 @@ export default class ColorbarService extends PubSubService {
|
||||
|
||||
public static REGISTRATION = {
|
||||
name: 'colorbarService',
|
||||
create: () => {
|
||||
return new ColorbarService();
|
||||
create: ({ servicesManager }: OhifTypes.Extensions.ExtensionParams) => {
|
||||
return new ColorbarService(servicesManager);
|
||||
},
|
||||
};
|
||||
colorbars = {};
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
|
||||
constructor() {
|
||||
constructor(servicesManager: AppTypes.ServicesManager) {
|
||||
super(ColorbarService.EVENTS);
|
||||
this.servicesManager = servicesManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +70,7 @@ export default class ColorbarService extends PubSubService {
|
||||
* @param options Configuration options for the colorbar, including position, colormaps, active colormap name, ticks, and width.
|
||||
*/
|
||||
public addColorbar(viewportId, displaySetInstanceUIDs, options = {} as ColorbarOptions) {
|
||||
const { displaySetService } = this.servicesManager.services;
|
||||
const renderingEngine = getRenderingEngine(RENDERING_ENGINE_ID);
|
||||
const viewport = renderingEngine.getViewport(viewportId);
|
||||
|
||||
@@ -95,6 +98,12 @@ export default class ColorbarService extends PubSubService {
|
||||
);
|
||||
|
||||
displaySetInstanceUIDs.forEach((displaySetInstanceUID, index) => {
|
||||
// don't show colorbar for overlay display sets (e.g. segmentation)
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
if (displaySet.isOverlayDisplaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const volumeId = this.getVolumeIdForIdentifier(viewport, displaySetInstanceUID);
|
||||
const properties = viewport?.getProperties(volumeId);
|
||||
const colormap = properties?.colormap;
|
||||
@@ -123,9 +132,19 @@ export default class ColorbarService extends PubSubService {
|
||||
volumeId: viewport instanceof VolumeViewport ? volumeId : undefined,
|
||||
});
|
||||
if (this.colorbars[viewportId]) {
|
||||
this.colorbars[viewportId].push({ colorbar, container: colorbarContainer });
|
||||
this.colorbars[viewportId].push({
|
||||
colorbar,
|
||||
container: colorbarContainer,
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
} else {
|
||||
this.colorbars[viewportId] = [{ colorbar, container: colorbarContainer }];
|
||||
this.colorbars[viewportId] = [
|
||||
{
|
||||
colorbar,
|
||||
container: colorbarContainer,
|
||||
displaySetInstanceUID,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -136,24 +155,50 @@ export default class ColorbarService extends PubSubService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the colorbar associated with a given viewport ID. This involves cleaning up any created DOM elements and internal references.
|
||||
* Removes a colorbar from a specific viewport. If displaySetInstanceUID is provided,
|
||||
* only the colorbar associated with that specific displaySetInstanceUID will be removed.
|
||||
* Otherwise, all colorbars for the given viewport will be removed.
|
||||
*
|
||||
* @param viewportId The identifier for the viewport from which the colorbar will be removed.
|
||||
* @param displaySetInstanceUID Optional. The specific display set instance UID associated with the colorbar to remove.
|
||||
*/
|
||||
public removeColorbar(viewportId) {
|
||||
public removeColorbar(viewportId, displaySetInstanceUID?: string) {
|
||||
const colorbarInfo = this.colorbars[viewportId];
|
||||
if (!colorbarInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
colorbarInfo.forEach(({ colorbar, container }) => {
|
||||
container.parentNode.removeChild(container);
|
||||
});
|
||||
if (displaySetInstanceUID) {
|
||||
// Find the index of the colorbar with the matching displaySetInstanceUID
|
||||
const index = colorbarInfo.findIndex(
|
||||
info => info.displaySetInstanceUID === displaySetInstanceUID
|
||||
);
|
||||
|
||||
delete this.colorbars[viewportId];
|
||||
if (index !== -1) {
|
||||
// Remove only the specific colorbar container
|
||||
const { container } = colorbarInfo[index];
|
||||
container.parentNode.removeChild(container);
|
||||
|
||||
// Remove the colorbar from the array
|
||||
colorbarInfo.splice(index, 1);
|
||||
|
||||
// If there are no more colorbars for this viewport, remove the entry
|
||||
if (colorbarInfo.length === 0) {
|
||||
delete this.colorbars[viewportId];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove all colorbars for the viewport (original behavior)
|
||||
colorbarInfo.forEach(({ container }) => {
|
||||
container.parentNode.removeChild(container);
|
||||
});
|
||||
|
||||
delete this.colorbars[viewportId];
|
||||
}
|
||||
|
||||
this._broadcastEvent(ColorbarService.EVENTS.STATE_CHANGED, {
|
||||
viewportId,
|
||||
displaySetInstanceUID,
|
||||
changeType: ChangeTypes.Removed,
|
||||
});
|
||||
}
|
||||
|
||||
+1
-2
@@ -3,8 +3,7 @@ import { cache as cs3DCache, Enums, volumeLoader } from '@cornerstonejs/core';
|
||||
|
||||
import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
|
||||
const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
|
||||
import { VOLUME_LOADER_SCHEME } from '../../constants';
|
||||
|
||||
class CornerstoneCacheService {
|
||||
static REGISTRATION = {
|
||||
|
||||
+21
-18
@@ -8,26 +8,29 @@
|
||||
* and geometry Id
|
||||
*/
|
||||
export function mapROIContoursToRTStructData(structureSet: unknown, rtDisplaySetUID: unknown) {
|
||||
return structureSet.ROIContours.map(({ contourPoints, ROINumber, ROIName, colorArray }) => {
|
||||
const data = contourPoints.map(({ points, ...rest }) => {
|
||||
const newPoints = points.map(({ x, y, z }) => {
|
||||
return [x, y, z];
|
||||
return structureSet.ROIContours.map(
|
||||
({ contourPoints, ROINumber, ROIName, colorArray, ROIGroup }) => {
|
||||
const data = contourPoints.map(({ points, ...rest }) => {
|
||||
const newPoints = points.map(({ x, y, z }) => {
|
||||
return [x, y, z];
|
||||
});
|
||||
|
||||
return {
|
||||
...rest,
|
||||
points: newPoints,
|
||||
};
|
||||
});
|
||||
|
||||
const id = ROIName || ROINumber;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
points: newPoints,
|
||||
data,
|
||||
id,
|
||||
segmentIndex: ROINumber,
|
||||
color: colorArray,
|
||||
group: ROIGroup,
|
||||
geometryId: `${rtDisplaySetUID}:${id}:segmentIndex-${ROINumber}`,
|
||||
};
|
||||
});
|
||||
|
||||
const id = ROIName || ROINumber;
|
||||
|
||||
return {
|
||||
data,
|
||||
id,
|
||||
segmentIndex: ROINumber,
|
||||
color: colorArray,
|
||||
geometryId: `${rtDisplaySetUID}:${id}:segmentIndex-${ROINumber}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { updateLabelmapSegmentationImageReferences } from '@cornerstonejs/tools/
|
||||
import { triggerSegmentationRepresentationModified } from '@cornerstonejs/tools/segmentation/triggerSegmentationEvents';
|
||||
import { convertStackToVolumeLabelmap } from '@cornerstonejs/tools/segmentation/helpers/convertStackToVolumeLabelmap';
|
||||
import { getLabelmapImageIds } from '@cornerstonejs/tools/segmentation';
|
||||
import { VOLUME_LOADER_SCHEME } from '../../constants';
|
||||
|
||||
const LABELMAP = csToolsEnums.SegmentationRepresentations.Labelmap;
|
||||
const CONTOUR = csToolsEnums.SegmentationRepresentations.Contour;
|
||||
@@ -80,8 +81,6 @@ const EVENTS = {
|
||||
|
||||
const VALUE_TYPES = {};
|
||||
|
||||
const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume';
|
||||
|
||||
class SegmentationService extends PubSubService {
|
||||
static REGISTRATION = {
|
||||
name: 'segmentationService',
|
||||
@@ -615,7 +614,7 @@ class SegmentationService extends PubSubService {
|
||||
|
||||
// Process each segment similarly to the SEG function
|
||||
for (const rtStructData of allRTStructData) {
|
||||
const { data, id, color, segmentIndex, geometryId } = rtStructData;
|
||||
const { data, id, color, segmentIndex, geometryId, group } = rtStructData;
|
||||
|
||||
// Add the color to the colorLUT array
|
||||
colorLUT.push(color);
|
||||
@@ -646,6 +645,7 @@ class SegmentationService extends PubSubService {
|
||||
cachedStats: segmentsCachedStats,
|
||||
locked: false,
|
||||
active: false,
|
||||
group,
|
||||
};
|
||||
|
||||
// Broadcast segment loading progress
|
||||
|
||||
+21
@@ -10,6 +10,11 @@ export type ActionComponentInfo = {
|
||||
indexPriority?: number;
|
||||
};
|
||||
|
||||
export type AlignAndSide = {
|
||||
align: 'start' | 'end' | 'center';
|
||||
side: 'top' | 'bottom' | 'left' | 'right';
|
||||
};
|
||||
|
||||
class ViewportActionCornersService extends PubSubService {
|
||||
public static readonly EVENTS = {};
|
||||
public static readonly LOCATIONS = ViewportActionCornersLocations;
|
||||
@@ -66,6 +71,22 @@ class ViewportActionCornersService extends PubSubService {
|
||||
public clear(viewportId: string) {
|
||||
this.serviceImplementation._clear(viewportId);
|
||||
}
|
||||
|
||||
public getAlignAndSide(location: ViewportActionCornersLocations): AlignAndSide {
|
||||
switch (location) {
|
||||
case ViewportActionCornersLocations.topLeft:
|
||||
return { align: 'start', side: 'bottom' };
|
||||
case ViewportActionCornersLocations.topRight:
|
||||
return { align: 'end', side: 'bottom' };
|
||||
case ViewportActionCornersLocations.bottomLeft:
|
||||
return { align: 'start', side: 'top' };
|
||||
case ViewportActionCornersLocations.bottomRight:
|
||||
return { align: 'end', side: 'top' };
|
||||
default:
|
||||
console.debug('Unknown location, defaulting to bottom-start');
|
||||
return { align: 'start', side: 'bottom' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ViewportActionCornersService;
|
||||
@@ -32,12 +32,16 @@ import { useLutPresentationStore } from '../../stores/useLutPresentationStore';
|
||||
import { usePositionPresentationStore } from '../../stores/usePositionPresentationStore';
|
||||
import { useSynchronizersStore } from '../../stores/useSynchronizersStore';
|
||||
import { useSegmentationPresentationStore } from '../../stores/useSegmentationPresentationStore';
|
||||
import { VOLUME_LOADER_SCHEME } from '../../constants';
|
||||
|
||||
const EVENTS = {
|
||||
VIEWPORT_DATA_CHANGED: 'event::cornerstoneViewportService:viewportDataChanged',
|
||||
VIEWPORT_VOLUMES_CHANGED: 'event::cornerstoneViewportService:viewportVolumesChanged',
|
||||
};
|
||||
|
||||
const MIN_STACK_VIEWPORTS_TO_ENQUEUE_RESIZE = 12;
|
||||
const MIN_VOLUME_VIEWPORTS_TO_ENQUEUE_RESIZE = 6;
|
||||
|
||||
export const WITH_NAVIGATION = { withNavigation: true, withOrientation: false };
|
||||
|
||||
/**
|
||||
@@ -62,9 +66,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
beforeResizePositionPresentations: Map<string, PositionPresentation> = new Map();
|
||||
|
||||
// Some configs
|
||||
enableResizeDetector: true;
|
||||
resizeRefreshRateMs: 200;
|
||||
resizeRefreshMode: 'debounce';
|
||||
servicesManager: AppTypes.ServicesManager = null;
|
||||
|
||||
resizeQueue = [];
|
||||
@@ -125,13 +126,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
/**
|
||||
* It triggers the resize on the rendering engine, and renders the viewports
|
||||
*
|
||||
* @param isGridResize - if the resize is triggered by a grid resize
|
||||
* this is used to avoid double resize of the viewports since if the
|
||||
* grid is resized, all viewports will be resized so there is no need
|
||||
* to resize them individually which will get triggered by their
|
||||
* individual resize observers
|
||||
*/
|
||||
public resize(isGridResize = false) {
|
||||
public resize() {
|
||||
// https://stackoverflow.com/a/26279685
|
||||
// This resize() call, among other things, rerenders the viewports. But when the entire viewer is
|
||||
// display: none'd, it makes the size of all hidden elements 0, including the viewport canvas and its containers.
|
||||
@@ -147,12 +143,24 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return;
|
||||
}
|
||||
|
||||
const numStackViewportsInViewportGrid = Array.from(this.viewportsById.values()).filter(
|
||||
viewportInfo => viewportInfo.getViewportType() === csEnums.ViewportType.STACK
|
||||
).length;
|
||||
|
||||
const numVolumeViewportsInViewportGrid = Array.from(this.viewportsById.values()).filter(
|
||||
viewportInfo => viewportInfo.getViewportType() === csEnums.ViewportType.ORTHOGRAPHIC
|
||||
).length;
|
||||
|
||||
const isEasyResize =
|
||||
numStackViewportsInViewportGrid <= MIN_STACK_VIEWPORTS_TO_ENQUEUE_RESIZE &&
|
||||
numVolumeViewportsInViewportGrid <= MIN_VOLUME_VIEWPORTS_TO_ENQUEUE_RESIZE;
|
||||
|
||||
// if there is a grid resize happening, it means the viewport grid
|
||||
// has been manipulated (e.g., panels closed, added, etc.) and we need
|
||||
// to resize all viewports, so we will add a timeout here to make sure
|
||||
// we don't double resize the viewports when viewports in the grid are
|
||||
// resized individually
|
||||
if (isGridResize) {
|
||||
if (isEasyResize) {
|
||||
this.performResize();
|
||||
this.resetGridResizeTimeout();
|
||||
this.resizeQueue = [];
|
||||
@@ -400,11 +408,6 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// and we would lose the presentation.
|
||||
this.storePresentation({ viewportId: viewportInfo.getViewportId() });
|
||||
|
||||
// Todo: i don't like this here, move it
|
||||
this.servicesManager.services.segmentationService.clearSegmentationRepresentations(
|
||||
viewportInfo.getViewportId()
|
||||
);
|
||||
|
||||
if (!viewportInfo) {
|
||||
throw new Error('element is not enabled for the given viewportId');
|
||||
}
|
||||
@@ -506,6 +509,11 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
return this.viewportsById.get(viewportId);
|
||||
}
|
||||
|
||||
public getOrientation(viewportId: string): string {
|
||||
const viewportInfo = this.getViewportInfo(viewportId);
|
||||
return viewportInfo.getOrientation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks through the viewports to see if the specified measurement can be
|
||||
* displayed in one of the viewports. This function tries to get a "best fit"
|
||||
@@ -754,8 +762,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const { imageIds, displaySetInstanceUID } = data;
|
||||
let volume = data.volume;
|
||||
|
||||
if (!volume) {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
if (!volume && displaySet.images) {
|
||||
volume = csToolsUtils.getOrCreateImageVolume(displaySet.images.map(image => image.imageId));
|
||||
}
|
||||
|
||||
@@ -773,6 +781,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
volumeInputArray.push({
|
||||
imageIds,
|
||||
volumeId,
|
||||
modality: displaySet.Modality,
|
||||
displaySetInstanceUID,
|
||||
blendMode: displaySetOptions.blendMode,
|
||||
slabThickness: this._getSlabThickness(displaySetOptions, volumeId),
|
||||
});
|
||||
@@ -815,8 +825,15 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewport.id);
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetUIDs[0]);
|
||||
const displaySetModality = displaySet?.Modality;
|
||||
|
||||
// filter overlay display sets (e.g. segmentation) since they will get handled below via the segmentation service
|
||||
const filteredVolumeInputArray = volumeInputArray.filter(volumeInput => {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(volumeInput.displaySetInstanceUID);
|
||||
return !displaySet?.isOverlayDisplaySet;
|
||||
});
|
||||
|
||||
// Todo: use presentations states
|
||||
const volumesProperties = volumeInputArray.map((volumeInput, index) => {
|
||||
const volumesProperties = filteredVolumeInputArray.map((volumeInput, index) => {
|
||||
const { volumeId } = volumeInput;
|
||||
const displaySetOption = displaySetOptions[index];
|
||||
const { voi, voiInverted, colormap, displayPreset } = displaySetOption;
|
||||
@@ -846,9 +863,29 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
});
|
||||
|
||||
// For SEG and RT viewports
|
||||
const { addOverlayFn } = this._processExtraDisplaySetsForViewport(viewport) || {};
|
||||
const { addOverlayFn, imageIds } = this._processExtraDisplaySetsForViewport(viewport) || {};
|
||||
|
||||
await viewport.setVolumes(volumeInputArray);
|
||||
if (!filteredVolumeInputArray.length && addOverlayFn) {
|
||||
// if there is no volume input array, and there is an addOverlayFn, means we need to take
|
||||
// care of the background overlay display set first then the addOverlayFn will add the
|
||||
// SEG displaySet
|
||||
const sampleImageId = imageIds[0];
|
||||
const backgroundDisplaySet = displaySetService.getDisplaySetsBy(
|
||||
displaySet =>
|
||||
!displaySet.isOverlayDisplaySet &&
|
||||
displaySet.images.some(image => image.imageId === sampleImageId)
|
||||
);
|
||||
|
||||
if (backgroundDisplaySet.length !== 1) {
|
||||
throw new Error('Background display set not found');
|
||||
}
|
||||
|
||||
await viewport.setVolumes([
|
||||
{ volumeId: `${VOLUME_LOADER_SCHEME}:${backgroundDisplaySet[0].displaySetInstanceUID}` },
|
||||
]);
|
||||
} else {
|
||||
await viewport.setVolumes(filteredVolumeInputArray);
|
||||
}
|
||||
|
||||
if (addOverlayFn) {
|
||||
addOverlayFn();
|
||||
@@ -889,31 +926,30 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
|
||||
// load any secondary displaySets
|
||||
const displaySetInstanceUIDs = this.viewportsDisplaySets.get(viewport.id);
|
||||
|
||||
// Can be SEG or RTSTRUCT for now but not PMAP
|
||||
const segOrRTSOverlayDisplaySet = displaySetInstanceUIDs
|
||||
// Find overlay display sets (e.g. SEG, RTSTRUCT)
|
||||
const overlayDisplaySet = displaySetInstanceUIDs
|
||||
.map(displaySetService.getDisplaySetByUID)
|
||||
.find(
|
||||
displaySet =>
|
||||
displaySet?.isOverlayDisplaySet && ['SEG', 'RTSTRUCT'].includes(displaySet.Modality)
|
||||
);
|
||||
.find(displaySet => displaySet?.isOverlayDisplaySet);
|
||||
|
||||
// if it is only the overlay displaySet, then we need to get the reference
|
||||
// displaySet imageIds and set them as the imageIds for the viewport,
|
||||
// here we can do some logic if the reference is missing
|
||||
// then find the most similar match of displaySet instead
|
||||
if (!segOrRTSOverlayDisplaySet) {
|
||||
if (!overlayDisplaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const referenceDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
segOrRTSOverlayDisplaySet.referencedDisplaySetInstanceUID
|
||||
);
|
||||
const imageIds = referenceDisplaySet.images.map(image => image.imageId);
|
||||
let imageIds;
|
||||
if (overlayDisplaySet.referencedDisplaySetInstanceUID) {
|
||||
const referenceDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
overlayDisplaySet.referencedDisplaySetInstanceUID
|
||||
);
|
||||
imageIds = referenceDisplaySet.images.map(image => image.imageId);
|
||||
}
|
||||
|
||||
return {
|
||||
imageIds,
|
||||
addOverlayFn: () =>
|
||||
this.addOverlayRepresentationForDisplaySet(segOrRTSOverlayDisplaySet, viewport),
|
||||
addOverlayFn: () => this.addOverlayRepresentationForDisplaySet(overlayDisplaySet, viewport),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,8 @@ export interface IViewportService {
|
||||
/**
|
||||
* It creates a resize observer for the viewport element, and observes
|
||||
* the element for resizing events
|
||||
* @param {*} elementRef
|
||||
*/
|
||||
resize(isGridResize: boolean): void;
|
||||
resize(): void;
|
||||
/**
|
||||
* Removes the viewport from cornerstone, and destroys the rendering engine
|
||||
*/
|
||||
|
||||
@@ -316,6 +316,10 @@ class ViewportInfo {
|
||||
return this.viewportOptions.orientation;
|
||||
}
|
||||
|
||||
public setOrientation(orientation: Enums.OrientationAxis): void {
|
||||
this.viewportOptions.orientation = orientation;
|
||||
}
|
||||
|
||||
public getDisplayArea(): Types.DisplayArea {
|
||||
return this.viewportOptions.displayArea;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { CommandsManager } from '@ohif/core';
|
||||
|
||||
export type ViewportPreset = {
|
||||
name: string;
|
||||
gradientOpacity: string;
|
||||
@@ -15,8 +13,6 @@ export type ViewportPreset = {
|
||||
|
||||
export type VolumeRenderingPresetsProps = {
|
||||
viewportId: string;
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
commandsManager: CommandsManager;
|
||||
volumeRenderingPresets: ViewportPreset[];
|
||||
};
|
||||
|
||||
@@ -24,13 +20,10 @@ export type VolumeRenderingPresetsContentProps = {
|
||||
presets: ViewportPreset[];
|
||||
onClose: () => void;
|
||||
viewportId: string;
|
||||
commandsManager: CommandsManager;
|
||||
};
|
||||
|
||||
export type VolumeRenderingOptionsProps = {
|
||||
viewportId: string;
|
||||
commandsManager: CommandsManager;
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
volumeRenderingQualityRange: VolumeRenderingQualityRange;
|
||||
};
|
||||
|
||||
@@ -42,27 +35,19 @@ export type VolumeRenderingQualityRange = {
|
||||
|
||||
export type VolumeRenderingQualityProps = {
|
||||
viewportId: string;
|
||||
commandsManager: CommandsManager;
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
volumeRenderingQualityRange: VolumeRenderingQualityRange;
|
||||
};
|
||||
|
||||
export type VolumeShiftProps = {
|
||||
viewportId: string;
|
||||
commandsManager: CommandsManager;
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
};
|
||||
|
||||
export type VolumeShadeProps = {
|
||||
viewportId: string;
|
||||
commandsManager: CommandsManager;
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
onClickShade?: (bool: boolean) => void;
|
||||
};
|
||||
|
||||
export type VolumeLightingProps = {
|
||||
viewportId: string;
|
||||
commandsManager: CommandsManager;
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
hasShade: boolean;
|
||||
};
|
||||
@@ -1,5 +1,10 @@
|
||||
import * as CornerstoneCacheService from './CornerstoneCacheService';
|
||||
import type CornerstoneServices from './CornerstoneServices';
|
||||
export { type SyncGroup } from '../services/SyncGroupService';
|
||||
export {
|
||||
type ViewportActionCornerLocation,
|
||||
type ViewportActionCornerService,
|
||||
type CustomizationService,
|
||||
} from '../hooks/useViewportActionCorners';
|
||||
|
||||
export type { CornerstoneCacheService, CornerstoneServices };
|
||||
@@ -180,15 +180,12 @@ const CornerstoneViewportDownloadForm = ({
|
||||
const toolGroup = ToolGroupManager.getToolGroupForViewport(activeViewportId, renderingEngineId);
|
||||
toolGroup.addViewport(downloadViewportId, renderingEngineId);
|
||||
|
||||
Object.keys(toolGroup.getToolInstances()).forEach(toolName => {
|
||||
if (show && toolName !== 'Crosshairs') {
|
||||
try {
|
||||
toolGroup.setToolEnabled(toolName);
|
||||
} catch (error) {
|
||||
console.debug('Error enabling tool:', error);
|
||||
}
|
||||
} else {
|
||||
toolGroup.setToolDisabled(toolName);
|
||||
const toolInstances = toolGroup.getToolInstances();
|
||||
const toolInstancesArray = Object.values(toolInstances);
|
||||
|
||||
toolInstancesArray.forEach(toolInstance => {
|
||||
if (toolInstance.constructor.isAnnotation !== false) {
|
||||
toolGroup.setToolEnabled(toolInstance.toolName);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -13,6 +13,15 @@ const colormaps = [
|
||||
RGBPoints: [0, 1, 1, 1, 1, 0, 0, 0],
|
||||
description: 'X Ray',
|
||||
},
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'Isodose',
|
||||
NanColor: [1, 0, 0],
|
||||
RGBPoints: [
|
||||
0, 0, 1, 0, 0.1, 0.5, 1, 0, 0.2, 1, 1, 0, 0.3, 1, 0.66, 0, 0.4, 1, 0.33, 0, 0.5, 1, 0, 0,
|
||||
],
|
||||
description: 'Isodose',
|
||||
},
|
||||
{
|
||||
ColorSpace: 'RGB',
|
||||
Name: 'hsv',
|
||||
|
||||
@@ -24,9 +24,13 @@ export function getViewportPresentations(
|
||||
|
||||
const { lutPresentationId, positionPresentationId, segmentationPresentationId } = presentationIds;
|
||||
|
||||
const positionPresentation = positionPresentationStore[positionPresentationId];
|
||||
const lutPresentation = lutPresentationStore[lutPresentationId];
|
||||
const segmentationPresentation = segmentationPresentationStore[segmentationPresentationId];
|
||||
|
||||
return {
|
||||
positionPresentation: positionPresentationStore[positionPresentationId],
|
||||
lutPresentation: lutPresentationStore[lutPresentationId],
|
||||
segmentationPresentation: segmentationPresentationStore[segmentationPresentationId],
|
||||
positionPresentation,
|
||||
lutPresentation,
|
||||
segmentationPresentation,
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,224 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [3.11.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.25...v3.11.0-beta.26) (2025-05-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.24...v3.11.0-beta.25) (2025-05-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.23...v3.11.0-beta.24) (2025-05-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **data overlay:** add foreground background control plus orientation change in the UI and add RTDOSE support ([#5007](https://github.com/OHIF/Viewers/issues/5007)) ([cd4e640](https://github.com/OHIF/Viewers/commit/cd4e6408bc2964e5ee27427cdc328751eaf4d088))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.22...v3.11.0-beta.23) (2025-05-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.21...v3.11.0-beta.22) (2025-05-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* improve menu modals customizations ([#5011](https://github.com/OHIF/Viewers/issues/5011)) ([7dbe5e3](https://github.com/OHIF/Viewers/commit/7dbe5e37b06056fdd5262a131417013d112c3d30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.20...v3.11.0-beta.21) (2025-05-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.19...v3.11.0-beta.20) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.18...v3.11.0-beta.19) (2025-05-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.17...v3.11.0-beta.18) (2025-04-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.16...v3.11.0-beta.17) (2025-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **referencelines:** and bring back the notification for worker updates but disallow duplication ([#5005](https://github.com/OHIF/Viewers/issues/5005)) ([98f8187](https://github.com/OHIF/Viewers/commit/98f8187dfeb8aec6eca145da75c855aec0da4e1d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.15...v3.11.0-beta.16) (2025-04-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.14...v3.11.0-beta.15) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.13...v3.11.0-beta.14) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.12...v3.11.0-beta.13) (2025-04-22)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.11...v3.11.0-beta.12) (2025-04-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Improve unsupported displayset messages ([#4979](https://github.com/OHIF/Viewers/issues/4979)) ([3b8faa8](https://github.com/OHIF/Viewers/commit/3b8faa8a76dde5088815431a947fec07db4a3c7f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.10...v3.11.0-beta.11) (2025-04-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.9...v3.11.0-beta.10) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.8...v3.11.0-beta.9) (2025-04-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.7...v3.11.0-beta.8) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.6...v3.11.0-beta.7) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.5...v3.11.0-beta.6) (2025-04-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.4...v3.11.0-beta.5) (2025-04-14)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.3...v3.11.0-beta.4) (2025-04-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.2...v3.11.0-beta.3) (2025-04-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* expose scroll thumbnail into view ([#4941](https://github.com/OHIF/Viewers/issues/4941)) ([4926a95](https://github.com/OHIF/Viewers/commit/4926a9532c89011ef932fad1dbae643806e780a8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.1...v3.11.0-beta.2) (2025-04-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make datasource selection work ([#4944](https://github.com/OHIF/Viewers/issues/4944)) ([a9b180c](https://github.com/OHIF/Viewers/commit/a9b180c5d427ec5a6823991cf0c6604be7af1cc2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.11.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.11.0-beta.0...v3.11.0-beta.1) (2025-04-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-default
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-default",
|
||||
"version": "3.11.0-beta.1",
|
||||
"version": "3.11.0-beta.26",
|
||||
"description": "Common/default features and functionality for basic image viewing",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
@@ -32,8 +32,8 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.11.0-beta.1",
|
||||
"@ohif/i18n": "3.11.0-beta.1",
|
||||
"@ohif/core": "3.11.0-beta.26",
|
||||
"@ohif/i18n": "3.11.0-beta.26",
|
||||
"dcmjs": "*",
|
||||
"dicomweb-client": "^0.10.4",
|
||||
"prop-types": "^15.6.2",
|
||||
|
||||
@@ -7,17 +7,14 @@ import {
|
||||
Icons,
|
||||
Button,
|
||||
} from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
/**
|
||||
* The default sub-menu appearance and setup is defined here, but this can be
|
||||
* replaced by
|
||||
*/
|
||||
const getMenuItemsDefault = ({
|
||||
commandsManager,
|
||||
items,
|
||||
servicesManager,
|
||||
...props
|
||||
}: withAppTypes) => {
|
||||
const getMenuItemsDefault = ({ commandsManager, items, ...props }: withAppTypes) => {
|
||||
const { servicesManager } = useSystem();
|
||||
const { customizationService } = servicesManager.services;
|
||||
|
||||
// This allows replacing the default child item for menus, whereas the entire
|
||||
@@ -36,7 +33,7 @@ const getMenuItemsDefault = ({
|
||||
onClick: ({ commandsManager, ...props }: withAppTypes) => () => void;
|
||||
};
|
||||
}) => (
|
||||
<DropdownMenuItem onClick={() => item.onClick({ commandsManager, ...props })}>
|
||||
<DropdownMenuItem onClick={() => item.onClick({ commandsManager, servicesManager, ...props })}>
|
||||
<div className="flex items-center gap-2">
|
||||
{item.iconName && <Icons.ByName name={item.iconName} />}
|
||||
<span>{item.label}</span>
|
||||
@@ -76,12 +73,8 @@ const getMenuItemsDefault = ({
|
||||
* @returns Component bound to the bindProps
|
||||
*/
|
||||
export default function MoreDropdownMenu(bindProps) {
|
||||
const {
|
||||
menuItemsKey,
|
||||
getMenuItems = getMenuItemsDefault,
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
} = bindProps;
|
||||
const { menuItemsKey, getMenuItems = getMenuItemsDefault, commandsManager } = bindProps;
|
||||
const { servicesManager } = useSystem();
|
||||
const { customizationService } = servicesManager.services;
|
||||
|
||||
const items = customizationService.getCustomization(menuItemsKey);
|
||||
|
||||
@@ -128,7 +128,9 @@ const DicomTagBrowser = ({
|
||||
<div className="mb-6 flex flex-row items-start pl-1">
|
||||
<div className="flex w-full flex-row items-start gap-4">
|
||||
<div className="flex w-1/3 flex-col">
|
||||
<span className="text-muted-foreground flex h-6 items-center text-xs">Series</span>
|
||||
<span className="text-muted-foreground flex h-6 items-center pb-2 text-base">
|
||||
Series
|
||||
</span>
|
||||
<Select
|
||||
value={selectedDisplaySetInstanceUID}
|
||||
onValueChange={value => onSelectChange({ value })}
|
||||
@@ -154,7 +156,7 @@ const DicomTagBrowser = ({
|
||||
</div>
|
||||
{shouldShowInstanceList && (
|
||||
<div className="mx-auto mt-0.5 flex w-1/4 flex-col">
|
||||
<span className="text-muted-foreground flex h-6 items-center text-xs">
|
||||
<span className="text-muted-foreground flex h-6 items-center pb-2 text-base">
|
||||
Instance Number ({instanceNumber} of {activeDisplaySet?.images?.length})
|
||||
</span>
|
||||
<Slider
|
||||
@@ -170,7 +172,7 @@ const DicomTagBrowser = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-auto mr-1 flex w-1/3 flex-col">
|
||||
<span className="text-muted-foreground flex h-6 items-center text-xs">
|
||||
<span className="text-muted-foreground flex h-6 items-center pb-2 text-base">
|
||||
Search metadata
|
||||
</span>
|
||||
<InputFilter
|
||||
@@ -182,7 +184,7 @@ const DicomTagBrowser = ({
|
||||
placeholder="Search metadata"
|
||||
className="pl-9 pr-9"
|
||||
/>
|
||||
<InputFilter.ClearButton />
|
||||
<InputFilter.ClearButton className="text-primary mr-0.5 p-0.5" />
|
||||
</InputFilter>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Toolbar } from '../Toolbar/Toolbar';
|
||||
import HeaderPatientInfo from './HeaderPatientInfo';
|
||||
import { PatientInfoVisibility } from './HeaderPatientInfo/HeaderPatientInfo';
|
||||
import { preserveQueryParameters } from '@ohif/app';
|
||||
import { Types } from '@ohif/core';
|
||||
|
||||
function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }>) {
|
||||
const { servicesManager, extensionManager, commandsManager } = useSystem();
|
||||
@@ -38,28 +39,34 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
|
||||
const { t } = useTranslation();
|
||||
const { show } = useModal();
|
||||
|
||||
const AboutModal = customizationService.getCustomization('ohif.aboutModal');
|
||||
const UserPreferencesModal = customizationService.getCustomization('ohif.userPreferencesModal');
|
||||
const AboutModal = customizationService.getCustomization(
|
||||
'ohif.aboutModal'
|
||||
) as Types.MenuComponentCustomization;
|
||||
|
||||
const UserPreferencesModal = customizationService.getCustomization(
|
||||
'ohif.userPreferencesModal'
|
||||
) as Types.MenuComponentCustomization;
|
||||
|
||||
const menuOptions = [
|
||||
{
|
||||
title: t('Header:About'),
|
||||
title: AboutModal?.menuTitle ?? t('Header:About'),
|
||||
icon: 'info',
|
||||
onClick: () =>
|
||||
show({
|
||||
content: AboutModal,
|
||||
title: t('AboutModal:About OHIF Viewer'),
|
||||
containerClassName: 'max-w-md',
|
||||
title: AboutModal?.title ?? t('AboutModal:About OHIF Viewer'),
|
||||
containerClassName: AboutModal?.containerClassName ?? 'max-w-md',
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: t('Header:Preferences'),
|
||||
title: UserPreferencesModal.menuTitle ?? t('Header:Preferences'),
|
||||
icon: 'settings',
|
||||
onClick: () =>
|
||||
show({
|
||||
content: UserPreferencesModal,
|
||||
title: t('UserPreferencesModal:User preferences'),
|
||||
containerClassName: 'flex max-w-4xl p-6 flex-col',
|
||||
title: UserPreferencesModal.title ?? t('UserPreferencesModal:User preferences'),
|
||||
containerClassName:
|
||||
UserPreferencesModal?.containerClassName ?? 'flex max-w-4xl p-6 flex-col',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -4,7 +4,7 @@ const collapsedOutsideBorderSize = 4;
|
||||
const collapsedWidth = 25;
|
||||
|
||||
const rightPanelInitialExpandedWidth = 280;
|
||||
const leftPanelInitialExpandedWidth = 292;
|
||||
const leftPanelInitialExpandedWidth = 282;
|
||||
|
||||
const panelGroupDefinition = {
|
||||
groupId: 'viewerLayoutResizablePanelGroup',
|
||||
|
||||
@@ -3,6 +3,12 @@ import { Types, DicomMetadataStore } from '@ohif/core';
|
||||
import { ContextMenuController } from './CustomizableContextMenu';
|
||||
import DicomTagBrowser from './DicomTagBrowser/DicomTagBrowser';
|
||||
import reuseCachedLayouts from './utils/reuseCachedLayouts';
|
||||
import {
|
||||
configureViewportForLayerAddition,
|
||||
configureViewportForLayerRemoval,
|
||||
canAddDisplaySetToViewport,
|
||||
DERIVED_OVERLAY_MODALITIES,
|
||||
} from './utils/layerConfigurationUtils';
|
||||
import findViewportsByPosition, {
|
||||
findOrCreateViewport as layoutFindOrCreate,
|
||||
} from './findViewportsByPosition';
|
||||
@@ -51,6 +57,137 @@ const commandsModule = ({
|
||||
const contextMenuController = new ContextMenuController(servicesManager, commandsManager);
|
||||
|
||||
const actions = {
|
||||
/**
|
||||
* Adds a display set as a layer to the specified viewport
|
||||
*
|
||||
* @param options.viewportId - The ID of the viewport to add the layer to
|
||||
* @param options.displaySetInstanceUID - The UID of the display set to add as a layer
|
||||
* @param options.removeFirst - Optional flag to remove the display set first if it's already added
|
||||
*/
|
||||
addDisplaySetAsLayer: ({ viewportId, displaySetInstanceUID, removeFirst = false }) => {
|
||||
if (!viewportId || !displaySetInstanceUID) {
|
||||
console.warn('Missing required parameters for addDisplaySetAsLayer command');
|
||||
return;
|
||||
}
|
||||
|
||||
const { displaySetService, viewportGridService, hangingProtocolService } =
|
||||
servicesManager.services;
|
||||
|
||||
// Get the display set
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
if (!displaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current display sets for the viewport
|
||||
const currentDisplaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
|
||||
// Check if we can add this display set to the viewport
|
||||
const canAdd = canAddDisplaySetToViewport({
|
||||
viewportId,
|
||||
displaySetInstanceUID,
|
||||
servicesManager,
|
||||
});
|
||||
|
||||
if (!canAdd) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the display set to the viewport
|
||||
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
viewportId,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
// Configure each viewport for layer addition
|
||||
updatedViewports.forEach(viewport => {
|
||||
configureViewportForLayerAddition({
|
||||
viewport,
|
||||
displaySetInstanceUID,
|
||||
currentDisplaySetUIDs,
|
||||
servicesManager,
|
||||
});
|
||||
});
|
||||
|
||||
// Update position presentation
|
||||
commandsManager.runCommand('updateStoredPositionPresentation', {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: updatedViewports[0].displaySetInstanceUIDs,
|
||||
});
|
||||
|
||||
// Run command to update viewports
|
||||
commandsManager.run('setDisplaySetsForViewports', {
|
||||
viewportsToUpdate: updatedViewports,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a display set layer from the specified viewport
|
||||
*
|
||||
* @param options.viewportId - The ID of the viewport to remove the layer from
|
||||
* @param options.displaySetInstanceUID - The UID of the display set to remove
|
||||
*/
|
||||
removeDisplaySetLayer: ({ viewportId, displaySetInstanceUID }) => {
|
||||
if (!viewportId || !displaySetInstanceUID) {
|
||||
console.warn('Missing required parameters for removeDisplaySetLayer command');
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
displaySetService,
|
||||
viewportGridService,
|
||||
hangingProtocolService,
|
||||
segmentationService,
|
||||
} = servicesManager.services;
|
||||
|
||||
// Get the display set
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
if (!displaySet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current display sets for the viewport
|
||||
const currentDisplaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
|
||||
// If the display set is not in the viewport, no need to remove it
|
||||
if (!currentDisplaySetUIDs.includes(displaySetInstanceUID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's a segmentation and handle accordingly
|
||||
const isSegmentation = DERIVED_OVERLAY_MODALITIES.includes(displaySet.Modality);
|
||||
if (isSegmentation) {
|
||||
segmentationService.removeSegmentationRepresentations(viewportId, {
|
||||
segmentationId: displaySetInstanceUID,
|
||||
});
|
||||
}
|
||||
|
||||
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
viewportId,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
// Configure each viewport for layer removal
|
||||
updatedViewports.forEach(viewport => {
|
||||
configureViewportForLayerRemoval({
|
||||
viewport,
|
||||
displaySetInstanceUID,
|
||||
currentDisplaySetUIDs,
|
||||
servicesManager,
|
||||
});
|
||||
});
|
||||
|
||||
// Update position presentation
|
||||
commandsManager.runCommand('updateStoredPositionPresentation', {
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: updatedViewports[0].displaySetInstanceUIDs,
|
||||
});
|
||||
|
||||
// Update the viewports
|
||||
commandsManager.run('setDisplaySetsForViewports', {
|
||||
viewportsToUpdate: updatedViewports,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Runs a command in multi-monitor mode. No-op if not multi-monitor.
|
||||
*/
|
||||
@@ -536,7 +673,11 @@ const commandsModule = ({
|
||||
const { activeViewportId, viewports } = viewportGridService.getState();
|
||||
|
||||
const activeViewport = viewports.get(activeViewportId);
|
||||
const activeDisplaySetInstanceUID = activeViewport.displaySetInstanceUIDs[0];
|
||||
const activeDisplaySetInstanceUID = activeViewport?.displaySetInstanceUIDs?.[0];
|
||||
|
||||
if (!activeDisplaySetInstanceUID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnailList = document.querySelector('#ohif-thumbnail-list');
|
||||
|
||||
@@ -544,24 +685,12 @@ const commandsModule = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnailListBounds = thumbnailList.getBoundingClientRect();
|
||||
|
||||
const thumbnail = document.querySelector(`#thumbnail-${activeDisplaySetInstanceUID}`);
|
||||
|
||||
if (!thumbnail) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnailBounds = thumbnail.getBoundingClientRect();
|
||||
|
||||
// This only handles a vertical thumbnail list.
|
||||
if (
|
||||
thumbnailBounds.top >= thumbnailListBounds.top &&
|
||||
thumbnailBounds.top <= thumbnailListBounds.bottom
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
thumbnail.scrollIntoView({ behavior: 'smooth' });
|
||||
},
|
||||
|
||||
@@ -651,6 +780,9 @@ const commandsModule = ({
|
||||
toggleOneUp: actions.toggleOneUp,
|
||||
openDICOMTagViewer: actions.openDICOMTagViewer,
|
||||
updateViewportDisplaySet: actions.updateViewportDisplaySet,
|
||||
scrollActiveThumbnailIntoView: actions.scrollActiveThumbnailIntoView,
|
||||
addDisplaySetAsLayer: actions.addDisplaySetAsLayer,
|
||||
removeDisplaySetLayer: actions.removeDisplaySetLayer,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { InputDialog, cn } from '@ohif/ui-next';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { InputDialog } from '@ohif/ui-next';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ohif/ui-next';
|
||||
import { useSystem } from '@ohif/core';
|
||||
|
||||
@@ -54,19 +54,19 @@ function ReportDialog({ dataSources, hide, onSave, onCancel }: ReportDialogProps
|
||||
setReportName(newReportName);
|
||||
}, [selectedSeries, seriesOptions]);
|
||||
|
||||
const handleSave = () => {
|
||||
const handleSave = useCallback(() => {
|
||||
onSave({
|
||||
reportName,
|
||||
dataSource: selectedDataSource,
|
||||
series: selectedSeries,
|
||||
});
|
||||
hide();
|
||||
};
|
||||
}, [selectedDataSource, selectedSeries, reportName, hide, onSave]);
|
||||
|
||||
const handleCancel = () => {
|
||||
const handleCancel = useCallback(() => {
|
||||
onCancel();
|
||||
hide();
|
||||
};
|
||||
}, [onCancel, hide]);
|
||||
|
||||
const showDataSourceSelect = dataSources?.length > 1;
|
||||
|
||||
|
||||
@@ -14,6 +14,26 @@ export default {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'addAsLayer',
|
||||
label: 'Add as Layer',
|
||||
iconName: 'ViewportViews',
|
||||
onClick: ({ commandsManager, displaySetInstanceUID, servicesManager }: withAppTypes) => {
|
||||
const { viewportGridService } = servicesManager.services;
|
||||
|
||||
// Get the active viewport
|
||||
const { activeViewportId } = viewportGridService.getState();
|
||||
if (!activeViewportId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the new command to add the display set as a layer
|
||||
commandsManager.runCommand('addDisplaySetAsLayer', {
|
||||
viewportId: activeViewportId,
|
||||
displaySetInstanceUID,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
'studyBrowser.sortFunctions': [
|
||||
{
|
||||
|
||||
@@ -6,9 +6,21 @@ import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core';
|
||||
export default function getDisplaySetsFromUnsupportedSeries(instances) {
|
||||
const imageSet = new ImageSet(instances);
|
||||
const messages = new DisplaySetMessageList();
|
||||
messages.addMessage(DisplaySetMessage.CODES.UNSUPPORTED_DISPLAYSET);
|
||||
const instance = instances[0];
|
||||
|
||||
if (!instances.length) {
|
||||
messages.addMessage(DisplaySetMessage.CODES.NO_VALID_INSTANCES);
|
||||
} else {
|
||||
const sopClassUid = instance.SOPClassUID;
|
||||
if (sopClassUid) {
|
||||
messages.addMessage(DisplaySetMessage.CODES.UNSUPPORTED_SOP_CLASS_UID, {
|
||||
sopClassUid,
|
||||
});
|
||||
} else {
|
||||
messages.addMessage(DisplaySetMessage.CODES.MISSING_SOP_CLASS_UID);
|
||||
}
|
||||
}
|
||||
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
|
||||
SeriesDate: instance.SeriesDate,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { utils, classes } from '@ohif/core';
|
||||
import i18n from '@ohif/i18n';
|
||||
import { id } from './id';
|
||||
import getDisplaySetMessages from './getDisplaySetMessages';
|
||||
import getDisplaySetsFromUnsupportedSeries from './getDisplaySetsFromUnsupportedSeries';
|
||||
@@ -99,6 +100,11 @@ const makeDisplaySet = instances => {
|
||||
averageSpacingBetweenFrames: averageSpacingBetweenFrames || null,
|
||||
isDynamicVolume,
|
||||
dynamicVolumeInfo,
|
||||
supportsWindowLevel: true,
|
||||
label:
|
||||
instance.SeriesDescription ||
|
||||
`${i18n.t('Series')} ${instance.SeriesNumber} - ${i18n.t(instance.Modality)}`,
|
||||
FrameOfReferenceUID: instance.FrameOfReferenceUID,
|
||||
});
|
||||
|
||||
// Sort the images in this series if needed
|
||||
@@ -260,6 +266,7 @@ const sopClassUids = [
|
||||
sopClassDictionary.LegacyConvertedEnhancedPETImageStorage,
|
||||
sopClassDictionary.RTImageStorage,
|
||||
sopClassDictionary.EnhancedUSVolumeStorage,
|
||||
sopClassDictionary.RTDoseStorage,
|
||||
];
|
||||
|
||||
function getSopClassHandlerModule(appContextParam) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ToolbarButton as ToolbarButtonLegacy } from '@ohif/ui';
|
||||
import { ToolButton, utils } from '@ohif/ui-next';
|
||||
|
||||
import ToolbarLayoutSelectorWithServices from './Toolbar/ToolbarLayoutSelector';
|
||||
@@ -33,28 +32,6 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
|
||||
name: 'ohif.toolBoxButton',
|
||||
defaultComponent: ToolBoxButtonWrapper,
|
||||
},
|
||||
// legacy will get removed in the future
|
||||
// legacy will get removed in the future
|
||||
// legacy will get removed in the future
|
||||
// legacy will get removed in the future
|
||||
// legacy will get removed in the future
|
||||
// legacy will get removed in the future
|
||||
{
|
||||
name: 'ohif.radioGroup',
|
||||
defaultComponent: ToolbarButtonLegacy,
|
||||
},
|
||||
{
|
||||
name: 'ohif.buttonGroup',
|
||||
defaultComponent: ToolbarButtonGroupWithServicesLegacy,
|
||||
},
|
||||
{
|
||||
name: 'ohif.divider',
|
||||
defaultComponent: ToolbarDividerLegacy,
|
||||
},
|
||||
{
|
||||
name: 'ohif.splitButton',
|
||||
defaultComponent: ToolbarSplitButtonWithServicesLegacy,
|
||||
},
|
||||
// others
|
||||
{
|
||||
name: 'ohif.layoutSelector',
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import { CommandsManager, ExtensionManager } from '@ohif/core';
|
||||
import LineChartViewport from './Components/LineChartViewport/index';
|
||||
|
||||
const getViewportModule = ({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
}: {
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
commandsManager: CommandsManager;
|
||||
extensionManager: ExtensionManager;
|
||||
}) => {
|
||||
const getViewportModule = () => {
|
||||
return [
|
||||
{
|
||||
name: 'chartViewport',
|
||||
|
||||
@@ -22,11 +22,11 @@ export default function init({
|
||||
cineService.EVENTS.CINE_STATE_CHANGED,
|
||||
]);
|
||||
// Add
|
||||
DicomMetadataStore.subscribe(DicomMetadataStore.EVENTS.INSTANCES_ADDED, handlePETImageMetadata);
|
||||
DicomMetadataStore.subscribe(DicomMetadataStore.EVENTS.INSTANCES_ADDED, handleScalingModules);
|
||||
|
||||
// If the metadata for PET has changed by the user (e.g. manually changing the PatientWeight)
|
||||
// we need to recalculate the SUV Scaling Factors
|
||||
DicomMetadataStore.subscribe(DicomMetadataStore.EVENTS.SERIES_UPDATED, handlePETImageMetadata);
|
||||
DicomMetadataStore.subscribe(DicomMetadataStore.EVENTS.SERIES_UPDATED, handleScalingModules);
|
||||
|
||||
// Adds extra custom attributes for use by hanging protocols
|
||||
registerHangingProtocolAttributes({ servicesManager });
|
||||
@@ -71,7 +71,7 @@ export default function init({
|
||||
});
|
||||
}
|
||||
|
||||
const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => {
|
||||
const handleScalingModules = ({ SeriesInstanceUID, StudyInstanceUID }) => {
|
||||
const { instances } = DicomMetadataStore.getSeries(StudyInstanceUID, SeriesInstanceUID);
|
||||
|
||||
if (!instances?.length) {
|
||||
@@ -80,12 +80,41 @@ const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => {
|
||||
|
||||
const modality = instances[0].Modality;
|
||||
|
||||
if (!modality || modality !== 'PT') {
|
||||
const allowedModality = ['PT', 'RTDOSE'];
|
||||
|
||||
if (!allowedModality.includes(modality)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageIds = instances.map(instance => instance.imageId);
|
||||
const instanceMetadataArray = [];
|
||||
|
||||
if (modality === 'RTDOSE') {
|
||||
const DoseGridScaling = instances[0].DoseGridScaling;
|
||||
const DoseSummation = instances[0].DoseSummation;
|
||||
const DoseType = instances[0].DoseType;
|
||||
const DoseUnit = instances[0].DoseUnit;
|
||||
const NumberOfFrames = instances[0].NumberOfFrames;
|
||||
const imageId = imageIds[0];
|
||||
|
||||
// add scaling module to the metadata
|
||||
// since RTDOSE is always a multiframe we should add the scaling module to each frame
|
||||
for (let i = 0; i < NumberOfFrames; i++) {
|
||||
const frameIndex = i + 1;
|
||||
|
||||
// Todo: we should support other things like wadouri, local etc
|
||||
const newImageId = `${imageId.replace(/\/frames\/\d+$/, '')}/frames/${frameIndex}`;
|
||||
metadataProvider.addCustomMetadata(newImageId, 'scalingModule', {
|
||||
DoseGridScaling,
|
||||
DoseSummation,
|
||||
DoseType,
|
||||
DoseUnit,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// try except block to prevent errors when the metadata is not correct
|
||||
try {
|
||||
imageIds.forEach(imageId => {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
export const DERIVED_OVERLAY_MODALITIES = ['SEG', 'RTSTRUCT'];
|
||||
export const DEFAULT_COLORMAP = 'hsv';
|
||||
export const DEFAULT_OPACITY = 0.9;
|
||||
export const DEFAULT_OPACITY_PERCENT = DEFAULT_OPACITY * 100;
|
||||
|
||||
/**
|
||||
* Get modality-specific color and opacity settings from the customization service
|
||||
*/
|
||||
export function getModalityOverlayColormap(customizationService, modality) {
|
||||
const modalityOverlayDefaultColorMaps = customizationService?.getCustomization(
|
||||
'cornerstone.modalityOverlayDefaultColorMaps'
|
||||
) || { defaultSettings: {} };
|
||||
|
||||
return (
|
||||
modalityOverlayDefaultColorMaps.defaultSettings[modality] || {
|
||||
colormap: DEFAULT_COLORMAP,
|
||||
opacity: DEFAULT_OPACITY,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create display set options based on modality and opacity settings
|
||||
*/
|
||||
export function createColormapOverlayDisplaySetOptions(displaySet, opacity, customizationService) {
|
||||
if (displaySet.Modality === 'SEG') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const modalitySettings = getModalityOverlayColormap(customizationService, displaySet.Modality);
|
||||
return {
|
||||
colormap: {
|
||||
name: modalitySettings.colormap || DEFAULT_COLORMAP,
|
||||
opacity: opacity / 100, // Convert from percentage to 0-1 range
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure viewport for adding a display set layer
|
||||
*/
|
||||
export function configureViewportForLayerAddition(params: {
|
||||
viewport: any;
|
||||
displaySetInstanceUID: string;
|
||||
currentDisplaySetUIDs: string[];
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
}): any {
|
||||
const { viewport, displaySetInstanceUID, currentDisplaySetUIDs, servicesManager } = params;
|
||||
const { cornerstoneViewportService, displaySetService, customizationService } =
|
||||
servicesManager.services;
|
||||
|
||||
const { viewportId } = viewport;
|
||||
|
||||
// Set the display set UIDs for the viewport
|
||||
const allDisplaySetInstanceUIDs = [...currentDisplaySetUIDs, displaySetInstanceUID];
|
||||
viewport.displaySetInstanceUIDs = allDisplaySetInstanceUIDs;
|
||||
|
||||
if (!viewport.viewportOptions) {
|
||||
viewport.viewportOptions = {};
|
||||
}
|
||||
|
||||
const requestedLayerDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
if (!viewport.viewportOptions.orientation) {
|
||||
viewport.viewportOptions.orientation = cornerstoneViewportService.getOrientation(viewportId);
|
||||
}
|
||||
|
||||
// Do not force volume for SEG and RTSTRUCT if there is only one display set
|
||||
if (requestedLayerDisplaySet.isOverlayDisplaySet && currentDisplaySetUIDs.length === 1) {
|
||||
viewport.viewportOptions.viewportType = 'stack';
|
||||
} else {
|
||||
viewport.viewportOptions.viewportType = 'volume';
|
||||
}
|
||||
|
||||
// create same amount of display set options as the number of display set UIDs
|
||||
const displaySetOptions = allDisplaySetInstanceUIDs.map((uid, index) => {
|
||||
if (index === 0) {
|
||||
// no colormap for background
|
||||
return {};
|
||||
}
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetByUID(uid);
|
||||
return createColormapOverlayDisplaySetOptions(displaySet, 90, customizationService);
|
||||
});
|
||||
|
||||
viewport.displaySetOptions = displaySetOptions;
|
||||
|
||||
return viewport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure viewport for removing a display set layer
|
||||
*/
|
||||
export function configureViewportForLayerRemoval(params: {
|
||||
viewport: any;
|
||||
displaySetInstanceUID: string;
|
||||
currentDisplaySetUIDs: string[];
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
}): any {
|
||||
const { viewport, displaySetInstanceUID, currentDisplaySetUIDs, servicesManager } = params;
|
||||
const { cornerstoneViewportService, displaySetService } = servicesManager.services;
|
||||
|
||||
const { viewportId } = viewport;
|
||||
|
||||
// Filter out the display set to remove
|
||||
viewport.displaySetInstanceUIDs = currentDisplaySetUIDs.filter(
|
||||
uid => uid !== displaySetInstanceUID
|
||||
);
|
||||
|
||||
if (!viewport.viewportOptions) {
|
||||
viewport.viewportOptions = {};
|
||||
}
|
||||
|
||||
viewport.viewportOptions.viewportType = 'volume';
|
||||
|
||||
// orientation
|
||||
if (!viewport.viewportOptions.orientation) {
|
||||
viewport.viewportOptions.orientation = cornerstoneViewportService.getOrientation(viewportId);
|
||||
}
|
||||
|
||||
// Recreate the display set options
|
||||
viewport.displaySetOptions = viewport.displaySetInstanceUIDs.map(() => {
|
||||
// For simplicity, we're returning empty options for now
|
||||
// In a more complete implementation, we would need to preserve existing display set options
|
||||
return {};
|
||||
});
|
||||
|
||||
return viewport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a display set can be added as a layer to the specified viewport
|
||||
*/
|
||||
export function canAddDisplaySetToViewport(params: {
|
||||
viewportId: string;
|
||||
displaySetInstanceUID: string;
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
}): boolean {
|
||||
const { viewportId, displaySetInstanceUID, servicesManager } = params;
|
||||
const { displaySetService, viewportGridService } = servicesManager.services;
|
||||
|
||||
// Check if the display set exists
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
if (!displaySet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get current display sets in the viewport
|
||||
const currentDisplaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
|
||||
|
||||
// Check if the display set is already in the viewport
|
||||
if (currentDisplaySetUIDs.includes(displaySetInstanceUID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Loaded 100 of 766 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user