Compare commits
207
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7b53be671 | ||
|
|
7e55d5b30e | ||
|
|
5d4f2e2a8e | ||
|
|
b19d7af9ff | ||
|
|
65c3719d1b | ||
|
|
f9fdaee3b2 | ||
|
|
e5286d9fc2 | ||
|
|
71f06dcd3e | ||
|
|
3b66867508 | ||
|
|
1d267e5576 | ||
|
|
610faa5a27 | ||
|
|
4f8281b04a | ||
|
|
2f7bb02387 | ||
|
|
54d4eabf03 | ||
|
|
5c20bf0faf | ||
|
|
b417063200 | ||
|
|
4bd343ab3e | ||
|
|
a6de628132 | ||
|
|
f4775eccae | ||
|
|
bf8fe7799c | ||
|
|
62b2748847 | ||
|
|
4a125236dd | ||
|
|
a5f03764d1 | ||
|
|
1801a217e3 | ||
|
|
23a2582231 | ||
|
|
1105db5dd1 | ||
|
|
05d41c5206 | ||
|
|
61e3a9cb52 | ||
|
|
4b69279c70 | ||
|
|
8fb8b3bfd1 | ||
|
|
c1a196d802 | ||
|
|
4a564528ba | ||
|
|
aa2b649444 | ||
|
|
3f7ae76113 | ||
|
|
43ad641cf1 | ||
|
|
c80cd6102d | ||
|
|
3d11ef28f2 | ||
|
|
69a81ae838 | ||
|
|
417785ecbc | ||
|
|
7ce7127d7c | ||
|
|
8981a61b8c | ||
|
|
c66c688ace | ||
|
|
d9450104af | ||
|
|
2835a555c8 | ||
|
|
41e8ee692b | ||
|
|
4f20523109 | ||
|
|
0166956dd8 | ||
|
|
1aa2c646a6 | ||
|
|
2e96371b06 | ||
|
|
543c31109b | ||
|
|
2d1704063c | ||
|
|
0610425740 | ||
|
|
ce9ff7d535 | ||
|
|
b4918d8a33 | ||
|
|
a7fd9fa5bf | ||
|
|
1d0259451f | ||
|
|
abf2093d30 | ||
|
|
8fc395e68f | ||
|
|
33af9bb021 | ||
|
|
8dab0883c8 | ||
|
|
a146c9a50e | ||
|
|
2ec4692eaf | ||
|
|
03aad4eba2 | ||
|
|
f14aee2733 | ||
|
|
57eb829173 | ||
|
|
ace67b3bbb | ||
|
|
307b14476b | ||
|
|
433cc8089d | ||
|
|
fa2435d5e9 | ||
|
|
e8f9345b93 | ||
|
|
e254e7bb3d | ||
|
|
7353f7f069 | ||
|
|
3dd0666c0c | ||
|
|
d5bcf54e23 | ||
|
|
ac1cad25af | ||
|
|
e0fca8c88c | ||
|
|
43f54aa49c | ||
|
|
9b2e98a760 | ||
|
|
465df5c573 | ||
|
|
9125cec333 | ||
|
|
87d623a76e | ||
|
|
e849199eb0 | ||
|
|
0495020389 | ||
|
|
98ab95a743 | ||
|
|
ec685ef5b9 | ||
|
|
70eb2aa6d7 | ||
|
|
a1597d7bd6 | ||
|
|
8f0035aeef | ||
|
|
c8923c5499 | ||
|
|
c0c4f2398c | ||
|
|
60a8d5154a | ||
|
|
efc554bd49 | ||
|
|
fc5f73bdfd | ||
|
|
579a592812 | ||
|
|
ea961d6b66 | ||
|
|
69a620ac68 | ||
|
|
8eaebd015d | ||
|
|
0326496c47 | ||
|
|
327f2cb1cf | ||
|
|
478def8447 | ||
|
|
0a9cbb7589 | ||
|
|
c5752e756a | ||
|
|
3b20d2b816 | ||
|
|
0d4cb69ec3 | ||
|
|
4f18a21868 | ||
|
|
c4299de984 | ||
|
|
a11cd6d6cb | ||
|
|
a72192da28 | ||
|
|
0dca891ff3 | ||
|
|
b4f08adfb6 | ||
|
|
4d87e4dbda | ||
|
|
6a02f0b41c | ||
|
|
43efed207e | ||
|
|
4f2583483c | ||
|
|
e3f7838ce0 | ||
|
|
08d329add4 | ||
|
|
579c0c9d01 | ||
|
|
ea7a3e07cb | ||
|
|
e91811ff23 | ||
|
|
dd8e69a8cf | ||
|
|
b1b66076b9 | ||
|
|
80b1f54af1 | ||
|
|
75fde87b59 | ||
|
|
64f2f50f63 | ||
|
|
7f17d5d733 | ||
|
|
9ff4ece541 | ||
|
|
e534802abb | ||
|
|
51a87028c1 | ||
|
|
eba85aa5b3 | ||
|
|
331778dfef | ||
|
|
daa5a857e4 | ||
|
|
cb1f3b877b | ||
|
|
f748d2a73a | ||
|
|
365d824b98 | ||
|
|
ee8962e9a7 | ||
|
|
cde457c626 | ||
|
|
5ef102681d | ||
|
|
d55e794a86 | ||
|
|
4f7fa35def | ||
|
|
1f09527ae6 | ||
|
|
118922f365 | ||
|
|
926f549e91 | ||
|
|
0d900bf4ad | ||
|
|
1bfce0a03c | ||
|
|
c08debd65a | ||
|
|
ab5cead2af | ||
|
|
7a96406a11 | ||
|
|
4432b6b420 | ||
|
|
7670941256 | ||
|
|
e1feffa425 | ||
|
|
3b47e6971c | ||
|
|
13ea778e61 | ||
|
|
14498d4e9a | ||
|
|
83f3bf4ec1 | ||
|
|
3b96c9e771 | ||
|
|
da8bdb94a9 | ||
|
|
1f2c7c4c57 | ||
|
|
5c7a8d0c56 | ||
|
|
b2c0faf29d | ||
|
|
4068488bb2 | ||
|
|
acc6a217c9 | ||
|
|
7599ec9421 | ||
|
|
b12e0211d4 | ||
|
|
689008eb82 | ||
|
|
7377db8d28 | ||
|
|
a2be64f899 | ||
|
|
5a2e88455a | ||
|
|
ab6e341731 | ||
|
|
45f3a2d84d | ||
|
|
00f78adb1f | ||
|
|
8e7cc11529 | ||
|
|
c21d4848ef | ||
|
|
1de19a07c1 | ||
|
|
9b10303a2e | ||
|
|
c6d3990341 | ||
|
|
c42cd90c1d | ||
|
|
90364189b8 | ||
|
|
00707db1ad | ||
|
|
688d896f3e | ||
|
|
72bef63ef6 | ||
|
|
50669867c9 | ||
|
|
90d974b90c | ||
|
|
aa0e5a5937 | ||
|
|
dd2648a080 | ||
|
|
0dbf85a3d5 | ||
|
|
d402ded8c3 | ||
|
|
b1514b9a3e | ||
|
|
bbfa3c1a1f | ||
|
|
ba8af0f35b | ||
|
|
5755ce5430 | ||
|
|
a7cba1a3cf | ||
|
|
16b7aa4f65 | ||
|
|
9f0828ae27 | ||
|
|
8903e3c3dd | ||
|
|
6231629b38 | ||
|
|
b9eb54c71f | ||
|
|
2656beef0e | ||
|
|
fc0064fd9d | ||
|
|
9f3fc382c3 | ||
|
|
640200ba7e | ||
|
|
246ebab6eb | ||
|
|
58da0324cd | ||
|
|
c9a2496aab | ||
|
|
4368eb3896 | ||
|
|
fc9b521fa9 | ||
|
|
dcd1e4eb28 | ||
|
|
0968b3abb1 |
No files matched your search
@@ -180,10 +180,6 @@ jobs:
|
||||
- run:
|
||||
name: Authenticate with NPM registry
|
||||
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||
- run:
|
||||
name: Increase the event emitter limit
|
||||
command: |
|
||||
node ./increaseEventEmitterLimit.mjs
|
||||
- run:
|
||||
name: build half of the packages (to avoid out of memory in circleci)
|
||||
command: |
|
||||
@@ -230,10 +226,6 @@ jobs:
|
||||
- run:
|
||||
name: Authenticate with NPM registry
|
||||
command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
|
||||
- run:
|
||||
name: Increase the event emitter limit
|
||||
command: |
|
||||
node ./increaseEventEmitterLimit.mjs
|
||||
- run:
|
||||
name: build half of the packages (to avoid out of memory in circleci)
|
||||
command: |
|
||||
@@ -385,7 +377,7 @@ jobs:
|
||||
- yarn-berry
|
||||
type: enum
|
||||
parallelism:
|
||||
default: 1
|
||||
default: 4
|
||||
description: |
|
||||
Number of Circle machines to use for load balancing, min 1
|
||||
(requires `parallel` and `record` flags in your `cypress-command`)
|
||||
|
||||
@@ -5,8 +5,6 @@ server {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cross-Origin-Opener-Policy same-origin;
|
||||
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||
add_header Cross-Origin-Resource-Policy same-origin;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
@@ -7,8 +7,6 @@ server {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cross-Origin-Opener-Policy same-origin;
|
||||
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||
add_header Cross-Origin-Resource-Policy same-origin;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
@@ -8,6 +8,8 @@ jobs:
|
||||
playwright-tests:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.49.0-noble
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -23,7 +25,9 @@ jobs:
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: export NODE_OPTIONS="--max_old_space_size=8192" && npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
run:
|
||||
export NODE_OPTIONS="--max_old_space_size=8192" && npx playwright test --shard=${{
|
||||
matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
@@ -4,26 +4,34 @@
|
||||
* The JSON file can be used to load the study into the OHIF Viewer. You can get more detail
|
||||
* in the DICOM JSON Data source on docs.ohif.org
|
||||
*
|
||||
* Usage: node dicomStudyToJSONLaunch.js <studyFolder> <urlPrefix> <outputJSONPath>
|
||||
* Usage: node dicom-json-generator.js <studyFolder> <urlPrefix> <outputJSONPath> <optional scheme>
|
||||
*
|
||||
* params:
|
||||
* - studyFolder: path to the study folder
|
||||
* - studyFolder: path to the study folder which contains the DICOM files
|
||||
* - urlPrefix: prefix to the url that will be used to load the study into the viewer. For instance
|
||||
* we use https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data as the urlPrefix for the
|
||||
* example since the data is hosted on S3 and each study is in a folder. So the url in the generated
|
||||
* json file for the first instance of the first series of the first study will be
|
||||
* dicomweb:https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data/Series1/Instance1
|
||||
*
|
||||
* as you see the dicomweb is a prefix that is used to load the data into the viewer, which is suited when
|
||||
* the .dcm file is hosted statically and can be accessed via a URL (like our example above)
|
||||
* However, you can specify a new scheme bellow.
|
||||
*
|
||||
* - outputJSONPath: path to the output JSON file
|
||||
* - scheme: default dicomweb if not provided
|
||||
*/
|
||||
const dcmjs = require('dcmjs');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const [studyDirectory, urlPrefix, outputPath] = args;
|
||||
const [studyDirectory, urlPrefix, outputPath, scheme = 'dicomweb'] = args;
|
||||
|
||||
if (args.length !== 3) {
|
||||
console.error('Usage: node dicomStudyToJSONLaunch.js <studyFolder> <urlPrefix> <outputJSONPath>');
|
||||
if (args.length < 3 || args.length > 4) {
|
||||
console.error(
|
||||
'Usage: node dicom-json-generator.js <studyFolder> <urlPrefix> <outputJSONPath> [scheme]'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -31,7 +39,7 @@ const model = {
|
||||
studies: [],
|
||||
};
|
||||
|
||||
async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath) {
|
||||
async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath, scheme) {
|
||||
try {
|
||||
const files = await recursiveReadDir(studyDirectory);
|
||||
console.debug('Processing...');
|
||||
@@ -42,7 +50,7 @@ async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath) {
|
||||
const dicomDict = dcmjs.data.DicomMessage.readFile(arrayBuffer.buffer);
|
||||
const instance = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomDict.dict);
|
||||
|
||||
instance.fileLocation = createImageId(file, urlPrefix, studyDirectory);
|
||||
instance.fileLocation = createImageId(file, urlPrefix, studyDirectory, scheme);
|
||||
processInstance(instance);
|
||||
}
|
||||
}
|
||||
@@ -77,10 +85,10 @@ async function recursiveReadDir(dir) {
|
||||
return results;
|
||||
}
|
||||
|
||||
function createImageId(fileLocation, urlPrefix, studyDirectory) {
|
||||
function createImageId(fileLocation, urlPrefix, studyDirectory, scheme) {
|
||||
const relativePath = path.relative(studyDirectory, fileLocation);
|
||||
const normalizedPath = path.normalize(relativePath).replace(/\\/g, '/');
|
||||
return `dicomweb:${urlPrefix}${normalizedPath}`;
|
||||
return `${scheme}:${urlPrefix}${normalizedPath}`;
|
||||
}
|
||||
|
||||
function processInstance(instance) {
|
||||
@@ -262,4 +270,4 @@ function createInstanceMetaDataMultiFrame(instance) {
|
||||
return instances;
|
||||
}
|
||||
|
||||
convertDICOMToJSON(studyDirectory, urlPrefix, outputPath);
|
||||
convertDICOMToJSON(studyDirectory, urlPrefix, outputPath, scheme);
|
||||
Vendored
+1
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"mikestead.dotenv",
|
||||
"bungcip.better-toml",
|
||||
|
||||
Vendored
+90
-12
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.rulers": [80, 120],
|
||||
// ===
|
||||
// Spacing
|
||||
@@ -14,23 +15,100 @@
|
||||
// Event Triggers
|
||||
// ===
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.autoFixOnSave": true,
|
||||
"eslint.run": "onSave",
|
||||
"eslint.validate": [
|
||||
{
|
||||
"language": "javascript",
|
||||
"autoFix": true
|
||||
},
|
||||
{
|
||||
"language": "javascriptreact",
|
||||
"autoFix": true
|
||||
}
|
||||
],
|
||||
"jest.autoRun": "off",
|
||||
"prettier.disableLanguages": ["html"],
|
||||
"prettier.endOfLine": "lf",
|
||||
"workbench.colorCustomizations": {},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
},
|
||||
"cSpell.userWords": [
|
||||
"aabb",
|
||||
"architectured",
|
||||
"attrname",
|
||||
"Barksy",
|
||||
"browserslist",
|
||||
"bulkdata",
|
||||
"Cacheable",
|
||||
"cfun",
|
||||
"clonedeep",
|
||||
"Colormap",
|
||||
"Colormaps",
|
||||
"Comlink",
|
||||
"cornerstonejs",
|
||||
"Crosshairs",
|
||||
"datasource",
|
||||
"dcmjs",
|
||||
"decache",
|
||||
"decached",
|
||||
"decaching",
|
||||
"deepmerge",
|
||||
"Dicom",
|
||||
"dicomweb",
|
||||
"DISPLAYSETS",
|
||||
"glwindow",
|
||||
"grababble",
|
||||
"grabbable",
|
||||
"Hounsfield",
|
||||
"Interactable",
|
||||
"Interactor",
|
||||
"istyle",
|
||||
"kitware",
|
||||
"labelmap",
|
||||
"labelmaps",
|
||||
"livewire",
|
||||
"Mergeable",
|
||||
"multiframe",
|
||||
"nifti",
|
||||
"ofun",
|
||||
"OHIF",
|
||||
"polylines",
|
||||
"POLYSEG",
|
||||
"prapogation",
|
||||
"precisionmetrics",
|
||||
"prefetch",
|
||||
"Prescaled",
|
||||
"pydicom",
|
||||
"Radiopharmaceutical",
|
||||
"rasterizing",
|
||||
"reconstructable",
|
||||
"Rehydratable",
|
||||
"renderable",
|
||||
"resampler",
|
||||
"resemblejs",
|
||||
"reslice",
|
||||
"resliced",
|
||||
"Reslices",
|
||||
"roadmap",
|
||||
"ROADMAPS",
|
||||
"Segmentations",
|
||||
"semibold",
|
||||
"sitk",
|
||||
"SUBRESOLUTION",
|
||||
"suvbsa",
|
||||
"suvbw",
|
||||
"suvlbm",
|
||||
"textbox",
|
||||
"thresholded",
|
||||
"thresholding",
|
||||
"timepoint",
|
||||
"timepoints",
|
||||
"TMTV",
|
||||
"TOOLGROUP",
|
||||
"tqdm",
|
||||
"transferables",
|
||||
"typedoc",
|
||||
"unsubscriptions",
|
||||
"uuidv",
|
||||
"viewplane",
|
||||
"viewports",
|
||||
"Voxel",
|
||||
"Voxels",
|
||||
"Vtkjs",
|
||||
"wado",
|
||||
"wadors",
|
||||
"wadouri",
|
||||
"workerpool"
|
||||
]
|
||||
}
|
||||
+21
-26
@@ -14,14 +14,14 @@ const TerserJSPlugin = require('terser-webpack-plugin');
|
||||
// const vtkRules = require('vtk.js/Utilities/config/dependency.js').webpack.core
|
||||
// .rules;
|
||||
// ~~ RULES
|
||||
const loadShadersRule = require('./rules/loadShaders.js');
|
||||
// const loadShadersRule = require('./rules/loadShaders.js');
|
||||
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
|
||||
const transpileJavaScriptRule = require('./rules/transpileJavaScript.js');
|
||||
const cssToJavaScript = require('./rules/cssToJavaScript.js');
|
||||
const stylusToJavaScript = require('./rules/stylusToJavaScript.js');
|
||||
// Only uncomment for old v2 stylus
|
||||
// const stylusToJavaScript = require('./rules/stylusToJavaScript.js');
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
|
||||
|
||||
// ~~ ENV VARS
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
const QUICK_BUILD = process.env.QUICK_BUILD;
|
||||
@@ -57,10 +57,6 @@ if (!process.env.APP_CONFIG) {
|
||||
}
|
||||
|
||||
module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||
if (!process.env.NODE_ENV) {
|
||||
throw new Error('process.env.NODE_ENV not set');
|
||||
}
|
||||
|
||||
const mode = NODE_ENV === 'production' ? 'production' : 'development';
|
||||
const isProdBuild = NODE_ENV === 'production';
|
||||
const isQuickBuild = QUICK_BUILD === 'true';
|
||||
@@ -98,16 +94,20 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||
type: 'filesystem',
|
||||
},
|
||||
module: {
|
||||
noParse: [/(codec)/, /(dicomicc)/],
|
||||
noParse: [/(dicomicc)/],
|
||||
rules: [
|
||||
...(isProdBuild ? [] : [{
|
||||
test: /\.[jt]sx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: ['react-refresh/babel'],
|
||||
},
|
||||
}]),
|
||||
...(isProdBuild
|
||||
? []
|
||||
: [
|
||||
{
|
||||
test: /\.[jt]sx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: isProdBuild ? [] : ['react-refresh/babel'],
|
||||
},
|
||||
},
|
||||
]),
|
||||
{
|
||||
test: /\.svg?$/,
|
||||
oneOf: [
|
||||
@@ -122,11 +122,11 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||
name: 'preset-default',
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false
|
||||
removeViewBox: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
prettier: false,
|
||||
svgo: true,
|
||||
@@ -140,11 +140,6 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
enforce: 'pre',
|
||||
use: 'source-map-loader',
|
||||
},
|
||||
transpileJavaScriptRule(mode),
|
||||
loadWebWorkersRule,
|
||||
// loadShadersRule,
|
||||
@@ -184,8 +179,8 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||
'@hooks': path.resolve(__dirname, '../platform/app/src/hooks'),
|
||||
'@routes': path.resolve(__dirname, '../platform/app/src/routes'),
|
||||
'@state': path.resolve(__dirname, '../platform/app/src/state'),
|
||||
'@cornerstonejs/dicom-image-loader':
|
||||
'@cornerstonejs/dicom-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js',
|
||||
'dicom-microscopy-viewer':
|
||||
'dicom-microscopy-viewer/dist/dynamic-import/dicomMicroscopyViewer.min.js',
|
||||
},
|
||||
// Which directories to search when resolving modules
|
||||
modules: [
|
||||
@@ -213,7 +208,7 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
...(isProdBuild ? [] : [new ReactRefreshWebpackPlugin()]),
|
||||
...(isProdBuild ? [] : [new ReactRefreshWebpackPlugin({ overlay: false })]),
|
||||
// Uncomment to generate bundle analyzer
|
||||
// new BundleAnalyzerPlugin(),
|
||||
],
|
||||
|
||||
+554
@@ -3,6 +3,560 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **header tools in volume3D:** All header tools are enabled in volume3D viewport ([#4681](https://github.com/OHIF/Viewers/issues/4681)) ([b19d7af](https://github.com/OHIF/Viewers/commit/b19d7af9ff81beedbcb8f6198ef21783f94782fa))
|
||||
* OHIF 3.9 Recovery 3 ([#4697](https://github.com/OHIF/Viewers/issues/4697)) ([5d4f2e2](https://github.com/OHIF/Viewers/commit/5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modes:** don't attempt to retrieve a stage index if HPs ([#4552](https://github.com/OHIF/Viewers/issues/4552)) ([e5286d9](https://github.com/OHIF/Viewers/commit/e5286d9fc2449da50b738e7e6208d489cce1633e))
|
||||
* **multiframe:** metadata handling of NM studies and loading order ([#4540](https://github.com/OHIF/Viewers/issues/4540)) ([71f06dc](https://github.com/OHIF/Viewers/commit/71f06dcd3e6d24f1e3d931f7db756c26038927da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* publish version 3.9 ([#4491](https://github.com/OHIF/Viewers/issues/4491)) ([2f7bb02](https://github.com/OHIF/Viewers/commit/2f7bb02387a19e953e5f09aad06b72d96cffcbd1))
|
||||
* **ui:** Scrolling and display fixes for study panel ([#4494](https://github.com/OHIF/Viewers/issues/4494)) ([4f8281b](https://github.com/OHIF/Viewers/commit/4f8281b04a1eae6890e7ecd94f8a61c281217d3f))
|
||||
* video playback ([#4497](https://github.com/OHIF/Viewers/issues/4497)) ([610faa5](https://github.com/OHIF/Viewers/commit/610faa5a2738da5eabc40e57e338c359f481e852))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Have an addIcon that adds to both ui and ui-next ([#4490](https://github.com/OHIF/Viewers/issues/4490)) ([4a12523](https://github.com/OHIF/Viewers/commit/4a125236ddbf8a4a95fb9c5820f511d0224e663f))
|
||||
* Measurement Tracking: Various UI and functionality improvements ([#4481](https://github.com/OHIF/Viewers/issues/4481)) ([62b2748](https://github.com/OHIF/Viewers/commit/62b27488471c9d5979142e2d15872a85778b90ed))
|
||||
* **styles:** several fixes for different styles ([#4483](https://github.com/OHIF/Viewers/issues/4483)) ([a5f0376](https://github.com/OHIF/Viewers/commit/a5f03764d1fe07b55635c52c5dac38ab5e694ba1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bugs:** Update dependencies and enhance UI components ([#4478](https://github.com/OHIF/Viewers/issues/4478)) ([05d41c5](https://github.com/OHIF/Viewers/commit/05d41c52068a3b7ba249f15ecdf71838c352fd30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **style:** worklist shifting ([#4477](https://github.com/OHIF/Viewers/issues/4477)) ([8fb8b3b](https://github.com/OHIF/Viewers/commit/8fb8b3bfd1c887cd67fc058629d7aba598c76f9e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tmtv:** fix toggle one up weird behaviours ([#4473](https://github.com/OHIF/Viewers/issues/4473)) ([aa2b649](https://github.com/OHIF/Viewers/commit/aa2b649444eb4fe5422e72ea7830a709c4d24a90))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build ([#4471](https://github.com/OHIF/Viewers/issues/4471)) ([3d11ef2](https://github.com/OHIF/Viewers/commit/3d11ef28f213361ec7586809317bd219fa70e742))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** show ui notification on displaySet load error ([#4447](https://github.com/OHIF/Viewers/issues/4447)) ([4f20523](https://github.com/OHIF/Viewers/commit/4f20523109ecbb7ec5a6d5f2c97f7e73f81cda09))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** display error in ui while loading seg ([#4433](https://github.com/OHIF/Viewers/issues/4433)) ([2e96371](https://github.com/OHIF/Viewers/commit/2e96371b0631a9e5d411b0142300708ab8ba7d27))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **packages:** http-proxy-middleware vulnerability ([#4443](https://github.com/OHIF/Viewers/issues/4443)) ([0610425](https://github.com/OHIF/Viewers/commit/06104257402e872d447e59cb166184d9a3548f8b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **new-study-panel:** default to list view for non thumbnail series, change default fitler to all, and add more menu to thumbnail items with a dicom tag browser ([#4417](https://github.com/OHIF/Viewers/issues/4417)) ([a7fd9fa](https://github.com/OHIF/Viewers/commit/a7fd9fa5bfff7a1b533d99cb96f7147a35fd528f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tmtv:** prevent fusion row in tmtv from getting inverted unexpectedly ([#4420](https://github.com/OHIF/Viewers/issues/4420)) ([33af9bb](https://github.com/OHIF/Viewers/commit/33af9bb021ff3a6c3b683d4df2c730413400ff8a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **3d-viewport:** exception was being thrown and 3d viewpot not getting resized. ([#4366](https://github.com/OHIF/Viewers/issues/4366)) ([433cc80](https://github.com/OHIF/Viewers/commit/433cc8089db6aa218c9075bd0eeb7952a7e4f028))
|
||||
* **createReport:** early return on cancel in prompt ([#4243](https://github.com/OHIF/Viewers/issues/4243)) ([2ec4692](https://github.com/OHIF/Viewers/commit/2ec4692eaf2349e21b141a2c0b5b104ee10f7a28))
|
||||
* **dicomjson:** Update getUIDsFromImageID to work with json data source + update getDisplaySetImageUIDs to work with mixed sop class json ([#4322](https://github.com/OHIF/Viewers/issues/4322)) ([3dd0666](https://github.com/OHIF/Viewers/commit/3dd0666c0c090cbd66161f24bc9795f96abb3697))
|
||||
* **hp-presets:** select the active displaySet when toggling an HP preset, not a random one from the series panel ([#4365](https://github.com/OHIF/Viewers/issues/4365)) ([ace67b3](https://github.com/OHIF/Viewers/commit/ace67b3bbb6be4e8c78e613e20d3e10b93762bf7))
|
||||
* **sr:** load existing point, if there is 2nd point in renderableData (Fix rotation in arrow annotation) ([#4356](https://github.com/OHIF/Viewers/issues/4356)) ([7353f7f](https://github.com/OHIF/Viewers/commit/7353f7f069446f8484278c2cff5b09149cfa23eb))
|
||||
* **tools:** check if seriesNumber is an undefined properly ([#4338](https://github.com/OHIF/Viewers/issues/4338)) ([307b144](https://github.com/OHIF/Viewers/commit/307b14476be41b10b861d6a8474f7386b5107618))
|
||||
* **typo:** type in fourup preset ([#4426](https://github.com/OHIF/Viewers/issues/4426)) ([03aad4e](https://github.com/OHIF/Viewers/commit/03aad4eba24e33a266a6d91eaf74df52dc2a550e))
|
||||
* **updateIndex:** getNumberOfSlices is defined when used with a 3D viewport ([#4424](https://github.com/OHIF/Viewers/issues/4424)) ([d5bcf54](https://github.com/OHIF/Viewers/commit/d5bcf54e23ef68abd85c5f0ea671feca637c4f49))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **hangingProtocols:** added selection of the HangingProtocol stage from the url ([#4310](https://github.com/OHIF/Viewers/issues/4310)) ([fa2435d](https://github.com/OHIF/Viewers/commit/fa2435d5e94e5f903404ca94687b086f90f8d1f8))
|
||||
* **SR:** SCOORD3D point annotations support for stack viewports ([#4315](https://github.com/OHIF/Viewers/issues/4315)) ([ac1cad2](https://github.com/OHIF/Viewers/commit/ac1cad25af12ee0f7d508647e3134ed724d9b4d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** oidc-react-issue ([#4410](https://github.com/OHIF/Viewers/issues/4410)) ([e849199](https://github.com/OHIF/Viewers/commit/e849199eb0a9ecba4f9845aa1e07df775d5ded9b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **fossa:** update fossa to track licenses correctly ([#4411](https://github.com/OHIF/Viewers/issues/4411)) ([ec685ef](https://github.com/OHIF/Viewers/commit/ec685ef5b9c8bfa5bff3bbf869eb394548ae1cab))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **tours:** freeze versions and add licensings doc ([#4407](https://github.com/OHIF/Viewers/issues/4407)) ([60a8d51](https://github.com/OHIF/Viewers/commit/60a8d5154a5d6d2b121bd93aeacf12d97ef9f8cb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Select:** select clear button ([#4398](https://github.com/OHIF/Viewers/issues/4398)) ([a11cd6d](https://github.com/OHIF/Viewers/commit/a11cd6d6cbe20d7d986430befb3398f910a03ada))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dependent:** high priority vulnerabilities ([#4396](https://github.com/OHIF/Viewers/issues/4396)) ([b4f08ad](https://github.com/OHIF/Viewers/commit/b4f08adfb638e5df11bb77d3c1b128b5efdf77a7))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 Fix imports for ui-next ([#4394](https://github.com/OHIF/Viewers/issues/4394)) ([43efed2](https://github.com/OHIF/Viewers/commit/43efed207e0d8d13bcbf52fab14c1be034d22d0c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** Fixed study component open and closed feedback in Studies panel ([#4384](https://github.com/OHIF/Viewers/issues/4384)) ([365d824](https://github.com/OHIF/Viewers/commit/365d824b98e03b87db294878abde6823abdcf409))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **toolbar:** enable extensions to change toolbar button sections ([#4367](https://github.com/OHIF/Viewers/issues/4367)) ([1bfce0a](https://github.com/OHIF/Viewers/commit/1bfce0a03cbbb4cc1f69e8b5d1d72244b30d6b46))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **studies-panel:** New OHIF study panel - under experimental flag ([#4254](https://github.com/OHIF/Viewers/issues/4254)) ([7a96406](https://github.com/OHIF/Viewers/commit/7a96406a116e46e62c396855fa64f434e2984b58))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add kheops integration into OHIF v3 again ([#4345](https://github.com/OHIF/Viewers/issues/4345)) ([e1feffa](https://github.com/OHIF/Viewers/commit/e1feffa42553d6c8650a4aceb09f72c637126660))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 🐛 SeriesInstanceUID fallback + update retrieve metadata filtered to check for lazy ([#4346](https://github.com/OHIF/Viewers/issues/4346)) ([14498d4](https://github.com/OHIF/Viewers/commit/14498d4e9a6a57324b8be9f0b314f2901459dc4a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add CS3D WSI and Video Viewports and add annotation navigation for MPR ([#4182](https://github.com/OHIF/Viewers/issues/4182)) ([7599ec9](https://github.com/OHIF/Viewers/commit/7599ec9421129dcade94e6fa6ec7908424ab3134))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **roundNumber:** handle negative numbers properly ([#4336](https://github.com/OHIF/Viewers/issues/4336)) ([7377db8](https://github.com/OHIF/Viewers/commit/7377db8d280a90515fe099cb580607450cb146a5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* unexpected mpr measurements ([#4332](https://github.com/OHIF/Viewers/issues/4332)) ([ab6e341](https://github.com/OHIF/Viewers/commit/ab6e341731652a4fa894fcb576eb23dc95aefa11))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** Tailwind build errors ([#4329](https://github.com/OHIF/Viewers/issues/4329)) ([8e7cc11](https://github.com/OHIF/Viewers/commit/8e7cc1152917f562ea7e6a5f3f7e492b300dc564))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **url:** series query param filtering ([#4328](https://github.com/OHIF/Viewers/issues/4328)) ([9b10303](https://github.com/OHIF/Viewers/commit/9b10303a2efa809096156d4a2322b2b46f160a91))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **ui:** Created design and added core components for ui-next ([#4324](https://github.com/OHIF/Viewers/issues/4324)) ([9036418](https://github.com/OHIF/Viewers/commit/90364189b865514cc471786d2f91c270517e98fc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* customization types ([#4321](https://github.com/OHIF/Viewers/issues/4321)) ([72bef63](https://github.com/OHIF/Viewers/commit/72bef63ef6e63395ba18ff91a39294913966e9db))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* ip SSRF improper categorization ([#4319](https://github.com/OHIF/Viewers/issues/4319)) ([aa0e5a5](https://github.com/OHIF/Viewers/commit/aa0e5a59379453bb8e6a4f286447576744ea6bf5))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** remove border-border class ([#4317](https://github.com/OHIF/Viewers/issues/4317)) ([d402ded](https://github.com/OHIF/Viewers/commit/d402ded8c36631f8009b7b15b2f1c7a02cd09f6c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **dicom:** Update multiframe DICOM JSON parsing for correct image ID generation ([#4307](https://github.com/OHIF/Viewers/issues/4307)) ([16b7aa4](https://github.com/OHIF/Viewers/commit/16b7aa4f6538b81e5915e47b9209d74575895dfe))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **pmap:** added support for parametric map ([#4284](https://github.com/OHIF/Viewers/issues/4284)) ([fc0064f](https://github.com/OHIF/Viewers/commit/fc0064fd9d8cdc8fde81b81f0e71fd5d077ca22b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **SR:** text structured report (TEXT, CODE, NUM, PNAME, DATE, TIME and DATETIME) ([#4287](https://github.com/OHIF/Viewers/issues/4287)) ([246ebab](https://github.com/OHIF/Viewers/commit/246ebab6ebf5431a704a1861a5804045b9644ba4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||
|
||||
**Note:** Version bump only for package ohif-monorepo-root
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||
|
||||
|
||||
|
||||
+148
@@ -10,6 +10,7 @@ Please find below the list of datasets used on the demo page, along with their r
|
||||
|
||||
|
||||
## Platforms
|
||||
|
||||
### NIH NCI IDC
|
||||
|
||||
- Fedorov, A., Longabaugh, W.J., Pot, D., Clunie, D.A., Pieper, S., Aerts, H.J., Homeyer, A., Lewis, R., Akbarzadeh, A., Bontempi, D. and Clifford, W., 2021. NCI imaging data commons. Cancer research, 81(16), p.4188.
|
||||
@@ -147,3 +148,150 @@ Stony Brook University COVID-19 Positive Cases (COVID-19-NY-SBU)
|
||||
### 2.16.840.1.114362.1.11972228.22789312658.616067305.306.2
|
||||
|
||||
https://data.kitware.com/
|
||||
|
||||
|
||||
### 1.2.276.0.7230010.3.1.2.296485376.1.1665793212.499772
|
||||
### 2.25.269859997690759739055099378767846712697
|
||||
### 1.3.6.1.4.1.14519.5.2.1.5099.8010.217836670708542506360829799868
|
||||
### 1.3.6.1.4.1.14519.5.2.1.4792.2001.232252967813565730694525674696
|
||||
### 1.3.6.1.4.1.14519.5.2.1.4792.2001.105216574054253895819671475627
|
||||
### 1.3.6.1.4.1.5962.99.1.1117.5035.1620319789811.1.2.1
|
||||
### 1.3.6.1.4.1.5962.99.1.1123.9231.1620326176300.1.2.1
|
||||
### 1.3.6.1.4.1.5962.99.1.1126.3483.1620329455972.1.2.1
|
||||
|
||||
https://github.com/ImagingInformatics/hackathon-images
|
||||
|
||||
### 2.16.124.113543.6004.101.103.20021117.162333.1
|
||||
### 2.16.124.113543.6004.101.103.20021117.190619.1
|
||||
### 2.16.124.113543.6004.101.103.20021117.123455.1
|
||||
### 2.16.124.113543.6004.101.103.20021117.061159.1
|
||||
|
||||
https://www.aapm.org/
|
||||
|
||||
|
||||
### 1.2.840.113619.2.30.1.1762295590.1623.978668949.886
|
||||
|
||||
|
||||
### 1.2.276.0.7230010.3.1.2.447481088.1.1669202398.851612
|
||||
|
||||
Custom data SPECT, specifically I123-FP-CIT (DaTSCAN) SPECT, evaluates the dopaminergic system to diagnose Parkinson's disease, especially when tremor symptoms are unclear. It helps distinguish Parkinson's disease from treatment-related tremor.
|
||||
|
||||
|
||||
|
||||
### 1.3.6.1.4.1.9328.50.1.54652
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/rider-pilot/
|
||||
|
||||
Lung Image Database Consortium (LIDC). (2023) RIDER Pilot [Data set]. The Cancer Imaging Archive (TCIA). https://doi.org/10.7937/m87f-mz83
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.331759366792756327296606233801322964986
|
||||
|
||||
Mayr, N., Yuh, W. T. C., Bowen, S., Harkenrider, M., Knopp, M. V., Lee, E. Y.-P., Leung, E., Lo, S. S., Small Jr., W., & Wolfson, A. H. (2023). Cervical Cancer – Tumor Heterogeneity: Serial Functional and Molecular Imaging Across the Radiation Therapy Course in Advanced Cervical Cancer (Version 1) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/ERZ5-QZ59
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/cc-tumor-heterogeneity/
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.297577087050970310787702792940607009472
|
||||
|
||||
Eslick, E. M., Kipritidis, J., Gradinscak, D., Stevens, M. J., Bailey, D. L., Harris, B., Booth, J. T., & Keall, P. J. (2022). CT Ventilation as a functional imaging modality for lung cancer radiotherapy (CT-vs-PET-Ventilation-Imaging) (Version 1) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/3ppx-7s22
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/ct-vs-pet-ventilation-imaging/
|
||||
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.2103.7010.634114621738943599785009586807
|
||||
### 1.3.6.1.4.1.14519.5.2.1.2103.7010.135953723682765205394176991681
|
||||
|
||||
Huang, W., Tudorica, A., Chui, S., Kemmer, K., Naik, A., Troxell, M., Oh, K., Roy, N., Afzal, A., & Holtorf, M. (2014). Variations of dynamic contrast-enhanced magnetic resonance imaging in evaluation of breast cancer therapy response: a multicenter data analysis challenge (QIN Breast DCE-MRI) (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/k9/tcia.2014.a2n1ixox
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/qin-breast-dce-mri/
|
||||
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.1.24766180081901755714059656629507905556
|
||||
|
||||
|
||||
Cancer Moonshot Biobank. (2023). Cancer Moonshoot Biobank – Acute Myeloid Leukemia (CMB-AML) (Version 4) [Dataset]. The Cancer Imaging Archive. https://doi.org/10.7937/PCTE-6M66
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/cmb-aml/
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.3098.5025.285242291560760827564488897577
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/anti-pd-1_lung/
|
||||
|
||||
Madhavi, P., Patel, S., & Tsao, A. S. (2019). Data from Anti-PD-1 Immunotherapy Lung [Data set]. The Cancer Imaging Archive. DOI: 10.7937/tcia.2019.zjjwb9ip
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.1.84416332615988066829602832830236187384
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/cmb-pca/
|
||||
|
||||
Cancer Moonshot Biobank. (2022). Cancer Moonshot Biobank – Prostate Cancer Collection (CMB-PCA) (Version 7) [Dataset]. The Cancer Imaging Archive. https://doi.org/10.7937/25T7-6Y12
|
||||
|
||||
### 1.3.6.1.4.1.32722.99.99.239341353911714368772597187099978969331
|
||||
|
||||
Aerts, H. J. W. L., Wee, L., Rios Velazquez, E., Leijenaar, R. T. H., Parmar, C., Grossmann, P., Carvalho, S., Bussink, J., Monshouwer, R., Haibe-Kains, B., Rietveld, D., Hoebers, F., Rietbergen, M. M., Leemans, C. R., Dekker, A., Quackenbush, J., Gillies, R. J., Lambin, P. (2014). Data From NSCLC-Radiomics (version 4) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2015.PF0M9REI
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/nsclc-radiomics/
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.7085.2626.494695569589117268722281491772
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/cptac-ucec/
|
||||
|
||||
|
||||
National Cancer Institute Clinical Proteomic Tumor Analysis Consortium (CPTAC). (2019). The Clinical Proteomic Tumor Analysis Consortium Uterine Corpus Endometrial Carcinoma Collection (CPTAC-UCEC) (Version 12) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2018.3R3JUISW
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.207544490797667703011829289839681390478
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/remind/
|
||||
|
||||
Juvekar, P., Dorent, R., Kögl, F., Torio, E., Barr, C., Rigolo, L., Galvin, C., Jowkar, N., Kazi, A., Haouchine, N., Cheema, H., Navab, N., Pieper, S., Wells, W. M., Bi, W. L., Golby, A., Frisken, S., & Kapur, T. (2023). The Brain Resection Multimodal Imaging Database (ReMIND) (Version 1) [dataset]. The Cancer Imaging Archive. https://doi.org/10.7937/3RAG-D070
|
||||
|
||||
### 1.3.12.2.1107.5.1.4.60175.30000008042114404745300000010
|
||||
|
||||
Gavrielides, M. A., Kinnard, L. M., Myers, K. J., Peregoy, J., Pritchard, W. F., Zeng, R., Esparza, J., Karanian, J., & Petrick, N. (2015). Data From Phantom FDA [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/k9/TCIA.2015.orbjkmux
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/phantom-fda/
|
||||
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.6834.5010.992793141464713669479982159310
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/4d-lung/
|
||||
|
||||
|
||||
Hugo, G. D., Weiss, E., Sleeman, W. C., Balik, S., Keall, P. J., Lu, J., & Williamson, J. F. (2016). Data from 4D Lung Imaging of NSCLC Patients (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2016.ELN8YGLE
|
||||
|
||||
|
||||
### 1.3.6.1.4.1.9328.50.17.15423521354819720574322014551955370036
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/rider-lung-pet-ct/
|
||||
|
||||
Muzi P, Wanner M, & Kinahan P. (2015). Data From RIDER Lung PET-CT. The Cancer Imaging Archive. https://doi.org/10.7937/k9/tcia.2015.ofip7tvm
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.9823.1001.134394060407147891170882809392
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/prostate-mri/
|
||||
|
||||
Choyke P, Turkbey B, Pinto P, Merino M, Wood B. (2016). Data From PROSTATE-MRI. The Cancer Imaging Archive. http://doi.org/10.7937/K9/TCIA.2016.6046GUDv
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.191696062987463500085282581898315738844
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/upenn-gbm/
|
||||
|
||||
Bakas, S., Sako, C., Akbari, H., Bilello, M., Sotiras, A., Shukla, G., Rudie, J. D., Flores Santamaria, N., Fathi Kazerooni, A., Pati, S., Rathore, S., Mamourian, E., Ha, S. M., Parker, W., Doshi, J., Baid, U., Bergman, M., Binder, Z. A., Verma, R., … Davatzikos, C. (2021). Multi-parametric magnetic resonance imaging (mpMRI) scans for de novo Glioblastoma (GBM) patients from the University of Pennsylvania Health System (UPENN-GBM) (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.709X-DN49
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.4792.2001.921758700577562664959693695481
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/breast-diagnosis/
|
||||
|
||||
Bloch, B. Nicolas, Jain, Ashali, & Jaffe, C. Carl. (2015). BREAST-DIAGNOSIS [Data set]. The Cancer Imaging Archive. http://doi.org/10.7937/K9/TCIA.2015.SDNRQXXR
|
||||
|
||||
|
||||
### 1.3.6.1.4.1.14519.5.2.1.1620.1225.189514895974227080410265976065
|
||||
|
||||
Comstock, C. E., Gatsonis, C., Newstead, G. M., Snyder, B. S., Gareen, I. F., Bergin, J. T., Rahbar, H., Sung, J. S., Jacobs, C., Harvey, J. A., Nicholson, M. H., Ward, R. C., Holt, J., Prather, A., Miller, K. D., Schnall, M. D., & Kuhl, C. K. (2023). Abbreviated Breast MRI and Digital Tomosynthesis Mammography in Screening Women With Dense Breasts (EA1141) (Version 1) [dataset]. The Cancer Imaging Archive. https://doi.org/10.7937/2BAS-HR33
|
||||
|
||||
https://www.cancerimagingarchive.net/collection/ea1141/
|
||||
|
||||
### 1.2.276.0.7230010.3.1.2.2155604110.4180.1021041295.21
|
||||
|
||||
From OFFIS DICOM-Team
|
||||
|
||||
https://www.offis.de/
|
||||
OFFIS DICOM-Team
|
||||
@@ -142,13 +142,11 @@ Here is a schematic representation of our development workflow:
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. [Fork this repository][how-to-fork]
|
||||
2. [Clone your forked repository][how-to-clone]
|
||||
- `git clone https://github.com/YOUR-USERNAME/Viewers.git`
|
||||
3. Navigate to the cloned project's directory
|
||||
4. Add this repo as a `remote` named `upstream`
|
||||
- `git remote add upstream https://github.com/OHIF/Viewers.git`
|
||||
5. `yarn install` to restore dependencies and link projects
|
||||
1. Clone the repository
|
||||
- `git clone https://github.com/OHIF/Viewers.git`
|
||||
2. Navigate to the cloned project's directory
|
||||
3. `yarn install` to restore dependencies and link projects
|
||||
4. `yarn dev` to start the development server
|
||||
|
||||
#### To Develop
|
||||
|
||||
@@ -168,7 +166,7 @@ These commands are available from the root directory. Each project directory
|
||||
also supports a number of commands that can be found in their respective
|
||||
`README.md` and `package.json` files.
|
||||
|
||||
| Yarn Commands | Description |
|
||||
| Commands | Description |
|
||||
| ---------------------------- | ------------------------------------------------------------- |
|
||||
| **Develop** | |
|
||||
| `dev` or `start` | Default development experience for Viewer |
|
||||
@@ -333,4 +331,4 @@ MIT © [OHIF](https://github.com/OHIF)
|
||||
[vtk-npm]: https://www.npmjs.com/package/@ohif/extension-vtk
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_large)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FOHIF%2FViewers?ref=badge_large&issueType=license)
|
||||
+430
@@ -3,6 +3,436 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OHIF 3.9 Recovery 3 ([#4697](https://github.com/OHIF/Viewers/issues/4697)) ([5d4f2e2](https://github.com/OHIF/Viewers/commit/5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modes:** don't attempt to retrieve a stage index if HPs ([#4552](https://github.com/OHIF/Viewers/issues/4552)) ([e5286d9](https://github.com/OHIF/Viewers/commit/e5286d9fc2449da50b738e7e6208d489cce1633e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||
|
||||
**Note:** Version bump only for package @externals/devDependencies
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@externals/devDependencies",
|
||||
"description": "External dev dependencies - put dev build dependencies here",
|
||||
"version": "3.9.0-beta.62",
|
||||
"version": "3.9.3",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"engines": {
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@kitware/vtk.js": "30.4.1",
|
||||
"@kitware/vtk.js": "32.1.1",
|
||||
"clsx": "^2.1.1",
|
||||
"core-js": "^3.2.1",
|
||||
"moment": "^2.9.4"
|
||||
@@ -68,7 +68,7 @@
|
||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||
"react-refresh": "^0.14.2",
|
||||
"semver": "^7.5.1",
|
||||
"serve": "^14.2.0",
|
||||
"serve": "^14.2.4",
|
||||
"shader-loader": "^1.3.1",
|
||||
"shx": "^0.3.3",
|
||||
"source-map-loader": "^4.0.1",
|
||||
@@ -77,9 +77,9 @@
|
||||
"stylus": "^0.59.0",
|
||||
"stylus-loader": "^7.1.3",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"typescript": "4.6.4",
|
||||
"typescript": "5.5.4",
|
||||
"unused-webpack-plugin": "2.4.0",
|
||||
"webpack": "5.89.0",
|
||||
"webpack": "5.94.0",
|
||||
"webpack-bundle-analyzer": "^4.8.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "4.7.3",
|
||||
|
||||
@@ -3,6 +3,430 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||
|
||||
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||
|
||||
**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.9.0-beta.62",
|
||||
"version": "3.9.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dicom-microscopy-viewer": "^0.46.1"
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@
|
||||
"glob-parent": "^6.0.2",
|
||||
"trim": "^1.0.0",
|
||||
"package-json": "^8.1.0",
|
||||
"typescript": "4.6.4",
|
||||
"typescript": "5.5.4",
|
||||
"sharp": "^0.32.6"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
38c19fab77cdb21d14bdae35813d73f43012cbd7
|
||||
5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb
|
||||
@@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
const ENTRY = {
|
||||
app: `${SRC_DIR}/index.tsx`,
|
||||
};
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
const webpack = require('webpack');
|
||||
const { merge } = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.base.js');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
const pkg = require('./../package.json');
|
||||
|
||||
const ROOT_DIR = path.join(__dirname, '../');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
const ENTRY = {
|
||||
app: `${SRC_DIR}/index.tsx`,
|
||||
};
|
||||
|
||||
const outputName = `ohif-${pkg.name.split('/').pop()}`;
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
|
||||
|
||||
return merge(commonConfig, {
|
||||
stats: {
|
||||
colors: true,
|
||||
hash: true,
|
||||
timings: true,
|
||||
assets: true,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
modules: false,
|
||||
children: false,
|
||||
warnings: true,
|
||||
},
|
||||
optimization: {
|
||||
minimize: true,
|
||||
sideEffects: true,
|
||||
},
|
||||
output: {
|
||||
path: ROOT_DIR,
|
||||
library: 'ohif-extension-cornerstone-dicom-pmap',
|
||||
libraryTarget: 'umd',
|
||||
filename: pkg.main,
|
||||
},
|
||||
externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/],
|
||||
plugins: [
|
||||
new webpack.optimize.LimitChunkCountPlugin({
|
||||
maxChunks: 1,
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: `./dist/${outputName}.css`,
|
||||
chunkFilename: `./dist/${outputName}.css`,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,428 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OHIF 3.9 Recovery 3 ([#4697](https://github.com/OHIF/Viewers/issues/4697)) ([5d4f2e2](https://github.com/OHIF/Viewers/commit/5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modes:** don't attempt to retrieve a stage index if HPs ([#4552](https://github.com/OHIF/Viewers/issues/4552)) ([e5286d9](https://github.com/OHIF/Viewers/commit/e5286d9fc2449da50b738e7e6208d489cce1633e))
|
||||
* **multiframe:** metadata handling of NM studies and loading order ([#4540](https://github.com/OHIF/Viewers/issues/4540)) ([71f06dc](https://github.com/OHIF/Viewers/commit/71f06dcd3e6d24f1e3d931f7db756c26038927da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* video playback ([#4497](https://github.com/OHIF/Viewers/issues/4497)) ([610faa5](https://github.com/OHIF/Viewers/commit/610faa5a2738da5eabc40e57e338c359f481e852))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Measurement Tracking: Various UI and functionality improvements ([#4481](https://github.com/OHIF/Viewers/issues/4481)) ([62b2748](https://github.com/OHIF/Viewers/commit/62b27488471c9d5979142e2d15872a85778b90ed))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bugs:** Update dependencies and enhance UI components ([#4478](https://github.com/OHIF/Viewers/issues/4478)) ([05d41c5](https://github.com/OHIF/Viewers/commit/05d41c52068a3b7ba249f15ecdf71838c352fd30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tmtv:** fix toggle one up weird behaviours ([#4473](https://github.com/OHIF/Viewers/issues/4473)) ([aa2b649](https://github.com/OHIF/Viewers/commit/aa2b649444eb4fe5422e72ea7830a709c4d24a90))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build ([#4471](https://github.com/OHIF/Viewers/issues/4471)) ([3d11ef2](https://github.com/OHIF/Viewers/commit/3d11ef28f213361ec7586809317bd219fa70e742))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **SR:** SCOORD3D point annotations support for stack viewports ([#4315](https://github.com/OHIF/Viewers/issues/4315)) ([ac1cad2](https://github.com/OHIF/Viewers/commit/ac1cad25af12ee0f7d508647e3134ed724d9b4d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-pmap
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **pmap:** added support for parametric map ([#4284](https://github.com/OHIF/Viewers/issues/4284)) ([fc0064f](https://github.com/OHIF/Viewers/commit/fc0064fd9d8cdc8fde81b81f0e71fd5d077ca22b))
|
||||
@@ -0,0 +1,20 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Open Health Imaging Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,12 @@
|
||||
# dicom-pmap
|
||||
## Description
|
||||
|
||||
DICOM PMAP read workflow. This extension will allow you to load a DICOM Parametric
|
||||
Map image and display it on OHIF.
|
||||
|
||||
## Author
|
||||
|
||||
OHIF
|
||||
|
||||
## License
|
||||
MIT
|
||||
@@ -0,0 +1,44 @@
|
||||
module.exports = {
|
||||
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||
env: {
|
||||
test: {
|
||||
presets: [
|
||||
[
|
||||
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: 'commonjs',
|
||||
debug: false,
|
||||
},
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
'@babel/preset-react',
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-transform-regenerator',
|
||||
'@babel/plugin-transform-runtime',
|
||||
],
|
||||
},
|
||||
production: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
development: {
|
||||
presets: [
|
||||
// WebPack handles ES6 --> Target Syntax
|
||||
['@babel/preset-env', { modules: false }],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: ['react-hot-loader/babel'],
|
||||
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-pmap",
|
||||
"version": "3.9.3",
|
||||
"description": "DICOM Parametric Map read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
"main": "dist/ohif-extension-cornerstone-dicom-pmap.umd.js",
|
||||
"module": "src/index.tsx",
|
||||
"files": [
|
||||
"dist/**",
|
||||
"public/**",
|
||||
"README.md"
|
||||
],
|
||||
"repository": "OHIF/Viewers",
|
||||
"keywords": [
|
||||
"ohif-extension"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "shx rm -rf dist",
|
||||
"clean:deep": "yarn run clean && shx rm -rf node_modules",
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo",
|
||||
"dev:dicom-pmap": "yarn run dev",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package-1": "yarn run build",
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.9.3",
|
||||
"@ohif/extension-cornerstone": "3.9.3",
|
||||
"@ohif/extension-default": "3.9.3",
|
||||
"@ohif/i18n": "3.9.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^12.2.2",
|
||||
"react-router": "^6.8.1",
|
||||
"react-router-dom": "^6.8.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^2.2.20",
|
||||
"@cornerstonejs/core": "^2.2.20",
|
||||
"@kitware/vtk.js": "32.1.1",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
import { utils } from '@ohif/core';
|
||||
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';
|
||||
const sopClassUids = ['1.2.840.10008.5.1.4.1.1.30'];
|
||||
|
||||
function _getDisplaySetsFromSeries(
|
||||
instances,
|
||||
servicesManager: AppTypes.ServicesManager,
|
||||
extensionManager
|
||||
) {
|
||||
const instance = instances[0];
|
||||
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
SOPInstanceUID,
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
SeriesDate,
|
||||
SOPClassUID,
|
||||
wadoRoot,
|
||||
wadoUri,
|
||||
wadoUriRoot,
|
||||
} = instance;
|
||||
|
||||
const displaySet = {
|
||||
// Parametric map use to have the same modality as its referenced volume but
|
||||
// "PMAP" is used in the viewer even though this is not a valid DICOM modality
|
||||
Modality: 'PMAP',
|
||||
isReconstructable: true, // by default for now
|
||||
displaySetInstanceUID: `pmap.${utils.guid()}`,
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
SeriesDate,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId,
|
||||
SOPClassUID,
|
||||
referencedImages: null,
|
||||
referencedSeriesInstanceUID: null,
|
||||
referencedDisplaySetInstanceUID: null,
|
||||
referencedVolumeURI: null,
|
||||
referencedVolumeId: null,
|
||||
isDerivedDisplaySet: true,
|
||||
loadStatus: {
|
||||
loading: false,
|
||||
loaded: false,
|
||||
},
|
||||
sopClassUids,
|
||||
instance,
|
||||
instances: [instance],
|
||||
wadoRoot,
|
||||
wadoUriRoot,
|
||||
wadoUri,
|
||||
isOverlayDisplaySet: true,
|
||||
};
|
||||
|
||||
const referencedSeriesSequence = instance.ReferencedSeriesSequence;
|
||||
|
||||
if (!referencedSeriesSequence) {
|
||||
console.error('ReferencedSeriesSequence is missing for the parametric map');
|
||||
return;
|
||||
}
|
||||
|
||||
const referencedSeries = referencedSeriesSequence[0] || referencedSeriesSequence;
|
||||
|
||||
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||
|
||||
// Does not get the referenced displaySet during parametric displaySet creation
|
||||
// because it is still not available (getDisplaySetByUID returns `undefined`).
|
||||
displaySet.getReferenceDisplaySet = () => {
|
||||
const { displaySetService } = servicesManager.services;
|
||||
|
||||
if (displaySet.referencedDisplaySetInstanceUID) {
|
||||
return displaySetService.getDisplaySetByUID(displaySet.referencedDisplaySetInstanceUID);
|
||||
}
|
||||
|
||||
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
|
||||
displaySet.referencedSeriesInstanceUID
|
||||
);
|
||||
|
||||
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
|
||||
throw new Error('Referenced displaySet is missing for the parametric map');
|
||||
}
|
||||
|
||||
const referencedDisplaySet = referencedDisplaySets[0];
|
||||
|
||||
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||
|
||||
return referencedDisplaySet;
|
||||
};
|
||||
|
||||
// Does not get the referenced volumeId during parametric displaySet creation because the
|
||||
// referenced displaySet is still not available (getDisplaySetByUID returns `undefined`).
|
||||
displaySet.getReferencedVolumeId = () => {
|
||||
if (displaySet.referencedVolumeId) {
|
||||
return displaySet.referencedVolumeId;
|
||||
}
|
||||
|
||||
const referencedDisplaySet = displaySet.getReferenceDisplaySet();
|
||||
const referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID;
|
||||
const referencedVolumeId = `${VOLUME_LOADER_SCHEME}:${referencedVolumeURI}`;
|
||||
|
||||
displaySet.referencedVolumeURI = referencedVolumeURI;
|
||||
displaySet.referencedVolumeId = referencedVolumeId;
|
||||
|
||||
return referencedVolumeId;
|
||||
};
|
||||
|
||||
displaySet.load = async ({ headers }) =>
|
||||
await _load(displaySet, servicesManager, extensionManager, headers);
|
||||
|
||||
return [displaySet];
|
||||
}
|
||||
|
||||
const getRangeFromPixelData = (pixelData: Float32Array) => {
|
||||
let lowest = pixelData[0];
|
||||
let highest = pixelData[0];
|
||||
|
||||
for (let i = 1; i < pixelData.length; i++) {
|
||||
if (pixelData[i] < lowest) {
|
||||
lowest = pixelData[i];
|
||||
}
|
||||
if (pixelData[i] > highest) {
|
||||
highest = pixelData[i];
|
||||
}
|
||||
}
|
||||
|
||||
return [lowest, highest];
|
||||
};
|
||||
|
||||
async function _load(
|
||||
displaySet,
|
||||
servicesManager: AppTypes.ServicesManager,
|
||||
extensionManager,
|
||||
headers
|
||||
) {
|
||||
const volumeId = `${VOLUME_LOADER_SCHEME}:${displaySet.displaySetInstanceUID}`;
|
||||
const volumeLoadObject = cache.getVolumeLoadObject(volumeId);
|
||||
|
||||
if (volumeLoadObject) {
|
||||
return volumeLoadObject.promise;
|
||||
}
|
||||
|
||||
displaySet.loading = true;
|
||||
displaySet.isLoaded = false;
|
||||
|
||||
// We don't want to fire multiple loads, so we'll wait for the first to finish
|
||||
// and also return the same promise to any other callers.
|
||||
const promise = _loadParametricMap({
|
||||
extensionManager,
|
||||
displaySet,
|
||||
headers,
|
||||
});
|
||||
|
||||
cache.putVolumeLoadObject(volumeId, { promise }).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
promise
|
||||
.then(() => {
|
||||
displaySet.loading = false;
|
||||
displaySet.isLoaded = true;
|
||||
// Broadcast that loading is complete
|
||||
servicesManager.services.segmentationService._broadcastEvent(
|
||||
servicesManager.services.segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||
{
|
||||
pmapDisplaySet: displaySet,
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
displaySet.loading = false;
|
||||
displaySet.isLoaded = false;
|
||||
throw err;
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function _loadParametricMap({ displaySet, headers }: withAppTypes) {
|
||||
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(displaySet, null, headers);
|
||||
const referencedVolumeId = displaySet.getReferencedVolumeId();
|
||||
const cachedReferencedVolume = cache.getVolume(referencedVolumeId);
|
||||
|
||||
// Parametric map can be loaded only if its referenced volume exists otherwise it will fail
|
||||
if (!cachedReferencedVolume) {
|
||||
throw new Error(
|
||||
'Referenced Volume is missing for the PMAP, and stack viewport PMAP is not supported yet'
|
||||
);
|
||||
}
|
||||
|
||||
const { imageIds } = cachedReferencedVolume;
|
||||
const results = await adaptersPMAP.Cornerstone3D.ParametricMap.generateToolState(
|
||||
imageIds,
|
||||
arrayBuffer,
|
||||
metaData
|
||||
);
|
||||
const { pixelData } = results;
|
||||
const TypedArrayConstructor = pixelData.constructor;
|
||||
const paramMapId = displaySet.displaySetInstanceUID;
|
||||
|
||||
const derivedVolume = await volumeLoader.createAndCacheDerivedVolume(referencedVolumeId, {
|
||||
volumeId: paramMapId,
|
||||
targetBuffer: {
|
||||
type: TypedArrayConstructor.name,
|
||||
},
|
||||
});
|
||||
|
||||
const newPixelData = new TypedArrayConstructor(pixelData.length);
|
||||
for (let i = 0; i < pixelData.length; i++) {
|
||||
newPixelData[i] = pixelData[i] * 100;
|
||||
}
|
||||
derivedVolume.voxelManager.setCompleteScalarDataArray(newPixelData);
|
||||
const range = getRangeFromPixelData(newPixelData);
|
||||
const windowLevel = csUtils.windowLevel.toWindowLevel(range[0], range[1]);
|
||||
|
||||
derivedVolume.metadata.voiLut = [windowLevel];
|
||||
derivedVolume.loadStatus = { loaded: true };
|
||||
|
||||
return derivedVolume;
|
||||
}
|
||||
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
const getDisplaySetsFromSeries = instances => {
|
||||
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'dicom-pmap',
|
||||
sopClassUids,
|
||||
getDisplaySetsFromSeries,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export default getSopClassHandlerModule;
|
||||
@@ -0,0 +1,7 @@
|
||||
import packageJson from '../package.json';
|
||||
|
||||
const id = packageJson.name;
|
||||
const SOPClassHandlerName = 'dicom-pmap';
|
||||
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
||||
|
||||
export { id, SOPClassHandlerId, SOPClassHandlerName };
|
||||
@@ -0,0 +1,39 @@
|
||||
import { id } from './id';
|
||||
import React from 'react';
|
||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstonePMAPViewport');
|
||||
});
|
||||
|
||||
const OHIFCornerstonePMAPViewport = props => {
|
||||
return (
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
<Component {...props} />
|
||||
</React.Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* You can remove any of the following modules if you don't need them.
|
||||
*/
|
||||
const extension = {
|
||||
id,
|
||||
getViewportModule({ servicesManager, extensionManager, commandsManager }) {
|
||||
const ExtendedOHIFCornerstonePMAPViewport = props => {
|
||||
return (
|
||||
<OHIFCornerstonePMAPViewport
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
commandsManager={commandsManager}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return [{ name: 'dicom-pmap', component: ExtendedOHIFCornerstonePMAPViewport }];
|
||||
},
|
||||
getSopClassHandlerModule,
|
||||
};
|
||||
|
||||
export default extension;
|
||||
@@ -0,0 +1,206 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui';
|
||||
|
||||
function OHIFCornerstonePMAPViewport(props: withAppTypes) {
|
||||
const {
|
||||
displaySets,
|
||||
children,
|
||||
viewportOptions,
|
||||
displaySetOptions,
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
} = props;
|
||||
const viewportId = viewportOptions.viewportId;
|
||||
const { displaySetService, segmentationService, uiNotificationService } =
|
||||
servicesManager.services;
|
||||
|
||||
// PMAP viewport will always have a single display set
|
||||
if (displaySets.length !== 1) {
|
||||
throw new Error('PMAP viewport must have a single display set');
|
||||
}
|
||||
|
||||
const pmapDisplaySet = displaySets[0];
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
const referencedDisplaySetRef = useRef(null);
|
||||
const { viewports, activeViewportId } = viewportGrid;
|
||||
const referencedDisplaySet = pmapDisplaySet.getReferenceDisplaySet();
|
||||
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
|
||||
referencedDisplaySet,
|
||||
pmapDisplaySet
|
||||
);
|
||||
|
||||
referencedDisplaySetRef.current = {
|
||||
displaySet: referencedDisplaySet,
|
||||
metadata: referencedDisplaySetMetadata,
|
||||
};
|
||||
|
||||
const [pmapIsLoading, setPmapIsLoading] = useState(!pmapDisplaySet.isLoaded);
|
||||
|
||||
// Add effect to listen for loading complete
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = segmentationService.subscribe(
|
||||
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||
evt => {
|
||||
if (evt.pmapDisplaySet?.displaySetInstanceUID === pmapDisplaySet.displaySetInstanceUID) {
|
||||
setPmapIsLoading(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [pmapDisplaySet]);
|
||||
|
||||
const getCornerstoneViewport = useCallback(() => {
|
||||
const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current;
|
||||
const { component: Component } = extensionManager.getModuleEntry(
|
||||
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
||||
);
|
||||
|
||||
displaySetOptions.unshift({});
|
||||
const [pmapDisplaySetOptions] = displaySetOptions;
|
||||
|
||||
// Make sure `options` exists
|
||||
pmapDisplaySetOptions.options = pmapDisplaySetOptions.options ?? {};
|
||||
|
||||
Object.assign(pmapDisplaySetOptions.options, {
|
||||
colormap: {
|
||||
name: 'rainbow_2',
|
||||
opacity: [
|
||||
{ value: 0, opacity: 0 },
|
||||
{ value: 0.25, opacity: 0.25 },
|
||||
{ value: 0.5, opacity: 0.5 },
|
||||
{ value: 0.75, opacity: 0.75 },
|
||||
{ value: 0.9, opacity: 0.99 },
|
||||
],
|
||||
},
|
||||
voi: {
|
||||
windowCenter: 50,
|
||||
windowWidth: 100,
|
||||
},
|
||||
});
|
||||
|
||||
uiNotificationService.show({
|
||||
title: 'Parametric Map',
|
||||
type: 'warning',
|
||||
message: 'The values are multiplied by 100 in the viewport for better visibility',
|
||||
});
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
// Referenced + PMAP displaySets must be passed as parameter in this order
|
||||
displaySets={[referencedDisplaySet, pmapDisplaySet]}
|
||||
viewportOptions={{
|
||||
viewportType: 'volume',
|
||||
orientation: viewportOptions.orientation,
|
||||
viewportId: viewportOptions.viewportId,
|
||||
presentationIds: viewportOptions.presentationIds,
|
||||
}}
|
||||
displaySetOptions={[{}, pmapDisplaySetOptions]}
|
||||
></Component>
|
||||
);
|
||||
}, [
|
||||
extensionManager,
|
||||
displaySetOptions,
|
||||
props,
|
||||
pmapDisplaySet,
|
||||
viewportOptions.orientation,
|
||||
viewportOptions.viewportId,
|
||||
]);
|
||||
|
||||
// Cleanup the PMAP viewport when the viewport is destroyed
|
||||
useEffect(() => {
|
||||
const onDisplaySetsRemovedSubscription = displaySetService.subscribe(
|
||||
displaySetService.EVENTS.DISPLAY_SETS_REMOVED,
|
||||
({ displaySetInstanceUIDs }) => {
|
||||
const activeViewport = viewports.get(activeViewportId);
|
||||
if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) {
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId: activeViewportId,
|
||||
displaySetInstanceUIDs: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
onDisplaySetsRemovedSubscription.unsubscribe();
|
||||
};
|
||||
}, [activeViewportId, displaySetService, viewportGridService, viewports]);
|
||||
|
||||
let childrenWithProps = null;
|
||||
|
||||
if (children && children.length) {
|
||||
childrenWithProps = children.map((child, index) => {
|
||||
return (
|
||||
child &&
|
||||
React.cloneElement(child, {
|
||||
viewportId,
|
||||
key: index,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex h-full w-full flex-row overflow-hidden">
|
||||
{pmapIsLoading && (
|
||||
<LoadingIndicatorTotalPercent
|
||||
className="h-full w-full"
|
||||
totalNumbers={null}
|
||||
percentComplete={null}
|
||||
loadingText="Loading Parametric Map..."
|
||||
/>
|
||||
)}
|
||||
{getCornerstoneViewport()}
|
||||
{childrenWithProps}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
OHIFCornerstonePMAPViewport.propTypes = {
|
||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||
viewportId: PropTypes.string.isRequired,
|
||||
dataSource: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
function _getReferencedDisplaySetMetadata(referencedDisplaySet, pmapDisplaySet) {
|
||||
const { SharedFunctionalGroupsSequence } = pmapDisplaySet.instance;
|
||||
|
||||
const SharedFunctionalGroup = Array.isArray(SharedFunctionalGroupsSequence)
|
||||
? SharedFunctionalGroupsSequence[0]
|
||||
: SharedFunctionalGroupsSequence;
|
||||
|
||||
const { PixelMeasuresSequence } = SharedFunctionalGroup;
|
||||
|
||||
const PixelMeasures = Array.isArray(PixelMeasuresSequence)
|
||||
? PixelMeasuresSequence[0]
|
||||
: PixelMeasuresSequence;
|
||||
|
||||
const { SpacingBetweenSlices, SliceThickness } = PixelMeasures;
|
||||
|
||||
const image0 = referencedDisplaySet.images[0];
|
||||
const referencedDisplaySetMetadata = {
|
||||
PatientID: image0.PatientID,
|
||||
PatientName: image0.PatientName,
|
||||
PatientSex: image0.PatientSex,
|
||||
PatientAge: image0.PatientAge,
|
||||
SliceThickness: image0.SliceThickness || SliceThickness,
|
||||
StudyDate: image0.StudyDate,
|
||||
SeriesDescription: image0.SeriesDescription,
|
||||
SeriesInstanceUID: image0.SeriesInstanceUID,
|
||||
SeriesNumber: image0.SeriesNumber,
|
||||
ManufacturerModelName: image0.ManufacturerModelName,
|
||||
SpacingBetweenSlices: image0.SpacingBetweenSlices || SpacingBetweenSlices,
|
||||
};
|
||||
|
||||
return referencedDisplaySetMetadata;
|
||||
}
|
||||
|
||||
export default OHIFCornerstonePMAPViewport;
|
||||
@@ -3,6 +3,433 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OHIF 3.9 Recovery 3 ([#4697](https://github.com/OHIF/Viewers/issues/4697)) ([5d4f2e2](https://github.com/OHIF/Viewers/commit/5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-rt",
|
||||
"version": "3.9.0-beta.62",
|
||||
"version": "3.9.3",
|
||||
"description": "DICOM RT read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -33,10 +33,10 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.9.0-beta.62",
|
||||
"@ohif/extension-cornerstone": "3.9.0-beta.62",
|
||||
"@ohif/extension-default": "3.9.0-beta.62",
|
||||
"@ohif/i18n": "3.9.0-beta.62",
|
||||
"@ohif/core": "3.9.3",
|
||||
"@ohif/extension-cornerstone": "3.9.3",
|
||||
"@ohif/extension-default": "3.9.3",
|
||||
"@ohif/i18n": "3.9.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
|
||||
const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
|
||||
const services = servicesManager.services;
|
||||
const { displaySetService, viewportGridService } = services;
|
||||
|
||||
const actions = {
|
||||
hydrateRTSDisplaySet: ({ displaySet, viewportId }) => {
|
||||
if (displaySet.Modality !== 'RTSTRUCT') {
|
||||
throw new Error('Display set is not an RTSTRUCT');
|
||||
}
|
||||
|
||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
displaySet.referencedDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
// update the previously stored segmentationPresentation with the new viewportId
|
||||
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||
// it will have the correct segmentation representation hydrated
|
||||
commandsManager.runCommand('updateStoredSegmentationPresentation', {
|
||||
displaySet: displaySet,
|
||||
type: SegmentationRepresentations.Contour,
|
||||
});
|
||||
|
||||
// update the previously stored positionPresentation with the new viewportId
|
||||
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||
// it will be in the correct position zoom and pan
|
||||
commandsManager.runCommand('updateStoredPositionPresentation', {
|
||||
viewportId,
|
||||
displaySetInstanceUID: referencedDisplaySet.displaySetInstanceUID,
|
||||
});
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
hydrateRTSDisplaySet: {
|
||||
commandFn: actions.hydrateRTSDisplaySet,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
actions,
|
||||
definitions,
|
||||
defaultContext: 'cornerstone-dicom-rt',
|
||||
};
|
||||
};
|
||||
|
||||
export default commandsModule;
|
||||
@@ -72,22 +72,27 @@ function _getDisplaySetsFromSeries(
|
||||
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||
|
||||
displaySet.getReferenceDisplaySet = () => {
|
||||
const { displaySetService } = servicesManager.services;
|
||||
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
|
||||
displaySet.referencedSeriesInstanceUID
|
||||
const { displaySetService } = servicesManager.services;
|
||||
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
|
||||
displaySet.referencedSeriesInstanceUID
|
||||
);
|
||||
|
||||
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
|
||||
// Instead of throwing error, subscribe to display sets added
|
||||
const { unsubscribe } = displaySetService.subscribe(
|
||||
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
({ displaySetsAdded }) => {
|
||||
const addedDisplaySet = displaySetsAdded[0];
|
||||
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
|
||||
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
|
||||
throw new Error('Referenced DisplaySet is missing for the RT');
|
||||
}
|
||||
|
||||
} else {
|
||||
const referencedDisplaySet = referencedDisplaySets[0];
|
||||
|
||||
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||
|
||||
return referencedDisplaySet;
|
||||
};
|
||||
}
|
||||
|
||||
displaySet.load = ({ headers }) => _load(displaySet, servicesManager, extensionManager, headers);
|
||||
|
||||
@@ -111,19 +116,13 @@ function _load(rtDisplaySet, servicesManager: AppTypes.ServicesManager, extensio
|
||||
// and also return the same promise to any other callers.
|
||||
loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
|
||||
if (!rtDisplaySet.structureSet) {
|
||||
const structureSet = await loadRTStruct(
|
||||
extensionManager,
|
||||
rtDisplaySet,
|
||||
rtDisplaySet.getReferenceDisplaySet(),
|
||||
headers
|
||||
);
|
||||
const structureSet = await loadRTStruct(extensionManager, rtDisplaySet, headers);
|
||||
|
||||
rtDisplaySet.structureSet = structureSet;
|
||||
}
|
||||
|
||||
const suppressEvents = true;
|
||||
segmentationService
|
||||
.createSegmentationForRTDisplaySet(rtDisplaySet, null, suppressEvents)
|
||||
.createSegmentationForRTDisplaySet(rtDisplaySet)
|
||||
.then(() => {
|
||||
rtDisplaySet.loading = false;
|
||||
resolve();
|
||||
|
||||
@@ -2,6 +2,7 @@ import { id } from './id';
|
||||
import React from 'react';
|
||||
import { Types } from '@ohif/core';
|
||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||
import getCommandsModule from './getCommandsModule';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport');
|
||||
@@ -24,6 +25,7 @@ const extension: Types.Extensions.Extension = {
|
||||
* You ID can be anything you want, but it should be unique.
|
||||
*/
|
||||
id,
|
||||
getCommandsModule,
|
||||
|
||||
/**
|
||||
* PanelModule should provide a list of panels that will be available in OHIF
|
||||
|
||||
@@ -89,12 +89,7 @@ async function checkAndLoadContourData(instance, datasource) {
|
||||
});
|
||||
}
|
||||
|
||||
export default async function loadRTStruct(
|
||||
extensionManager,
|
||||
rtStructDisplaySet,
|
||||
referencedDisplaySet,
|
||||
headers
|
||||
) {
|
||||
export default async function loadRTStruct(extensionManager, rtStructDisplaySet, headers) {
|
||||
const utilityModule = extensionManager.getModuleEntry(
|
||||
'@ohif/extension-cornerstone.utilityModule.common'
|
||||
);
|
||||
@@ -102,8 +97,6 @@ export default async function loadRTStruct(
|
||||
const { bulkDataURI } = dataSource.getConfig?.() || {};
|
||||
|
||||
const { dicomLoaderService } = utilityModule.exports;
|
||||
const imageIdSopInstanceUidPairs =
|
||||
_getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet);
|
||||
|
||||
// Set here is loading is asynchronous.
|
||||
// If this function throws its set back to false.
|
||||
@@ -133,6 +126,7 @@ export default async function loadRTStruct(
|
||||
SeriesInstanceUID: instance.SeriesInstanceUID,
|
||||
ROIContours: [],
|
||||
visible: true,
|
||||
ReferencedSOPInstanceUIDsSet: new Set(),
|
||||
};
|
||||
|
||||
for (let i = 0; i < ROIContourSequence.length; i++) {
|
||||
@@ -149,7 +143,7 @@ export default async function loadRTStruct(
|
||||
|
||||
const contourPoints = [];
|
||||
for (let c = 0; c < ContourSequenceArray.length; c++) {
|
||||
const { ContourImageSequence, ContourData, NumberOfContourPoints, ContourGeometricType } =
|
||||
const { ContourData, NumberOfContourPoints, ContourGeometricType, ContourImageSequence } =
|
||||
ContourSequenceArray[c];
|
||||
|
||||
let isSupported = false;
|
||||
@@ -180,6 +174,12 @@ export default async function loadRTStruct(
|
||||
type: ContourGeometricType,
|
||||
isSupported,
|
||||
});
|
||||
|
||||
if (ContourImageSequence?.ReferencedSOPInstanceUID) {
|
||||
structureSet.ReferencedSOPInstanceUIDsSet.add(
|
||||
ContourImageSequence?.ReferencedSOPInstanceUID
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_setROIContourMetadata(
|
||||
@@ -194,24 +194,6 @@ export default async function loadRTStruct(
|
||||
return structureSet;
|
||||
}
|
||||
|
||||
const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
|
||||
const imageIdSopInstanceUidPairsEntry = imageIdSopInstanceUidPairs.find(
|
||||
imageIdSopInstanceUidPairsEntry =>
|
||||
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
||||
);
|
||||
|
||||
return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
|
||||
};
|
||||
|
||||
function _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet) {
|
||||
return referencedDisplaySet.images.map(image => {
|
||||
return {
|
||||
imageId: image.imageId,
|
||||
sopInstanceUID: image.SOPInstanceUID,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function _setROIContourMetadata(
|
||||
structureSet,
|
||||
StructureSetROISequence,
|
||||
|
||||
@@ -10,7 +10,6 @@ function promptHydrateRT({
|
||||
servicesManager,
|
||||
rtDisplaySet,
|
||||
viewportId,
|
||||
toolGroupId = 'default',
|
||||
preHydrateCallbacks,
|
||||
hydrateRTDisplaySet,
|
||||
}: withAppTypes) {
|
||||
@@ -27,16 +26,13 @@ function promptHydrateRT({
|
||||
callback();
|
||||
});
|
||||
|
||||
window.setTimeout(async () => {
|
||||
const isHydrated = await hydrateRTDisplaySet({
|
||||
rtDisplaySet,
|
||||
viewportId,
|
||||
toolGroupId,
|
||||
servicesManager,
|
||||
});
|
||||
const isHydrated = await hydrateRTDisplaySet({
|
||||
rtDisplaySet,
|
||||
viewportId,
|
||||
servicesManager,
|
||||
});
|
||||
|
||||
resolve(isHydrated);
|
||||
}, 0);
|
||||
resolve(isHydrated);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import { useViewportGrid, LoadingIndicatorTotalPercent, ViewportActionArrows } f
|
||||
|
||||
import promptHydrateRT from '../utils/promptHydrateRT';
|
||||
import _getStatusComponent from './_getStatusComponent';
|
||||
|
||||
import createRTToolGroupAndAddTools from '../utils/initRTToolGroup';
|
||||
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||
|
||||
const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup';
|
||||
|
||||
@@ -41,8 +43,8 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
// States
|
||||
const [isToolGroupCreated, setToolGroupCreated] = useState(false);
|
||||
const [selectedSegment, setSelectedSegment] = useState(1);
|
||||
const { setPositionPresentation } = usePositionPresentationStore();
|
||||
|
||||
// Hydration means that the RT is opened and segments are loaded into the
|
||||
// segmentation panel, and RT is also rendered on any viewport that is in the
|
||||
@@ -62,7 +64,10 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
|
||||
const { viewports, activeViewportId } = viewportGrid;
|
||||
|
||||
const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet();
|
||||
const referencedDisplaySetInstanceUID = rtDisplaySet.referencedDisplaySetInstanceUID;
|
||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(referencedDisplaySet);
|
||||
|
||||
referencedDisplaySetRef.current = {
|
||||
@@ -91,12 +96,15 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
});
|
||||
}, [viewportGrid]);
|
||||
|
||||
const hydrateRTDisplaySet = ({ rtDisplaySet, viewportId }) => {
|
||||
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
displaySets: [rtDisplaySet],
|
||||
viewportId,
|
||||
});
|
||||
};
|
||||
const hydrateRTDisplaySet = useCallback(
|
||||
({ rtDisplaySet, viewportId }) => {
|
||||
commandsManager.runCommand('hydrateRTSDisplaySet', {
|
||||
displaySet: rtDisplaySet,
|
||||
viewportId,
|
||||
});
|
||||
},
|
||||
[commandsManager]
|
||||
);
|
||||
|
||||
const getCornerstoneViewport = useCallback(() => {
|
||||
const { component: Component } = extensionManager.getModuleEntry(
|
||||
@@ -115,6 +123,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
toolGroupId: toolGroupId,
|
||||
orientation: viewportOptions.orientation,
|
||||
viewportId: viewportOptions.viewportId,
|
||||
presentationIds: viewportOptions.presentationIds,
|
||||
}}
|
||||
onElementEnabled={evt => {
|
||||
props.onElementEnabled?.(evt);
|
||||
@@ -143,10 +152,10 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
newSelectedSegmentIndex = numberOfSegments - 1;
|
||||
}
|
||||
|
||||
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId);
|
||||
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, viewportId);
|
||||
setSelectedSegment(newSelectedSegmentIndex);
|
||||
},
|
||||
[selectedSegment]
|
||||
[selectedSegment, segmentationService]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -177,6 +186,19 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
setRtIsLoading(false);
|
||||
}
|
||||
|
||||
if (rtDisplaySet?.firstSegmentedSliceImageId && viewportOptions?.presentationIds) {
|
||||
const { firstSegmentedSliceImageId } = rtDisplaySet;
|
||||
const { presentationIds } = viewportOptions;
|
||||
|
||||
setPositionPresentation(presentationIds.positionPresentationId, {
|
||||
viewportType: 'stack',
|
||||
viewReference: {
|
||||
referencedImageId: firstSegmentedSliceImageId,
|
||||
},
|
||||
viewPresentation: {},
|
||||
});
|
||||
}
|
||||
|
||||
if (evt.overlappingSegments) {
|
||||
uiNotificationService.show({
|
||||
title: 'Overlapping Segments',
|
||||
@@ -239,11 +261,9 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
|
||||
toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
|
||||
|
||||
setToolGroupCreated(true);
|
||||
|
||||
return () => {
|
||||
// remove the segmentation representations if seg displayset changed
|
||||
segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||
segmentationService.removeSegmentationRepresentations(viewportId);
|
||||
|
||||
toolGroupService.destroyToolGroup(toolGroupId);
|
||||
};
|
||||
@@ -254,11 +274,27 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
|
||||
return () => {
|
||||
// remove the segmentation representations if seg displayset changed
|
||||
segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||
segmentationService.removeSegmentationRepresentations(viewportId);
|
||||
referencedDisplaySetRef.current = null;
|
||||
};
|
||||
}, [rtDisplaySet]);
|
||||
|
||||
const onStatusClick = useCallback(async () => {
|
||||
// Before hydrating a RT and make it added to all viewports in the grid
|
||||
// that share the same frameOfReferenceUID, we need to store the viewport grid
|
||||
// presentation state, so that we can restore it after hydrating the RT. This is
|
||||
// required if the user has changed the viewport (other viewport than RT viewport)
|
||||
// presentation state (w/l and invert) and then opens the RT. If we don't store
|
||||
// the presentation state, the viewport will be reset to the default presentation
|
||||
storePresentationState();
|
||||
const isHydrated = await hydrateRTDisplaySet({
|
||||
rtDisplaySet,
|
||||
viewportId,
|
||||
});
|
||||
|
||||
setIsHydrated(isHydrated);
|
||||
}, [hydrateRTDisplaySet, rtDisplaySet, storePresentationState, viewportId]);
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
let childrenWithProps = null;
|
||||
|
||||
@@ -282,24 +318,8 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
});
|
||||
}
|
||||
|
||||
const onStatusClick = useCallback(async () => {
|
||||
// Before hydrating a RT and make it added to all viewports in the grid
|
||||
// that share the same frameOfReferenceUID, we need to store the viewport grid
|
||||
// presentation state, so that we can restore it after hydrating the RT. This is
|
||||
// required if the user has changed the viewport (other viewport than RT viewport)
|
||||
// presentation state (w/l and invert) and then opens the RT. If we don't store
|
||||
// the presentation state, the viewport will be reset to the default presentation
|
||||
storePresentationState();
|
||||
const isHydrated = await hydrateRTDisplaySet({
|
||||
rtDisplaySet,
|
||||
viewportId,
|
||||
});
|
||||
|
||||
setIsHydrated(isHydrated);
|
||||
}, [hydrateRTDisplaySet, rtDisplaySet, storePresentationState, viewportId]);
|
||||
|
||||
useEffect(() => {
|
||||
viewportActionCornersService.setComponents([
|
||||
viewportActionCornersService.addComponents([
|
||||
{
|
||||
viewportId,
|
||||
id: 'viewportStatusComponent',
|
||||
@@ -318,7 +338,7 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
|
||||
key="actionArrows"
|
||||
onArrowsClick={onSegmentChange}
|
||||
className={
|
||||
viewportId === activeViewportId ? 'visible' : 'invisible group-hover:visible'
|
||||
viewportId === activeViewportId ? 'visible' : 'invisible group-hover/pane:visible'
|
||||
}
|
||||
></ViewportActionArrows>
|
||||
),
|
||||
|
||||
@@ -3,6 +3,469 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OHIF 3.9 Recovery 3 ([#4697](https://github.com/OHIF/Viewers/issues/4697)) ([5d4f2e2](https://github.com/OHIF/Viewers/commit/5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modes:** don't attempt to retrieve a stage index if HPs ([#4552](https://github.com/OHIF/Viewers/issues/4552)) ([e5286d9](https://github.com/OHIF/Viewers/commit/e5286d9fc2449da50b738e7e6208d489cce1633e))
|
||||
* **multiframe:** metadata handling of NM studies and loading order ([#4540](https://github.com/OHIF/Viewers/issues/4540)) ([71f06dc](https://github.com/OHIF/Viewers/commit/71f06dcd3e6d24f1e3d931f7db756c26038927da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* video playback ([#4497](https://github.com/OHIF/Viewers/issues/4497)) ([610faa5](https://github.com/OHIF/Viewers/commit/610faa5a2738da5eabc40e57e338c359f481e852))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Measurement Tracking: Various UI and functionality improvements ([#4481](https://github.com/OHIF/Viewers/issues/4481)) ([62b2748](https://github.com/OHIF/Viewers/commit/62b27488471c9d5979142e2d15872a85778b90ed))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bugs:** Update dependencies and enhance UI components ([#4478](https://github.com/OHIF/Viewers/issues/4478)) ([05d41c5](https://github.com/OHIF/Viewers/commit/05d41c52068a3b7ba249f15ecdf71838c352fd30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tmtv:** fix toggle one up weird behaviours ([#4473](https://github.com/OHIF/Viewers/issues/4473)) ([aa2b649](https://github.com/OHIF/Viewers/commit/aa2b649444eb4fe5422e72ea7830a709c4d24a90))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build ([#4471](https://github.com/OHIF/Viewers/issues/4471)) ([3d11ef2](https://github.com/OHIF/Viewers/commit/3d11ef28f213361ec7586809317bd219fa70e742))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** display error in ui while loading seg ([#4433](https://github.com/OHIF/Viewers/issues/4433)) ([2e96371](https://github.com/OHIF/Viewers/commit/2e96371b0631a9e5d411b0142300708ab8ba7d27))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **new-study-panel:** default to list view for non thumbnail series, change default fitler to all, and add more menu to thumbnail items with a dicom tag browser ([#4417](https://github.com/OHIF/Viewers/issues/4417)) ([a7fd9fa](https://github.com/OHIF/Viewers/commit/a7fd9fa5bfff7a1b533d99cb96f7147a35fd528f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **createReport:** early return on cancel in prompt ([#4243](https://github.com/OHIF/Viewers/issues/4243)) ([2ec4692](https://github.com/OHIF/Viewers/commit/2ec4692eaf2349e21b141a2c0b5b104ee10f7a28))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **SR:** SCOORD3D point annotations support for stack viewports ([#4315](https://github.com/OHIF/Viewers/issues/4315)) ([ac1cad2](https://github.com/OHIF/Viewers/commit/ac1cad25af12ee0f7d508647e3134ed724d9b4d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **studies-panel:** New OHIF study panel - under experimental flag ([#4254](https://github.com/OHIF/Viewers/issues/4254)) ([7a96406](https://github.com/OHIF/Viewers/commit/7a96406a116e46e62c396855fa64f434e2984b58))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-seg",
|
||||
"version": "3.9.0-beta.62",
|
||||
"version": "3.9.3",
|
||||
"description": "DICOM SEG read workflow",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -33,10 +33,10 @@
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "3.9.0-beta.62",
|
||||
"@ohif/extension-cornerstone": "3.9.0-beta.62",
|
||||
"@ohif/extension-default": "3.9.0-beta.62",
|
||||
"@ohif/i18n": "3.9.0-beta.62",
|
||||
"@ohif/core": "3.9.3",
|
||||
"@ohif/extension-cornerstone": "3.9.3",
|
||||
"@ohif/extension-default": "3.9.3",
|
||||
"@ohif/i18n": "3.9.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.81.6",
|
||||
"@cornerstonejs/core": "^1.81.6",
|
||||
"@kitware/vtk.js": "30.4.1",
|
||||
"@cornerstonejs/adapters": "^2.2.20",
|
||||
"@cornerstonejs/core": "^2.2.20",
|
||||
"@kitware/vtk.js": "32.1.1",
|
||||
"react-color": "^2.19.3"
|
||||
}
|
||||
}
|
||||
@@ -14,18 +14,22 @@ import vtkImageMarchingSquares from '@kitware/vtk.js/Filters/General/ImageMarchi
|
||||
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
|
||||
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
|
||||
|
||||
import {
|
||||
updateViewportsForSegmentationRendering,
|
||||
getUpdatedViewportsForSegmentation,
|
||||
getTargetViewport,
|
||||
} from './utils/hydrationUtils';
|
||||
const { segmentation: segmentationUtils } = utilities;
|
||||
|
||||
const { datasetToBlob } = dcmjs.data;
|
||||
|
||||
const getTargetViewport = ({ viewportId, viewportGridService }) => {
|
||||
const { viewports, activeViewportId } = viewportGridService.getState();
|
||||
const targetViewportId = viewportId || activeViewportId;
|
||||
|
||||
const viewport = viewports.get(targetViewportId);
|
||||
|
||||
return viewport;
|
||||
};
|
||||
|
||||
const {
|
||||
Cornerstone3D: {
|
||||
Segmentation: { generateLabelMaps2DFrom3D, generateSegmentation },
|
||||
Segmentation: { generateSegmentation },
|
||||
},
|
||||
} = adaptersSEG;
|
||||
|
||||
@@ -42,87 +46,14 @@ const commandsModule = ({
|
||||
extensionManager,
|
||||
}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
|
||||
const {
|
||||
uiNotificationService,
|
||||
segmentationService,
|
||||
uiDialogService,
|
||||
displaySetService,
|
||||
viewportGridService,
|
||||
toolGroupService,
|
||||
cornerstoneViewportService,
|
||||
} = servicesManager.services;
|
||||
} = servicesManager.services as AppTypes.Services;
|
||||
|
||||
const actions = {
|
||||
/**
|
||||
* Retrieves a list of viewports that require updates in preparation for segmentation rendering.
|
||||
* This function evaluates viewports based on their compatibility with the provided segmentation's
|
||||
* frame of reference UID and appends them to the updated list if they should render the segmentation.
|
||||
*
|
||||
* @param {Object} params - Parameters for the function.
|
||||
* @param params.viewportId - the ID of the viewport to be updated.
|
||||
* @param params.servicesManager - The services manager
|
||||
* @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance.
|
||||
*
|
||||
* @returns {Array} Returns an array of viewports that require updates for segmentation rendering.
|
||||
*/
|
||||
getUpdatedViewportsForSegmentation,
|
||||
/**
|
||||
* Creates an empty segmentation for a specified viewport.
|
||||
* It first checks if the display set associated with the viewport is reconstructable.
|
||||
* If not, it raises a notification error. Otherwise, it creates a new segmentation
|
||||
* for the display set after handling the necessary steps for making the viewport
|
||||
* a volume viewport first
|
||||
*
|
||||
* @param {Object} params - Parameters for the function.
|
||||
* @param params.viewportId - the target viewport ID.
|
||||
*
|
||||
*/
|
||||
createEmptySegmentationForViewport: async ({ viewportId }) => {
|
||||
const viewport = getTargetViewport({ viewportId, viewportGridService });
|
||||
// Todo: add support for multiple display sets
|
||||
const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
if (!displaySet.isReconstructable) {
|
||||
uiNotificationService.show({
|
||||
title: 'Segmentation',
|
||||
message: 'Segmentation is not supported for non-reconstructible displaysets yet',
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
updateViewportsForSegmentationRendering({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
displaySet,
|
||||
loadFn: async () => {
|
||||
const currentSegmentations = segmentationService.getSegmentations();
|
||||
const segmentationId = await segmentationService.createSegmentationForDisplaySet(
|
||||
displaySetInstanceUID,
|
||||
{ label: `Segmentation ${currentSegmentations.length + 1}` }
|
||||
);
|
||||
|
||||
const toolGroupId = viewport.viewportOptions.toolGroupId;
|
||||
|
||||
await segmentationService.addSegmentationRepresentationToToolGroup(
|
||||
toolGroupId,
|
||||
segmentationId
|
||||
);
|
||||
|
||||
// Add only one segment for now
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
toolGroupId,
|
||||
segmentIndex: 1,
|
||||
properties: {
|
||||
label: 'Segment 1',
|
||||
},
|
||||
});
|
||||
|
||||
return segmentationId;
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Loads segmentations for a specified viewport.
|
||||
* The function prepares the viewport for rendering, then loads the segmentation details.
|
||||
@@ -134,106 +65,30 @@ const commandsModule = ({
|
||||
*
|
||||
*/
|
||||
loadSegmentationsForViewport: async ({ segmentations, viewportId }) => {
|
||||
updateViewportsForSegmentationRendering({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
loadFn: async () => {
|
||||
// Todo: handle adding more than one segmentation
|
||||
const viewport = getTargetViewport({ viewportId, viewportGridService });
|
||||
const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];
|
||||
|
||||
const segmentation = segmentations[0];
|
||||
const segmentationId = segmentation.id;
|
||||
const label = segmentation.label;
|
||||
const segments = segmentation.segments;
|
||||
|
||||
delete segmentation.segments;
|
||||
|
||||
await segmentationService.createSegmentationForDisplaySet(displaySetInstanceUID, {
|
||||
segmentationId,
|
||||
label,
|
||||
});
|
||||
|
||||
if (segmentation.scalarData) {
|
||||
const labelmapVolume = segmentationService.getLabelmapVolume(segmentationId);
|
||||
labelmapVolume.scalarData.set(segmentation.scalarData);
|
||||
}
|
||||
|
||||
segmentationService.addOrUpdateSegmentation(segmentation);
|
||||
|
||||
const toolGroupId = viewport.viewportOptions.toolGroupId;
|
||||
await segmentationService.addSegmentationRepresentationToToolGroup(
|
||||
toolGroupId,
|
||||
segmentationId
|
||||
);
|
||||
|
||||
segments.forEach(segment => {
|
||||
if (segment === null) {
|
||||
return;
|
||||
}
|
||||
segmentationService.addSegment(segmentationId, {
|
||||
segmentIndex: segment.segmentIndex,
|
||||
toolGroupId,
|
||||
properties: {
|
||||
color: segment.color,
|
||||
label: segment.label,
|
||||
opacity: segment.opacity,
|
||||
isLocked: segment.isLocked,
|
||||
visibility: segment.isVisible,
|
||||
active: segmentation.activeSegmentIndex === segment.segmentIndex,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (segmentation.centroidsIJK) {
|
||||
segmentationService.setCentroids(segmentation.id, segmentation.centroidsIJK);
|
||||
}
|
||||
|
||||
return segmentationId;
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Loads segmentation display sets for a specified viewport.
|
||||
* Depending on the modality of the display set (SEG or RTSTRUCT),
|
||||
* it chooses the appropriate service function to create
|
||||
* the segmentation for the display set.
|
||||
* The function then prepares the viewport for rendering segmentation.
|
||||
*
|
||||
* @param {Object} params - Parameters for the function.
|
||||
* @param params.viewportId - ID of the viewport where the segmentation display sets should be loaded.
|
||||
* @param params.displaySets - Array of display sets to be loaded for segmentation.
|
||||
*
|
||||
*/
|
||||
loadSegmentationDisplaySetsForViewport: async ({ viewportId, displaySets }) => {
|
||||
// Todo: handle adding more than one segmentation
|
||||
const displaySet = displaySets[0];
|
||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
displaySet.referencedDisplaySetInstanceUID
|
||||
);
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
const initialSliceIndex = viewport.getSliceIndex();
|
||||
const viewport = getTargetViewport({ viewportId, viewportGridService });
|
||||
const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0];
|
||||
|
||||
updateViewportsForSegmentationRendering({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
displaySet,
|
||||
loadFn: async () => {
|
||||
const segDisplaySet = displaySet;
|
||||
const suppressEvents = false;
|
||||
const serviceFunction =
|
||||
segDisplaySet.Modality === 'SEG'
|
||||
? 'createSegmentationForSEGDisplaySet'
|
||||
: 'createSegmentationForRTDisplaySet';
|
||||
const segmentation = segmentations[0];
|
||||
const segmentationId = segmentation.segmentationId;
|
||||
const label = segmentation.config.label;
|
||||
const segments = segmentation.config.segments;
|
||||
|
||||
const boundFn = segmentationService[serviceFunction].bind(segmentationService);
|
||||
const segmentationId = await boundFn(segDisplaySet, null, suppressEvents);
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
segmentation.description = `S${referencedDisplaySet.SeriesNumber}: ${referencedDisplaySet.SeriesDescription}`;
|
||||
return segmentationId;
|
||||
},
|
||||
initialSliceIndex,
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
await segmentationService.createLabelmapForDisplaySet(displaySet, {
|
||||
segmentationId,
|
||||
segments,
|
||||
label,
|
||||
});
|
||||
|
||||
segmentationService.addOrUpdateSegmentation(segmentation);
|
||||
|
||||
await segmentationService.addSegmentationRepresentation(viewport.viewportId, {
|
||||
segmentationId,
|
||||
});
|
||||
|
||||
return segmentationId;
|
||||
},
|
||||
/**
|
||||
* Generates a segmentation from a given segmentation ID.
|
||||
@@ -251,25 +106,61 @@ const commandsModule = ({
|
||||
generateSegmentation: ({ segmentationId, options = {} }) => {
|
||||
const segmentation = cornerstoneToolsSegmentation.state.getSegmentation(segmentationId);
|
||||
|
||||
const { referencedVolumeId } = segmentation.representationData.LABELMAP;
|
||||
const { imageIds } = segmentation.representationData.Labelmap;
|
||||
|
||||
const segmentationVolume = cache.getVolume(segmentationId);
|
||||
const referencedVolume = cache.getVolume(referencedVolumeId);
|
||||
const referencedImages = referencedVolume.getCornerstoneImages();
|
||||
const segImages = imageIds.map(imageId => cache.getImage(imageId));
|
||||
const referencedImages = segImages.map(image => cache.getImage(image.referencedImageId));
|
||||
|
||||
const labelmapObj = generateLabelMaps2DFrom3D(segmentationVolume);
|
||||
const labelmaps2D = [];
|
||||
|
||||
// Generate fake metadata as an example
|
||||
labelmapObj.metadata = [];
|
||||
let z = 0;
|
||||
|
||||
for (const segImage of segImages) {
|
||||
const segmentsOnLabelmap = new Set();
|
||||
const pixelData = segImage.getPixelData();
|
||||
const { rows, columns } = segImage;
|
||||
|
||||
// Use a single pass through the pixel data
|
||||
for (let i = 0; i < pixelData.length; i++) {
|
||||
const segment = pixelData[i];
|
||||
if (segment !== 0) {
|
||||
segmentsOnLabelmap.add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
labelmaps2D[z++] = {
|
||||
segmentsOnLabelmap: Array.from(segmentsOnLabelmap),
|
||||
pixelData,
|
||||
rows,
|
||||
columns,
|
||||
};
|
||||
}
|
||||
|
||||
const allSegmentsOnLabelmap = labelmaps2D.map(labelmap => labelmap.segmentsOnLabelmap);
|
||||
|
||||
const labelmap3D = {
|
||||
segmentsOnLabelmap: Array.from(new Set(allSegmentsOnLabelmap.flat())),
|
||||
metadata: [],
|
||||
labelmaps2D,
|
||||
};
|
||||
|
||||
const segmentationInOHIF = segmentationService.getSegmentation(segmentationId);
|
||||
segmentationInOHIF.segments.forEach(segment => {
|
||||
const representations = segmentationService.getRepresentationsForSegmentation(segmentationId);
|
||||
|
||||
Object.entries(segmentationInOHIF.segments).forEach(([segmentIndex, segment]) => {
|
||||
// segmentation service already has a color for each segment
|
||||
if (!segment) {
|
||||
return;
|
||||
}
|
||||
const segmentIndex = segment.segmentIndex;
|
||||
const { label, color } = segment;
|
||||
|
||||
const { label } = segment;
|
||||
|
||||
const firstRepresentation = representations[0];
|
||||
const color = segmentationService.getSegmentColor(
|
||||
firstRepresentation.viewportId,
|
||||
segmentationId,
|
||||
segment.segmentIndex
|
||||
);
|
||||
|
||||
const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB(
|
||||
color.slice(0, 3).map(value => value / 255)
|
||||
@@ -292,12 +183,12 @@ const commandsModule = ({
|
||||
CodeMeaning: 'Tissue',
|
||||
},
|
||||
};
|
||||
labelmapObj.metadata[segmentIndex] = segmentMetadata;
|
||||
labelmap3D.metadata[segmentIndex] = segmentMetadata;
|
||||
});
|
||||
|
||||
const generatedSegmentation = generateSegmentation(
|
||||
referencedImages,
|
||||
labelmapObj,
|
||||
labelmap3D,
|
||||
metaData,
|
||||
options
|
||||
);
|
||||
@@ -339,7 +230,7 @@ const commandsModule = ({
|
||||
extensionManager,
|
||||
});
|
||||
|
||||
if (promptResult.action !== 1 && promptResult.value) {
|
||||
if (promptResult.action !== 1 && !promptResult.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -445,18 +336,19 @@ const commandsModule = ({
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
getUpdatedViewportsForSegmentation: {
|
||||
commandFn: actions.getUpdatedViewportsForSegmentation,
|
||||
},
|
||||
/**
|
||||
* Obsolete?
|
||||
*/
|
||||
loadSegmentationDisplaySetsForViewport: {
|
||||
commandFn: actions.loadSegmentationDisplaySetsForViewport,
|
||||
},
|
||||
/**
|
||||
* Obsolete?
|
||||
*/
|
||||
loadSegmentationsForViewport: {
|
||||
commandFn: actions.loadSegmentationsForViewport,
|
||||
},
|
||||
createEmptySegmentationForViewport: {
|
||||
commandFn: actions.createEmptySegmentationForViewport,
|
||||
},
|
||||
|
||||
generateSegmentation: {
|
||||
commandFn: actions.generateSegmentation,
|
||||
},
|
||||
|
||||
@@ -20,6 +20,17 @@ const segProtocol: Types.HangingProtocol.Protocol = {
|
||||
viewportType: 'stack',
|
||||
toolGroupId: 'default',
|
||||
allowUnmatchedView: true,
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
// options: {
|
||||
// matchingRules: ['sameFOR'],
|
||||
// },
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
@@ -52,7 +63,20 @@ const segProtocol: Types.HangingProtocol.Protocol = {
|
||||
},
|
||||
viewports: [
|
||||
{
|
||||
viewportOptions: { allowUnmatchedView: true },
|
||||
viewportOptions: {
|
||||
allowUnmatchedView: true,
|
||||
syncGroups: [
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
// options: {
|
||||
// matchingRules: ['sameFOR'],
|
||||
// },
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
{
|
||||
id: 'segDisplaySetId',
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useAppConfig } from '@state';
|
||||
import { Toolbox } from '@ohif/ui';
|
||||
import PanelSegmentation from './panels/PanelSegmentation';
|
||||
|
||||
const getPanelModule = ({
|
||||
commandsManager,
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
configuration,
|
||||
title,
|
||||
}: withAppTypes) => {
|
||||
const { customizationService } = servicesManager.services;
|
||||
|
||||
const wrappedPanelSegmentation = configuration => {
|
||||
const [appConfig] = useAppConfig();
|
||||
|
||||
return (
|
||||
<PanelSegmentation
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
configuration={{
|
||||
...configuration,
|
||||
disableEditing: appConfig.disableEditing,
|
||||
...customizationService.get('segmentation.panel'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const wrappedPanelSegmentationWithTools = configuration => {
|
||||
const [appConfig] = useAppConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbox
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
buttonSectionId="segmentationToolbox"
|
||||
title="Segmentation Tools"
|
||||
configuration={{
|
||||
...configuration,
|
||||
}}
|
||||
/>
|
||||
<PanelSegmentation
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
configuration={{
|
||||
...configuration,
|
||||
disableEditing: appConfig.disableEditing,
|
||||
...customizationService.get('segmentation.panel'),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'panelSegmentation',
|
||||
iconName: 'tab-segmentation',
|
||||
iconLabel: 'Segmentation',
|
||||
label: 'Segmentation',
|
||||
component: wrappedPanelSegmentation,
|
||||
},
|
||||
{
|
||||
name: 'panelSegmentationWithTools',
|
||||
iconName: 'tab-segmentation',
|
||||
iconLabel: 'Segmentation',
|
||||
label: 'Segmentation',
|
||||
component: wrappedPanelSegmentationWithTools,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default getPanelModule;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { utils } from '@ohif/core';
|
||||
import { metaData, cache, triggerEvent, eventTarget } from '@cornerstonejs/core';
|
||||
import { CONSTANTS } from '@cornerstonejs/tools';
|
||||
import { metaData, triggerEvent, eventTarget } from '@cornerstonejs/core';
|
||||
import { CONSTANTS, segmentation as cstSegmentation } from '@cornerstonejs/tools';
|
||||
import { adaptersSEG, Enums } from '@cornerstonejs/adapters';
|
||||
|
||||
import { SOPClassHandlerId } from './id';
|
||||
@@ -70,28 +70,32 @@ function _getDisplaySetsFromSeries(
|
||||
|
||||
displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence;
|
||||
displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID;
|
||||
const { displaySetService } = servicesManager.services;
|
||||
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
|
||||
displaySet.referencedSeriesInstanceUID
|
||||
);
|
||||
|
||||
displaySet.getReferenceDisplaySet = () => {
|
||||
const { displaySetService } = servicesManager.services;
|
||||
const referencedDisplaySets = displaySetService.getDisplaySetsForSeries(
|
||||
displaySet.referencedSeriesInstanceUID
|
||||
const referencedDisplaySet = referencedDisplaySets[0];
|
||||
|
||||
if (!referencedDisplaySet) {
|
||||
// subscribe to display sets added which means at some point it will be available
|
||||
const { unsubscribe } = displaySetService.subscribe(
|
||||
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
({ displaySetsAdded }) => {
|
||||
// here we can also do a little bit of search, since sometimes DICOM SEG
|
||||
// does not contain the referenced display set uid , and we can just
|
||||
// see which of the display sets added is more similar and assign it
|
||||
// to the referencedDisplaySet
|
||||
const addedDisplaySet = displaySetsAdded[0];
|
||||
if (addedDisplaySet.SeriesInstanceUID === displaySet.referencedSeriesInstanceUID) {
|
||||
displaySet.referencedDisplaySetInstanceUID = addedDisplaySet.displaySetInstanceUID;
|
||||
unsubscribe();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!referencedDisplaySets || referencedDisplaySets.length === 0) {
|
||||
throw new Error('Referenced DisplaySet is missing for the SEG');
|
||||
}
|
||||
|
||||
const referencedDisplaySet = referencedDisplaySets[0];
|
||||
|
||||
} else {
|
||||
displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID;
|
||||
|
||||
// Todo: this needs to be able to work with other reference volumes (other than streaming) such as nifti, etc.
|
||||
displaySet.referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID;
|
||||
const referencedVolumeId = `cornerstoneStreamingImageVolume:${displaySet.referencedVolumeURI}`;
|
||||
displaySet.referencedVolumeId = referencedVolumeId;
|
||||
|
||||
return referencedDisplaySet;
|
||||
};
|
||||
}
|
||||
|
||||
displaySet.load = async ({ headers }) =>
|
||||
await _load(displaySet, servicesManager, extensionManager, headers);
|
||||
@@ -111,7 +115,7 @@ function _load(
|
||||
if (
|
||||
(segDisplaySet.loading || segDisplaySet.isLoaded) &&
|
||||
loadPromises[SOPInstanceUID] &&
|
||||
_segmentationExists(segDisplaySet, segmentationService)
|
||||
_segmentationExists(segDisplaySet)
|
||||
) {
|
||||
return loadPromises[SOPInstanceUID];
|
||||
}
|
||||
@@ -122,17 +126,21 @@ function _load(
|
||||
// and also return the same promise to any other callers.
|
||||
loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => {
|
||||
if (!segDisplaySet.segments || Object.keys(segDisplaySet.segments).length === 0) {
|
||||
await _loadSegments({
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
segDisplaySet,
|
||||
headers,
|
||||
});
|
||||
try {
|
||||
await _loadSegments({
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
segDisplaySet,
|
||||
headers,
|
||||
});
|
||||
} catch (e) {
|
||||
segDisplaySet.loading = false;
|
||||
return reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
const suppressEvents = true;
|
||||
segmentationService
|
||||
.createSegmentationForSEGDisplaySet(segDisplaySet, null, suppressEvents)
|
||||
.createSegmentationForSEGDisplaySet(segDisplaySet)
|
||||
.then(() => {
|
||||
segDisplaySet.loading = false;
|
||||
resolve();
|
||||
@@ -161,20 +169,20 @@ async function _loadSegments({
|
||||
const { dicomLoaderService } = utilityModule.exports;
|
||||
const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers);
|
||||
|
||||
const cachedReferencedVolume = cache.getVolume(segDisplaySet.referencedVolumeId);
|
||||
const referencedDisplaySet = servicesManager.services.displaySetService.getDisplaySetByUID(
|
||||
segDisplaySet.referencedDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
if (!cachedReferencedVolume) {
|
||||
throw new Error(
|
||||
'Referenced Volume is missing for the SEG, and stack viewport SEG is not supported yet'
|
||||
);
|
||||
if (!referencedDisplaySet) {
|
||||
throw new Error('referencedDisplaySet is missing for SEG');
|
||||
}
|
||||
|
||||
const { imageIds } = cachedReferencedVolume;
|
||||
const { instances: images } = referencedDisplaySet;
|
||||
const imageIds = images.map(({ imageId }) => imageId);
|
||||
|
||||
// Todo: what should be defaults here
|
||||
const tolerance = 0.001;
|
||||
const skipOverlapping = true;
|
||||
|
||||
eventTarget.addEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, evt => {
|
||||
const { percentComplete } = evt.detail;
|
||||
segmentationService._broadcastEvent(segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE, {
|
||||
@@ -226,9 +234,8 @@ async function _loadSegments({
|
||||
Object.assign(segDisplaySet, results);
|
||||
}
|
||||
|
||||
function _segmentationExists(segDisplaySet, segmentationService: AppTypes.SegmentationService) {
|
||||
// This should be abstracted with the CornerstoneCacheService
|
||||
return segmentationService.getSegmentation(segDisplaySet.displaySetInstanceUID);
|
||||
function _segmentationExists(segDisplaySet) {
|
||||
return cstSegmentation.state.getSegmentation(segDisplaySet.displaySetInstanceUID);
|
||||
}
|
||||
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
|
||||
@@ -8,7 +8,7 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
|
||||
// forcing the button to have black background since initially
|
||||
// it is designed for the toolbox not the toolbar on top
|
||||
// we should then branch the buttonSectionId to have different styles
|
||||
const segmentations = segmentationService.getSegmentations();
|
||||
const segmentations = segmentationService.getSegmentationRepresentations(viewportId);
|
||||
if (!segmentations?.length) {
|
||||
return {
|
||||
disabled: true,
|
||||
|
||||
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
|
||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||
import getHangingProtocolModule from './getHangingProtocolModule';
|
||||
import getPanelModule from './getPanelModule';
|
||||
import getCommandsModule from './commandsModule';
|
||||
import { getToolbarModule } from './getToolbarModule';
|
||||
|
||||
@@ -28,13 +27,6 @@ const extension = {
|
||||
* You ID can be anything you want, but it should be unique.
|
||||
*/
|
||||
id,
|
||||
/**
|
||||
* PanelModule should provide a list of panels that will be available in OHIF
|
||||
* for Modes to consume and render. Each panel is defined by a {name,
|
||||
* iconName, iconLabel, label, component} object. Example of a panel module
|
||||
* is the StudyBrowserPanel that is provided by the default extension in OHIF.
|
||||
*/
|
||||
getPanelModule,
|
||||
getCommandsModule,
|
||||
getToolbarModule,
|
||||
getViewportModule({ servicesManager, extensionManager, commandsManager }) {
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
import { createReportAsync } from '@ohif/extension-default';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SegmentationGroupTable, SegmentationGroupTableExpanded } from '@ohif/ui';
|
||||
import { SegmentationPanelMode } from '../types/segmentation';
|
||||
import callInputDialog from './callInputDialog';
|
||||
import callColorPickerDialog from './colorPickerDialog';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const components = {
|
||||
[SegmentationPanelMode.Expanded]: SegmentationGroupTableExpanded,
|
||||
[SegmentationPanelMode.Dropdown]: SegmentationGroupTable,
|
||||
};
|
||||
|
||||
export default function PanelSegmentation({
|
||||
servicesManager,
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
configuration,
|
||||
}: withAppTypes) {
|
||||
const {
|
||||
segmentationService,
|
||||
viewportGridService,
|
||||
uiDialogService,
|
||||
displaySetService,
|
||||
cornerstoneViewportService,
|
||||
} = servicesManager.services;
|
||||
|
||||
const { t } = useTranslation('PanelSegmentation');
|
||||
|
||||
const [selectedSegmentationId, setSelectedSegmentationId] = useState(null);
|
||||
const [addSegmentationClassName, setAddSegmentationClassName] = useState('');
|
||||
const [segmentationConfiguration, setSegmentationConfiguration] = useState(
|
||||
segmentationService.getConfiguration()
|
||||
);
|
||||
|
||||
const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations());
|
||||
|
||||
useEffect(() => {
|
||||
// ~~ Subscription
|
||||
const added = segmentationService.EVENTS.SEGMENTATION_ADDED;
|
||||
const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED;
|
||||
const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED;
|
||||
const subscriptions = [];
|
||||
|
||||
[added, updated, removed].forEach(evt => {
|
||||
const { unsubscribe } = segmentationService.subscribe(evt, () => {
|
||||
const segmentations = segmentationService.getSegmentations();
|
||||
setSegmentations(segmentations);
|
||||
setSegmentationConfiguration(segmentationService.getConfiguration());
|
||||
});
|
||||
subscriptions.push(unsubscribe);
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach(unsub => {
|
||||
unsub();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// temporary measure to not allow add segmentation when the selected viewport
|
||||
// is stack viewport
|
||||
useEffect(() => {
|
||||
const handleActiveViewportChange = viewportId => {
|
||||
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(
|
||||
viewportId || viewportGridService.getActiveViewportId()
|
||||
);
|
||||
|
||||
if (!displaySetUIDs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isReconstructable =
|
||||
displaySetUIDs?.some(displaySetUID => {
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetUID);
|
||||
return displaySet?.isReconstructable;
|
||||
}) || false;
|
||||
|
||||
if (isReconstructable) {
|
||||
setAddSegmentationClassName('');
|
||||
} else {
|
||||
setAddSegmentationClassName('ohif-disabled');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle initial state
|
||||
handleActiveViewportChange();
|
||||
|
||||
const changedGrid = viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED;
|
||||
const ready = viewportGridService.EVENTS.VIEWPORTS_READY;
|
||||
|
||||
const subsGrid = [];
|
||||
[ready, changedGrid].forEach(evt => {
|
||||
const { unsubscribe } = viewportGridService.subscribe(evt, ({ viewportId }) => {
|
||||
handleActiveViewportChange(viewportId);
|
||||
});
|
||||
|
||||
subsGrid.push(unsubscribe);
|
||||
});
|
||||
|
||||
const changedData = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED;
|
||||
|
||||
const subsData = [];
|
||||
[changedData].forEach(evt => {
|
||||
const { unsubscribe } = cornerstoneViewportService.subscribe(evt, () => {
|
||||
handleActiveViewportChange();
|
||||
});
|
||||
|
||||
subsData.push(unsubscribe);
|
||||
});
|
||||
|
||||
// Clean up
|
||||
return () => {
|
||||
subsGrid.forEach(unsub => unsub());
|
||||
subsData.forEach(unsub => unsub());
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getToolGroupIds = segmentationId => {
|
||||
const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId);
|
||||
|
||||
return toolGroupIds;
|
||||
};
|
||||
|
||||
const onSegmentationAdd = async () => {
|
||||
commandsManager.runCommand('createEmptySegmentationForViewport', {
|
||||
viewportId: viewportGridService.getActiveViewportId(),
|
||||
});
|
||||
};
|
||||
|
||||
const onSegmentationClick = (segmentationId: string) => {
|
||||
segmentationService.setActiveSegmentationForToolGroup(segmentationId);
|
||||
};
|
||||
|
||||
const onSegmentationDelete = (segmentationId: string) => {
|
||||
segmentationService.remove(segmentationId);
|
||||
};
|
||||
|
||||
const onSegmentAdd = segmentationId => {
|
||||
segmentationService.addSegment(segmentationId);
|
||||
};
|
||||
|
||||
const onSegmentClick = (segmentationId, segmentIndex) => {
|
||||
segmentationService.setActiveSegment(segmentationId, segmentIndex);
|
||||
|
||||
const toolGroupIds = getToolGroupIds(segmentationId);
|
||||
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
// const toolGroupId =
|
||||
segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
|
||||
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, toolGroupId);
|
||||
});
|
||||
};
|
||||
|
||||
const onSegmentEdit = (segmentationId, segmentIndex) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
const segment = segmentation.segments[segmentIndex];
|
||||
const { label } = segment;
|
||||
|
||||
callInputDialog(uiDialogService, label, (label, actionId) => {
|
||||
if (label === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
|
||||
});
|
||||
};
|
||||
|
||||
const onSegmentationEdit = segmentationId => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
const { label } = segmentation;
|
||||
|
||||
callInputDialog(uiDialogService, label, (label, actionId) => {
|
||||
if (label === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentationService.addOrUpdateSegmentation(
|
||||
{
|
||||
id: segmentationId,
|
||||
label,
|
||||
},
|
||||
false, // suppress event
|
||||
true // notYetUpdatedAtSource
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const onSegmentColorClick = (segmentationId, segmentIndex) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
const segment = segmentation.segments[segmentIndex];
|
||||
const { color, opacity } = segment;
|
||||
|
||||
const rgbaColor = {
|
||||
r: color[0],
|
||||
g: color[1],
|
||||
b: color[2],
|
||||
a: opacity / 255.0,
|
||||
};
|
||||
|
||||
callColorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
|
||||
if (actionId === 'cancel') {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentationService.setSegmentRGBAColor(segmentationId, segmentIndex, [
|
||||
newRgbaColor.r,
|
||||
newRgbaColor.g,
|
||||
newRgbaColor.b,
|
||||
newRgbaColor.a * 255.0,
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
const onSegmentDelete = (segmentationId, segmentIndex) => {
|
||||
segmentationService.removeSegment(segmentationId, segmentIndex);
|
||||
};
|
||||
|
||||
// segment hide
|
||||
const onToggleSegmentVisibility = (segmentationId, segmentIndex) => {
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
const segmentInfo = segmentation.segments[segmentIndex];
|
||||
const isVisible = !segmentInfo.isVisible;
|
||||
const toolGroupIds = getToolGroupIds(segmentationId);
|
||||
|
||||
// Todo: right now we apply the visibility to all tool groups
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
segmentationService.setSegmentVisibility(
|
||||
segmentationId,
|
||||
segmentIndex,
|
||||
isVisible,
|
||||
toolGroupId
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const onToggleSegmentLock = (segmentationId, segmentIndex) => {
|
||||
segmentationService.toggleSegmentLocked(segmentationId, segmentIndex);
|
||||
};
|
||||
|
||||
const onToggleSegmentationVisibility = segmentationId => {
|
||||
segmentationService.toggleSegmentationVisibility(segmentationId);
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
const isVisible = segmentation.isVisible;
|
||||
const segments = segmentation.segments;
|
||||
|
||||
const toolGroupIds = getToolGroupIds(segmentationId);
|
||||
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
segments.forEach((segment, segmentIndex) => {
|
||||
segmentationService.setSegmentVisibility(
|
||||
segmentationId,
|
||||
segmentIndex,
|
||||
isVisible,
|
||||
toolGroupId
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const _setSegmentationConfiguration = useCallback(
|
||||
(segmentationId, key, value) => {
|
||||
segmentationService.setConfiguration({
|
||||
segmentationId,
|
||||
[key]: value,
|
||||
});
|
||||
},
|
||||
[segmentationService]
|
||||
);
|
||||
|
||||
const onSegmentationDownload = segmentationId => {
|
||||
commandsManager.runCommand('downloadSegmentation', {
|
||||
segmentationId,
|
||||
});
|
||||
};
|
||||
|
||||
const storeSegmentation = async segmentationId => {
|
||||
const datasources = extensionManager.getActiveDataSource();
|
||||
|
||||
const displaySetInstanceUIDs = await createReportAsync({
|
||||
servicesManager,
|
||||
getReport: () =>
|
||||
commandsManager.runCommand('storeSegmentation', {
|
||||
segmentationId,
|
||||
dataSource: datasources[0],
|
||||
}),
|
||||
reportType: 'Segmentation',
|
||||
});
|
||||
|
||||
// Show the exported report in the active viewport as read only (similar to SR)
|
||||
if (displaySetInstanceUIDs) {
|
||||
// clear the segmentation that we exported, similar to the storeMeasurement
|
||||
// where we remove the measurements and prompt again the user if they would like
|
||||
// to re-read the measurements in a SR read only viewport
|
||||
segmentationService.remove(segmentationId);
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId: viewportGridService.getActiveViewportId(),
|
||||
displaySetInstanceUIDs,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSegmentationDownloadRTSS = segmentationId => {
|
||||
commandsManager.runCommand('downloadRTSS', {
|
||||
segmentationId,
|
||||
});
|
||||
};
|
||||
|
||||
const SegmentationGroupTableComponent =
|
||||
components[configuration?.segmentationPanelMode] || SegmentationGroupTable;
|
||||
const allowAddSegment = configuration?.addSegment;
|
||||
const onSegmentationAddWrapper =
|
||||
configuration?.onSegmentationAdd && typeof configuration?.onSegmentationAdd === 'function'
|
||||
? configuration?.onSegmentationAdd
|
||||
: onSegmentationAdd;
|
||||
|
||||
return (
|
||||
<SegmentationGroupTableComponent
|
||||
title={t('Segmentations')}
|
||||
segmentations={segmentations}
|
||||
disableEditing={configuration.disableEditing}
|
||||
activeSegmentationId={selectedSegmentationId || ''}
|
||||
onSegmentationAdd={onSegmentationAddWrapper}
|
||||
addSegmentationClassName={addSegmentationClassName}
|
||||
showAddSegment={allowAddSegment}
|
||||
onSegmentationClick={onSegmentationClick}
|
||||
onSegmentationDelete={onSegmentationDelete}
|
||||
onSegmentationDownload={onSegmentationDownload}
|
||||
onSegmentationDownloadRTSS={onSegmentationDownloadRTSS}
|
||||
storeSegmentation={storeSegmentation}
|
||||
onSegmentationEdit={onSegmentationEdit}
|
||||
onSegmentClick={onSegmentClick}
|
||||
onSegmentEdit={onSegmentEdit}
|
||||
onSegmentAdd={onSegmentAdd}
|
||||
onSegmentColorClick={onSegmentColorClick}
|
||||
onSegmentDelete={onSegmentDelete}
|
||||
onToggleSegmentVisibility={onToggleSegmentVisibility}
|
||||
onToggleSegmentLock={onToggleSegmentLock}
|
||||
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
|
||||
showDeleteSegment={true}
|
||||
segmentationConfig={{ initialConfig: segmentationConfiguration }}
|
||||
setRenderOutline={value =>
|
||||
_setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value)
|
||||
}
|
||||
setOutlineOpacityActive={value =>
|
||||
_setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value)
|
||||
}
|
||||
setRenderFill={value =>
|
||||
_setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value)
|
||||
}
|
||||
setRenderInactiveSegmentations={value =>
|
||||
_setSegmentationConfiguration(selectedSegmentationId, 'renderInactiveSegmentations', value)
|
||||
}
|
||||
setOutlineWidthActive={value =>
|
||||
_setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value)
|
||||
}
|
||||
setFillAlpha={value =>
|
||||
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value)
|
||||
}
|
||||
setFillAlphaInactive={value =>
|
||||
_setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
PanelSegmentation.propTypes = {
|
||||
commandsManager: PropTypes.shape({
|
||||
runCommand: PropTypes.func.isRequired,
|
||||
}),
|
||||
servicesManager: PropTypes.shape({
|
||||
services: PropTypes.shape({
|
||||
segmentationService: PropTypes.shape({
|
||||
getSegmentation: PropTypes.func.isRequired,
|
||||
getSegmentations: PropTypes.func.isRequired,
|
||||
toggleSegmentationVisibility: PropTypes.func.isRequired,
|
||||
subscribe: PropTypes.func.isRequired,
|
||||
EVENTS: PropTypes.object.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Input, Dialog, ButtonEnums } from '@ohif/ui';
|
||||
|
||||
function callInputDialog(uiDialogService, label, callback) {
|
||||
const dialogId = 'enter-segment-label';
|
||||
|
||||
const onSubmitHandler = ({ action, value }) => {
|
||||
switch (action.id) {
|
||||
case 'save':
|
||||
callback(value.label, action.id);
|
||||
break;
|
||||
case 'cancel':
|
||||
callback('', action.id);
|
||||
break;
|
||||
}
|
||||
uiDialogService.dismiss({ id: dialogId });
|
||||
};
|
||||
|
||||
if (uiDialogService) {
|
||||
uiDialogService.create({
|
||||
id: dialogId,
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
showOverlay: true,
|
||||
content: Dialog,
|
||||
contentProps: {
|
||||
title: 'Segment',
|
||||
value: { label },
|
||||
noCloseButton: true,
|
||||
onClose: () => uiDialogService.dismiss({ id: dialogId }),
|
||||
actions: [
|
||||
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
|
||||
{ id: 'save', text: 'Confirm', type: ButtonEnums.type.primary },
|
||||
],
|
||||
onSubmit: onSubmitHandler,
|
||||
body: ({ value, setValue }) => {
|
||||
return (
|
||||
<Input
|
||||
label="Enter the segment label"
|
||||
labelClassName="text-white text-[14px] leading-[1.2]"
|
||||
autoFocus
|
||||
className="border-primary-main bg-black"
|
||||
type="text"
|
||||
value={value.label}
|
||||
onChange={event => {
|
||||
event.persist();
|
||||
setValue(value => ({ ...value, label: event.target.value }));
|
||||
}}
|
||||
onKeyPress={event => {
|
||||
if (event.key === 'Enter') {
|
||||
onSubmitHandler({ value, action: { id: 'save' } });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default callInputDialog;
|
||||
@@ -1,33 +0,0 @@
|
||||
// Todo: use defaults in cs3d
|
||||
const initialState = {
|
||||
renderOutline: true,
|
||||
renderFill: true,
|
||||
outlineOpacity: 0.9,
|
||||
outlineWidth: 3,
|
||||
fillOpacity: 0.9,
|
||||
fillOpacityInactive: 0.8,
|
||||
renderInactiveSegmentations: true,
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'RENDER_OUTLINE':
|
||||
return { ...state, renderOutline: action.payload.value };
|
||||
case 'RENDER_FILL':
|
||||
return { ...state, renderFill: action.payload.value };
|
||||
case 'SET_OUTLINE_OPACITY':
|
||||
return { ...state, outlineOpacity: action.payload.value };
|
||||
case 'SET_OUTLINE_WIDTH':
|
||||
return { ...state, outlineWidth: action.payload.value };
|
||||
case 'SET_FILL_OPACITY':
|
||||
return { ...state, fillOpacity: action.payload.value };
|
||||
case 'SET_FILL_OPACITY_INACTIVE':
|
||||
return { ...state, fillOpacityInactive: action.payload.value };
|
||||
case 'RENDER_INACTIVE_SEGMENTATIONS':
|
||||
return { ...state, renderInactiveSegmentations: action.payload.value };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { initialState, reducer };
|
||||
@@ -1,203 +0,0 @@
|
||||
import { Enums, cache } from '@cornerstonejs/core';
|
||||
|
||||
/**
|
||||
* Updates the viewports in preparation for rendering segmentations.
|
||||
* Evaluates each viewport to determine which need modifications,
|
||||
* then for those viewports, changes them to a volume type and ensures
|
||||
* they are ready for segmentation rendering.
|
||||
*
|
||||
* @param {Object} params - Parameters for the function.
|
||||
* @param params.viewportId - ID of the viewport to be updated.
|
||||
* @param params.loadFn - Function to load the segmentation data.
|
||||
* @param params.servicesManager - The services manager.
|
||||
* @param params.displaySet - the display set.
|
||||
* @param params.initialSliceIndex - The initial slice index.
|
||||
*
|
||||
* @returns Returns true upon successful update of viewports for segmentation rendering.
|
||||
*/
|
||||
async function updateViewportsForSegmentationRendering({
|
||||
viewportId,
|
||||
loadFn,
|
||||
servicesManager,
|
||||
displaySet,
|
||||
initialSliceIndex = null,
|
||||
}: {
|
||||
viewportId: string;
|
||||
loadFn: () => Promise<string>;
|
||||
servicesManager: AppTypes.ServicesManager;
|
||||
displaySet?: any;
|
||||
initialSliceIndex?: number;
|
||||
}) {
|
||||
const { cornerstoneViewportService, segmentationService, viewportGridService } =
|
||||
servicesManager.services;
|
||||
|
||||
const viewport = getTargetViewport({ viewportId, viewportGridService });
|
||||
const targetViewportId = viewport.viewportOptions.viewportId;
|
||||
|
||||
const referencedDisplaySetInstanceUID =
|
||||
displaySet?.referencedDisplaySetInstanceUID || viewport?.displaySetInstanceUIDs[0];
|
||||
|
||||
const updatedViewports = getUpdatedViewportsForSegmentation({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
displaySet,
|
||||
});
|
||||
|
||||
// create Segmentation callback which needs to be waited until
|
||||
// the volume is created (if coming from stack)
|
||||
const createSegmentationForVolume = async () => {
|
||||
const segmentationId = await loadFn();
|
||||
segmentationService.hydrateSegmentation(segmentationId);
|
||||
};
|
||||
|
||||
// the reference volume that is used to draw the segmentation. so check if the
|
||||
// volume exists in the cache (the target Viewport is already a volume viewport)
|
||||
const volumeExists = Array.from(cache._volumeCache.keys()).some(volumeId =>
|
||||
volumeId.includes(referencedDisplaySetInstanceUID)
|
||||
);
|
||||
|
||||
updatedViewports.forEach(async viewport => {
|
||||
viewport.viewportOptions = {
|
||||
...viewport.viewportOptions,
|
||||
viewportType: displaySet?.Modality === 'RTSTRUCT' ? 'stack' : 'volume',
|
||||
needsRerendering: true,
|
||||
};
|
||||
const viewportId = viewport.viewportId;
|
||||
|
||||
// maintain the prehydration slice on the target viewport only
|
||||
if (viewportId === targetViewportId) {
|
||||
viewport.viewportOptions.initialImageOptions = {
|
||||
index: initialSliceIndex,
|
||||
useOnce: true,
|
||||
};
|
||||
}
|
||||
|
||||
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
const prevCamera = csViewport.getCamera();
|
||||
|
||||
// only run the createSegmentationForVolume for the targetViewportId
|
||||
// since the rest will get handled by cornerstoneViewportService
|
||||
if ((volumeExists || displaySet.Modality === 'RTSTRUCT') && viewportId === targetViewportId) {
|
||||
await createSegmentationForVolume();
|
||||
return;
|
||||
}
|
||||
|
||||
const createNewSegmentationWhenVolumeMounts = async evt => {
|
||||
const isTheActiveViewportVolumeMounted = evt.detail.volumeActors?.find(ac =>
|
||||
ac.uid.includes(referencedDisplaySetInstanceUID)
|
||||
);
|
||||
|
||||
// Note: make sure to re-grab the viewport since it might have changed
|
||||
// during the time it took for the volume to be mounted, for instance
|
||||
// the stack viewport has been changed to a volume viewport
|
||||
const volumeViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
volumeViewport.setCamera(prevCamera);
|
||||
|
||||
volumeViewport.element.removeEventListener(
|
||||
Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
|
||||
createNewSegmentationWhenVolumeMounts
|
||||
);
|
||||
|
||||
if (!isTheActiveViewportVolumeMounted) {
|
||||
// it means it is one of those other updated viewports so just update the camera
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewportId === targetViewportId) {
|
||||
await createSegmentationForVolume();
|
||||
}
|
||||
};
|
||||
|
||||
csViewport.element.addEventListener(
|
||||
Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
|
||||
createNewSegmentationWhenVolumeMounts
|
||||
);
|
||||
});
|
||||
|
||||
// Set the displaySets for the viewports that require to be updated
|
||||
viewportGridService.setDisplaySetsForViewports(updatedViewports);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const getTargetViewport = ({ viewportId, viewportGridService }) => {
|
||||
const { viewports, activeViewportId } = viewportGridService.getState();
|
||||
const targetViewportId = viewportId || activeViewportId;
|
||||
|
||||
const viewport = viewports.get(targetViewportId);
|
||||
|
||||
return viewport;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a list of viewports that require updates in preparation for segmentation rendering.
|
||||
* This function evaluates viewports based on their compatibility with the provided segmentation's
|
||||
* frame of reference UID and appends them to the updated list if they should render the segmentation.
|
||||
*
|
||||
* @param {Object} params - Parameters for the function.
|
||||
* @param params.viewportId - the ID of the viewport to be updated.
|
||||
* @param params.servicesManager - The services manager
|
||||
* @param params.displaySet - the display set.
|
||||
*
|
||||
* @returns {Array} Returns an array of viewports that require updates for segmentation rendering.
|
||||
*/
|
||||
function getUpdatedViewportsForSegmentation({
|
||||
viewportId,
|
||||
servicesManager,
|
||||
displaySet,
|
||||
}: withAppTypes) {
|
||||
const { hangingProtocolService, displaySetService, segmentationService, viewportGridService } =
|
||||
servicesManager.services;
|
||||
|
||||
const { viewports, isHangingProtocolLayout } = viewportGridService.getState();
|
||||
|
||||
const viewport = getTargetViewport({ viewportId, viewportGridService });
|
||||
const targetViewportId = viewport.viewportOptions.viewportId;
|
||||
|
||||
const displaySetInstanceUIDs = viewports.get(targetViewportId).displaySetInstanceUIDs;
|
||||
|
||||
const referenceDisplaySetInstanceUID =
|
||||
displaySet?.referencedDisplaySetInstanceUID || displaySetInstanceUIDs[0];
|
||||
|
||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(referenceDisplaySetInstanceUID);
|
||||
const segmentationFrameOfReferenceUID = referencedDisplaySet.instances[0].FrameOfReferenceUID;
|
||||
|
||||
const updatedViewports = hangingProtocolService.getViewportsRequireUpdate(
|
||||
targetViewportId,
|
||||
referenceDisplaySetInstanceUID,
|
||||
isHangingProtocolLayout
|
||||
);
|
||||
|
||||
viewports.forEach((viewport, viewportId) => {
|
||||
if (
|
||||
targetViewportId === viewportId ||
|
||||
updatedViewports.find(v => v.viewportId === viewportId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldDisplaySeg = segmentationService.shouldRenderSegmentation(
|
||||
viewport.displaySetInstanceUIDs,
|
||||
segmentationFrameOfReferenceUID
|
||||
);
|
||||
|
||||
if (shouldDisplaySeg) {
|
||||
updatedViewports.push({
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: viewport.displaySetInstanceUIDs,
|
||||
viewportOptions: {
|
||||
viewportType: displaySet?.Modality === 'RTSTRUCT' ? 'stack' : 'volume',
|
||||
needsRerendering: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return updatedViewports.filter(v => v.viewportOptions?.viewportType !== 'volume3d');
|
||||
}
|
||||
|
||||
export {
|
||||
updateViewportsForSegmentationRendering,
|
||||
getUpdatedViewportsForSegmentation,
|
||||
getTargetViewport,
|
||||
};
|
||||
@@ -11,7 +11,7 @@ function promptHydrateSEG({
|
||||
segDisplaySet,
|
||||
viewportId,
|
||||
preHydrateCallbacks,
|
||||
hydrateSEGDisplaySet,
|
||||
hydrateCallback,
|
||||
}: withAppTypes) {
|
||||
const { uiViewportDialogService } = servicesManager.services;
|
||||
const extensionManager = servicesManager._extensionManager;
|
||||
@@ -28,7 +28,7 @@ function promptHydrateSEG({
|
||||
});
|
||||
|
||||
window.setTimeout(async () => {
|
||||
const isHydrated = await hydrateSEGDisplaySet({
|
||||
const isHydrated = await hydrateCallback({
|
||||
segDisplaySet,
|
||||
viewportId,
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
|
||||
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
|
||||
import promptHydrateSEG from '../utils/promptHydrateSEG';
|
||||
import _getStatusComponent from './_getStatusComponent';
|
||||
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||
|
||||
const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup';
|
||||
|
||||
@@ -37,11 +38,11 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
}
|
||||
|
||||
const segDisplaySet = displaySets[0];
|
||||
|
||||
const [viewportGrid, viewportGridService] = useViewportGrid();
|
||||
|
||||
// States
|
||||
const [selectedSegment, setSelectedSegment] = useState(1);
|
||||
const { setPositionPresentation } = usePositionPresentationStore();
|
||||
|
||||
// Hydration means that the SEG is opened and segments are loaded into the
|
||||
// segmentation panel, and SEG is also rendered on any viewport that is in the
|
||||
@@ -61,7 +62,11 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
|
||||
const { viewports, activeViewportId } = viewportGrid;
|
||||
|
||||
const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet();
|
||||
const referencedDisplaySetInstanceUID = segDisplaySet.referencedDisplaySetInstanceUID;
|
||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(
|
||||
referencedDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
|
||||
referencedDisplaySet,
|
||||
segDisplaySet
|
||||
@@ -98,18 +103,17 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
'@ohif/extension-cornerstone.viewportModule.cornerstone'
|
||||
);
|
||||
|
||||
const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current;
|
||||
|
||||
// Todo: jump to the center of the first segment
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
displaySets={[referencedDisplaySet, segDisplaySet]}
|
||||
displaySets={[segDisplaySet]}
|
||||
viewportOptions={{
|
||||
viewportType: 'volume',
|
||||
viewportType: viewportOptions.viewportType,
|
||||
toolGroupId: toolGroupId,
|
||||
orientation: viewportOptions.orientation,
|
||||
viewportId: viewportOptions.viewportId,
|
||||
presentationIds: viewportOptions.presentationIds,
|
||||
}}
|
||||
onElementEnabled={evt => {
|
||||
props.onElementEnabled?.(evt);
|
||||
@@ -139,12 +143,35 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
newSelectedSegmentIndex = numberOfSegments - 1;
|
||||
}
|
||||
|
||||
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId);
|
||||
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, viewportId);
|
||||
setSelectedSegment(newSelectedSegmentIndex);
|
||||
},
|
||||
[selectedSegment]
|
||||
);
|
||||
|
||||
const hydrateSEG = useCallback(() => {
|
||||
// update the previously stored segmentationPresentation with the new viewportId
|
||||
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||
// it will have the correct segmentation representation hydrated
|
||||
commandsManager.runCommand('updateStoredSegmentationPresentation', {
|
||||
displaySet: segDisplaySet,
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
});
|
||||
|
||||
// update the previously stored positionPresentation with the new viewportId
|
||||
// presentation so that when we put the referencedDisplaySet back in the viewport
|
||||
// it will be in the correct position zoom and pan
|
||||
commandsManager.runCommand('updateStoredPositionPresentation', {
|
||||
viewportId,
|
||||
displaySetInstanceUID: referencedDisplaySet.displaySetInstanceUID,
|
||||
});
|
||||
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId,
|
||||
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
|
||||
});
|
||||
}, [commandsManager, viewportId, referencedDisplaySet, segDisplaySet]);
|
||||
|
||||
useEffect(() => {
|
||||
if (segIsLoading) {
|
||||
return;
|
||||
@@ -155,21 +182,35 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
viewportId,
|
||||
segDisplaySet,
|
||||
preHydrateCallbacks: [storePresentationState],
|
||||
hydrateSEGDisplaySet,
|
||||
hydrateCallback: hydrateSEG,
|
||||
}).then(isHydrated => {
|
||||
if (isHydrated) {
|
||||
setIsHydrated(true);
|
||||
}
|
||||
});
|
||||
}, [servicesManager, viewportId, segDisplaySet, segIsLoading]);
|
||||
}, [servicesManager, viewportId, segDisplaySet, segIsLoading, hydrateSEG]);
|
||||
|
||||
useEffect(() => {
|
||||
// on new seg display set, remove all segmentations from all viewports
|
||||
segmentationService.clearSegmentationRepresentations(viewportId);
|
||||
|
||||
const { unsubscribe } = segmentationService.subscribe(
|
||||
segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE,
|
||||
evt => {
|
||||
if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) {
|
||||
setSegIsLoading(false);
|
||||
}
|
||||
|
||||
if (segDisplaySet?.firstSegmentedSliceImageId && viewportOptions?.presentationIds) {
|
||||
const { firstSegmentedSliceImageId } = segDisplaySet;
|
||||
const { presentationIds } = viewportOptions;
|
||||
|
||||
setPositionPresentation(presentationIds.positionPresentationId, {
|
||||
viewReference: {
|
||||
referencedImageId: firstSegmentedSliceImageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -223,39 +264,31 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
return;
|
||||
}
|
||||
|
||||
// keep the already stored segmentationPresentation for this viewport in memory
|
||||
// so that we can restore it after hydrating the SEG
|
||||
commandsManager.runCommand('updateStoredSegmentationPresentation', {
|
||||
displaySet: segDisplaySet,
|
||||
type: SegmentationRepresentations.Labelmap,
|
||||
});
|
||||
|
||||
// always start fresh for this viewport since it is special type of viewport
|
||||
// that should only show one segmentation at a time.
|
||||
segmentationService.clearSegmentationRepresentations(viewportId);
|
||||
|
||||
// This creates a custom tool group which has the lifetime of this view
|
||||
// only, and does NOT interfere with currently displayed segmentations.
|
||||
toolGroup = createSEGToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId);
|
||||
|
||||
return () => {
|
||||
// remove the segmentation representations if seg displayset changed
|
||||
segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||
// e.g., another seg displayset is dragged into the viewport
|
||||
segmentationService.clearSegmentationRepresentations(viewportId);
|
||||
|
||||
// Only destroy the viewport specific implementation
|
||||
toolGroupService.destroyToolGroup(toolGroupId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setIsHydrated(segDisplaySet.isHydrated);
|
||||
|
||||
return () => {
|
||||
// remove the segmentation representations if seg displayset changed
|
||||
segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId);
|
||||
referencedDisplaySetRef.current = null;
|
||||
};
|
||||
}, [segDisplaySet]);
|
||||
|
||||
const hydrateSEGDisplaySet = useCallback(
|
||||
({ segDisplaySet, viewportId }) => {
|
||||
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
|
||||
displaySets: [segDisplaySet],
|
||||
viewportId,
|
||||
});
|
||||
},
|
||||
[commandsManager]
|
||||
);
|
||||
|
||||
const onStatusClick = useCallback(async () => {
|
||||
// Before hydrating a SEG and make it added to all viewports in the grid
|
||||
// that share the same frameOfReferenceUID, we need to store the viewport grid
|
||||
@@ -264,16 +297,11 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
// presentation state (w/l and invert) and then opens the SEG. If we don't store
|
||||
// the presentation state, the viewport will be reset to the default presentation
|
||||
storePresentationState();
|
||||
const isHydrated = await hydrateSEGDisplaySet({
|
||||
segDisplaySet,
|
||||
viewportId,
|
||||
});
|
||||
|
||||
setIsHydrated(isHydrated);
|
||||
}, [hydrateSEGDisplaySet, segDisplaySet, storePresentationState, viewportId]);
|
||||
hydrateSEG();
|
||||
}, [storePresentationState, hydrateSEG]);
|
||||
|
||||
useEffect(() => {
|
||||
viewportActionCornersService.setComponents([
|
||||
viewportActionCornersService.addComponents([
|
||||
{
|
||||
viewportId,
|
||||
id: 'viewportStatusComponent',
|
||||
@@ -292,7 +320,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
key="actionArrows"
|
||||
onArrowsClick={onSegmentChange}
|
||||
className={
|
||||
viewportId === activeViewportId ? 'visible' : 'invisible group-hover:visible'
|
||||
viewportId === activeViewportId ? 'visible' : 'invisible group-hover/pane:visible'
|
||||
}
|
||||
></ViewportActionArrows>
|
||||
),
|
||||
@@ -332,18 +360,6 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
PatientID,
|
||||
PatientName,
|
||||
PatientSex,
|
||||
PatientAge,
|
||||
SliceThickness,
|
||||
ManufacturerModelName,
|
||||
StudyDate,
|
||||
SeriesDescription,
|
||||
SpacingBetweenSlices,
|
||||
} = referencedDisplaySetRef.current.metadata;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex h-full w-full flex-row overflow-hidden">
|
||||
@@ -362,13 +378,6 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
|
||||
);
|
||||
}
|
||||
|
||||
OHIFCornerstoneSEGViewport.propTypes = {
|
||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||
viewportId: PropTypes.string.isRequired,
|
||||
dataSource: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
function _getReferencedDisplaySetMetadata(referencedDisplaySet, segDisplaySet) {
|
||||
const { SharedFunctionalGroupsSequence } = segDisplaySet.instance;
|
||||
|
||||
|
||||
@@ -3,6 +3,468 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OHIF 3.9 Recovery 3 ([#4697](https://github.com/OHIF/Viewers/issues/4697)) ([5d4f2e2](https://github.com/OHIF/Viewers/commit/5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **multiframe:** metadata handling of NM studies and loading order ([#4540](https://github.com/OHIF/Viewers/issues/4540)) ([71f06dc](https://github.com/OHIF/Viewers/commit/71f06dcd3e6d24f1e3d931f7db756c26038927da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* video playback ([#4497](https://github.com/OHIF/Viewers/issues/4497)) ([610faa5](https://github.com/OHIF/Viewers/commit/610faa5a2738da5eabc40e57e338c359f481e852))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Measurement Tracking: Various UI and functionality improvements ([#4481](https://github.com/OHIF/Viewers/issues/4481)) ([62b2748](https://github.com/OHIF/Viewers/commit/62b27488471c9d5979142e2d15872a85778b90ed))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bugs:** Update dependencies and enhance UI components ([#4478](https://github.com/OHIF/Viewers/issues/4478)) ([05d41c5](https://github.com/OHIF/Viewers/commit/05d41c52068a3b7ba249f15ecdf71838c352fd30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tmtv:** fix toggle one up weird behaviours ([#4473](https://github.com/OHIF/Viewers/issues/4473)) ([aa2b649](https://github.com/OHIF/Viewers/commit/aa2b649444eb4fe5422e72ea7830a709c4d24a90))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build ([#4471](https://github.com/OHIF/Viewers/issues/4471)) ([3d11ef2](https://github.com/OHIF/Viewers/commit/3d11ef28f213361ec7586809317bd219fa70e742))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **new-study-panel:** default to list view for non thumbnail series, change default fitler to all, and add more menu to thumbnail items with a dicom tag browser ([#4417](https://github.com/OHIF/Viewers/issues/4417)) ([a7fd9fa](https://github.com/OHIF/Viewers/commit/a7fd9fa5bfff7a1b533d99cb96f7147a35fd528f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **sr:** load existing point, if there is 2nd point in renderableData (Fix rotation in arrow annotation) ([#4356](https://github.com/OHIF/Viewers/issues/4356)) ([7353f7f](https://github.com/OHIF/Viewers/commit/7353f7f069446f8484278c2cff5b09149cfa23eb))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **SR:** SCOORD3D point annotations support for stack viewports ([#4315](https://github.com/OHIF/Viewers/issues/4315)) ([ac1cad2](https://github.com/OHIF/Viewers/commit/ac1cad25af12ee0f7d508647e3134ed724d9b4d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add CS3D WSI and Video Viewports and add annotation navigation for MPR ([#4182](https://github.com/OHIF/Viewers/issues/4182)) ([7599ec9](https://github.com/OHIF/Viewers/commit/7599ec9421129dcade94e6fa6ec7908424ab3134))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **SR:** text structured report (TEXT, CODE, NUM, PNAME, DATE, TIME and DATETIME) ([#4287](https://github.com/OHIF/Viewers/issues/4287)) ([246ebab](https://github.com/OHIF/Viewers/commit/246ebab6ebf5431a704a1861a5804045b9644ba4))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dicom-sr",
|
||||
"version": "3.9.0-beta.62",
|
||||
"version": "3.9.3",
|
||||
"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.9.0-beta.62",
|
||||
"@ohif/extension-cornerstone": "3.9.0-beta.62",
|
||||
"@ohif/extension-measurement-tracking": "3.9.0-beta.62",
|
||||
"@ohif/ui": "3.9.0-beta.62",
|
||||
"@ohif/core": "3.9.3",
|
||||
"@ohif/extension-cornerstone": "3.9.3",
|
||||
"@ohif/extension-measurement-tracking": "3.9.3",
|
||||
"@ohif/ui": "3.9.3",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.9",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -46,9 +46,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.82.0",
|
||||
"@cornerstonejs/core": "^1.82.0",
|
||||
"@cornerstonejs/tools": "^1.82.0",
|
||||
"@cornerstonejs/adapters": "^2.2.20",
|
||||
"@cornerstonejs/core": "^2.2.20",
|
||||
"@cornerstonejs/tools": "^2.2.20",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { OHIFCornerstoneSRContentItem } from './OHIFCornerstoneSRContentItem';
|
||||
|
||||
export function OHIFCornerstoneSRContainer(props) {
|
||||
const { container, nodeIndexesTree = [0], containerNumberedTree = [1] } = props;
|
||||
const { ContinuityOfContent, ConceptNameCodeSequence } = container;
|
||||
const { CodeMeaning } = ConceptNameCodeSequence ?? {};
|
||||
let childContainerIndex = 1;
|
||||
const contentItems = container.ContentSequence?.map((contentItem, i) => {
|
||||
const { ValueType } = contentItem;
|
||||
const childNodeLevel = [...nodeIndexesTree, i];
|
||||
const key = childNodeLevel.join('.');
|
||||
|
||||
let Component;
|
||||
let componentProps;
|
||||
|
||||
if (ValueType === 'CONTAINER') {
|
||||
const childContainerNumberedTree = [...containerNumberedTree, childContainerIndex++];
|
||||
|
||||
Component = OHIFCornerstoneSRContainer;
|
||||
componentProps = {
|
||||
container: contentItem,
|
||||
nodeIndexesTree: childNodeLevel,
|
||||
containerNumberedTree: childContainerNumberedTree,
|
||||
};
|
||||
} else {
|
||||
Component = OHIFCornerstoneSRContentItem;
|
||||
componentProps = {
|
||||
contentItem,
|
||||
nodeIndexesTree: childNodeLevel,
|
||||
continuityOfContent: ContinuityOfContent,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Component
|
||||
key={key}
|
||||
{...componentProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="font-bold">
|
||||
{containerNumberedTree.join('.')}.
|
||||
{CodeMeaning}
|
||||
</div>
|
||||
<div className="ml-4 mb-2">{contentItems}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
OHIFCornerstoneSRContainer.propTypes = {
|
||||
/**
|
||||
* A tree node that may contain another container or one or more content items
|
||||
* (text, code, uidref, pname, etc.)
|
||||
*/
|
||||
container: PropTypes.object,
|
||||
/**
|
||||
* A 0-based index list
|
||||
*/
|
||||
nodeIndexesTree: PropTypes.arrayOf(PropTypes.number),
|
||||
/**
|
||||
* A 1-based index list that represents a container in a multi-level numbered
|
||||
* list (tree).
|
||||
*
|
||||
* Example:
|
||||
* 1. History
|
||||
* 1.1. Chief Complaint
|
||||
* 1.2. Present Illness
|
||||
* 1.3. Past History
|
||||
* 1.4. Family History
|
||||
* 2. Findings
|
||||
* */
|
||||
containerNumberedTree: PropTypes.arrayOf(PropTypes.number),
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { CodeNameCodeSequenceValues } from '../enums';
|
||||
import formatContentItemValue from '../utils/formatContentItem';
|
||||
|
||||
const EMPTY_TAG_VALUE = '[empty]';
|
||||
|
||||
function OHIFCornerstoneSRContentItem(props) {
|
||||
const { contentItem, nodeIndexesTree, continuityOfContent } = props;
|
||||
const { ConceptNameCodeSequence } = contentItem;
|
||||
const { CodeValue, CodeMeaning } = ConceptNameCodeSequence;
|
||||
const isChildFirstNode = nodeIndexesTree[nodeIndexesTree.length - 1] === 0;
|
||||
const formattedValue = formatContentItemValue(contentItem) ?? EMPTY_TAG_VALUE;
|
||||
const startWithAlphaNumCharRegEx = /^[a-zA-Z0-9]/;
|
||||
const isContinuous = continuityOfContent === 'CONTINUOUS';
|
||||
const isFinding = CodeValue === CodeNameCodeSequenceValues.Finding;
|
||||
const addExtraSpace =
|
||||
isContinuous && !isChildFirstNode && startWithAlphaNumCharRegEx.test(formattedValue?.[0]);
|
||||
|
||||
// Collapse sequences of white space preserving newline characters
|
||||
let className = 'whitespace-pre-line';
|
||||
|
||||
if (CodeValue === CodeNameCodeSequenceValues.Finding) {
|
||||
// Preserve spaces because it is common to see tabular text in a
|
||||
// "Findings" ConceptNameCodeSequence
|
||||
className = 'whitespace-pre-wrap';
|
||||
}
|
||||
|
||||
if (isContinuous) {
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={className}
|
||||
title={CodeMeaning}
|
||||
>
|
||||
{addExtraSpace ? ' ' : ''}
|
||||
{formattedValue}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
<span className="font-bold">{CodeMeaning}: </span>
|
||||
{isFinding ? (
|
||||
<pre>{formattedValue}</pre>
|
||||
) : (
|
||||
<span className={className}>{formattedValue}</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
OHIFCornerstoneSRContentItem.propTypes = {
|
||||
contentItem: PropTypes.object,
|
||||
nodeIndexesTree: PropTypes.arrayOf(PropTypes.number),
|
||||
continuityOfContent: PropTypes.string,
|
||||
};
|
||||
|
||||
export { OHIFCornerstoneSRContentItem };
|
||||
+37
-42
@@ -9,17 +9,20 @@ import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
|
||||
import hydrateStructuredReport from '../utils/hydrateStructuredReport';
|
||||
import { useAppConfig } from '@state';
|
||||
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
|
||||
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
|
||||
|
||||
const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking';
|
||||
|
||||
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
|
||||
|
||||
function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
function OHIFCornerstoneSRMeasurementViewport(props: withAppTypes) {
|
||||
const { children, dataSource, displaySets, viewportOptions, servicesManager, extensionManager } =
|
||||
props;
|
||||
|
||||
const [appConfig] = useAppConfig();
|
||||
|
||||
const { setPositionPresentation } = usePositionPresentationStore();
|
||||
|
||||
const {
|
||||
displaySetService,
|
||||
cornerstoneViewportService,
|
||||
@@ -63,6 +66,7 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
trackedMeasurements = tracked?.[0];
|
||||
sendTrackedMeasurementsEvent = tracked?.[1];
|
||||
}
|
||||
|
||||
if (!sendTrackedMeasurementsEvent) {
|
||||
// if no panels from measurement-tracking extension is used, this code will run
|
||||
trackedMeasurements = null;
|
||||
@@ -72,6 +76,7 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
{ servicesManager, extensionManager, appConfig },
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]);
|
||||
if (displaySets.length) {
|
||||
viewportGridService.setDisplaySetsForViewports([
|
||||
@@ -84,6 +89,11 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo: what is this, not sure what it does regarding the react aspect,
|
||||
* it is updating a local variable? which is not state.
|
||||
*/
|
||||
const [isLocked, setIsLocked] = useState(trackedMeasurements?.context?.trackedSeries?.length > 0);
|
||||
/**
|
||||
* Store the tracking identifiers per viewport in order to be able to
|
||||
* show the SR measurements on the referenced image on the correct viewport,
|
||||
@@ -135,30 +145,21 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
newMeasurementSelected,
|
||||
displaySetService
|
||||
).then(({ referencedDisplaySet, referencedDisplaySetMetadata }) => {
|
||||
if (!referencedDisplaySet || !referencedDisplaySetMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
setMeasurementSelected(newMeasurementSelected);
|
||||
setActiveImageDisplaySetData(referencedDisplaySet);
|
||||
setReferencedDisplaySetMetadata(referencedDisplaySetMetadata);
|
||||
|
||||
if (
|
||||
referencedDisplaySet.displaySetInstanceUID ===
|
||||
activeImageDisplaySetData?.displaySetInstanceUID
|
||||
) {
|
||||
const { measurements } = srDisplaySet;
|
||||
|
||||
// it means that we have a new referenced display set, and the
|
||||
// imageIdIndex will handle it by updating the viewport, but if they
|
||||
// are the same we just need to use measurementService to jump to the
|
||||
// new measurement
|
||||
const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
const imageIds = csViewport.getImageIds();
|
||||
|
||||
const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId);
|
||||
|
||||
if (imageIdIndex !== -1) {
|
||||
csViewport.setImageIdIndex(imageIdIndex);
|
||||
}
|
||||
}
|
||||
const { presentationIds } = viewportOptions;
|
||||
const measurement = srDisplaySet.measurements[newMeasurementSelected];
|
||||
setPositionPresentation(presentationIds.positionPresentationId, {
|
||||
viewReference: {
|
||||
referencedImageId: measurement.imageId,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
[dataSource, srDisplaySet, activeImageDisplaySetData, viewportId]
|
||||
@@ -180,10 +181,6 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const initialImageIndex = activeImageDisplaySetData.images.findIndex(
|
||||
image => image.imageId === measurement.imageId
|
||||
);
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
@@ -208,7 +205,6 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
props.onElementEnabled?.(evt);
|
||||
onElementEnabled(evt);
|
||||
}}
|
||||
initialImageIndex={initialImageIndex}
|
||||
isJumpToMeasurementDisabled={true}
|
||||
></Component>
|
||||
);
|
||||
@@ -278,28 +274,23 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
*/
|
||||
useEffect(() => {
|
||||
const updateSR = async () => {
|
||||
if (!srDisplaySet.isLoaded) {
|
||||
await srDisplaySet.load();
|
||||
}
|
||||
if (!element || !srDisplaySet.isLoaded) {
|
||||
return;
|
||||
}
|
||||
setTrackingIdentifiers(measurementSelected);
|
||||
}
|
||||
if (!srDisplaySet.isLoaded) {
|
||||
await srDisplaySet.load();
|
||||
}
|
||||
if (!element || !srDisplaySet.isLoaded) {
|
||||
return;
|
||||
}
|
||||
setTrackingIdentifiers(measurementSelected);
|
||||
};
|
||||
updateSR();
|
||||
}, [measurementSelected, element, setTrackingIdentifiers, srDisplaySet]);
|
||||
|
||||
/**
|
||||
* Todo: what is this, not sure what it does regarding the react aspect,
|
||||
* it is updating a local variable? which is not state.
|
||||
*/
|
||||
const [isLocked, setIsLocked] = useState(trackedMeasurements?.context?.trackedSeries?.length > 0);
|
||||
useEffect(() => {
|
||||
setIsLocked(trackedMeasurements?.context?.trackedSeries?.length > 0);
|
||||
}, [trackedMeasurements]);
|
||||
|
||||
useEffect(() => {
|
||||
viewportActionCornersService.setComponents([
|
||||
viewportActionCornersService.addComponents([
|
||||
{
|
||||
viewportId,
|
||||
id: 'viewportStatusComponent',
|
||||
@@ -367,7 +358,7 @@ function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
);
|
||||
}
|
||||
|
||||
OHIFCornerstoneSRViewport.propTypes = {
|
||||
OHIFCornerstoneSRMeasurementViewport.propTypes = {
|
||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||
viewportId: PropTypes.string.isRequired,
|
||||
dataSource: PropTypes.object,
|
||||
@@ -395,6 +386,10 @@ async function _getViewportReferencedDisplaySetData(
|
||||
displaySet.keyImageDisplaySet = createReferencedImageDisplaySet(displaySetService, displaySet);
|
||||
}
|
||||
|
||||
if (!displaySetInstanceUID) {
|
||||
return { referencedDisplaySetMetadata: null, referencedDisplaySet: null };
|
||||
}
|
||||
|
||||
const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
const image0 = referencedDisplaySet.images[0];
|
||||
@@ -508,4 +503,4 @@ function _getStatusComponent({
|
||||
);
|
||||
}
|
||||
|
||||
export default OHIFCornerstoneSRViewport;
|
||||
export default OHIFCornerstoneSRMeasurementViewport;
|
||||
@@ -0,0 +1,32 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { ExtensionManager } from '@ohif/core';
|
||||
import { OHIFCornerstoneSRContainer } from './OHIFCornerstoneSRContainer';
|
||||
|
||||
function OHIFCornerstoneSRTextViewport(props: withAppTypes) {
|
||||
const { displaySets } = props;
|
||||
const displaySet = displaySets[0];
|
||||
const instance = displaySet.instances[0];
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col overflow-auto p-4 text-white">
|
||||
<div>
|
||||
{/* The root level is always a container */}
|
||||
<OHIFCornerstoneSRContainer container={instance} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
OHIFCornerstoneSRTextViewport.propTypes = {
|
||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||
viewportId: PropTypes.string.isRequired,
|
||||
dataSource: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
viewportLabel: PropTypes.string,
|
||||
viewportOptions: PropTypes.object,
|
||||
servicesManager: PropTypes.object.isRequired,
|
||||
extensionManager: PropTypes.instanceOf(ExtensionManager).isRequired,
|
||||
};
|
||||
|
||||
export default OHIFCornerstoneSRTextViewport;
|
||||
@@ -0,0 +1,30 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { ExtensionManager } from '@ohif/core';
|
||||
|
||||
import OHIFCornerstoneSRMeasurementViewport from './OHIFCornerstoneSRMeasurementViewport';
|
||||
import OHIFCornerstoneSRTextViewport from './OHIFCornerstoneSRTextViewport';
|
||||
|
||||
function OHIFCornerstoneSRViewport(props: withAppTypes) {
|
||||
const { displaySets } = props;
|
||||
const { isImagingMeasurementReport } = displaySets[0];
|
||||
|
||||
if (isImagingMeasurementReport) {
|
||||
return <OHIFCornerstoneSRMeasurementViewport {...props}></OHIFCornerstoneSRMeasurementViewport>;
|
||||
}
|
||||
|
||||
return <OHIFCornerstoneSRTextViewport {...props}></OHIFCornerstoneSRTextViewport>;
|
||||
}
|
||||
|
||||
OHIFCornerstoneSRViewport.propTypes = {
|
||||
displaySets: PropTypes.arrayOf(PropTypes.object),
|
||||
viewportId: PropTypes.string.isRequired,
|
||||
dataSource: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
viewportLabel: PropTypes.string,
|
||||
viewportOptions: PropTypes.object,
|
||||
servicesManager: PropTypes.object.isRequired,
|
||||
extensionManager: PropTypes.instanceOf(ExtensionManager).isRequired,
|
||||
};
|
||||
|
||||
export default OHIFCornerstoneSRViewport;
|
||||
@@ -1,7 +0,0 @@
|
||||
export default {
|
||||
POINT: 'POINT',
|
||||
MULTIPOINT: 'MULTIPOINT',
|
||||
POLYLINE: 'POLYLINE',
|
||||
CIRCLE: 'CIRCLE',
|
||||
ELLIPSE: 'ELLIPSE',
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
export const SCOORDTypes = {
|
||||
POINT: 'POINT',
|
||||
MULTIPOINT: 'MULTIPOINT',
|
||||
POLYLINE: 'POLYLINE',
|
||||
CIRCLE: 'CIRCLE',
|
||||
ELLIPSE: 'ELLIPSE',
|
||||
};
|
||||
|
||||
export const CodeNameCodeSequenceValues = {
|
||||
ImagingMeasurementReport: '126000',
|
||||
ImageLibrary: '111028',
|
||||
ImagingMeasurements: '126010',
|
||||
MeasurementGroup: '125007',
|
||||
ImageLibraryGroup: '126200',
|
||||
TrackingUniqueIdentifier: '112040',
|
||||
TrackingIdentifier: '112039',
|
||||
Finding: '121071',
|
||||
FindingSite: 'G-C0E3', // SRT
|
||||
FindingSiteSCT: '363698007', // SCT
|
||||
};
|
||||
|
||||
export const CodingSchemeDesignators = {
|
||||
SRT: 'SRT',
|
||||
SCT: 'SCT',
|
||||
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'],
|
||||
};
|
||||
|
||||
export const RelationshipType = {
|
||||
INFERRED_FROM: 'INFERRED FROM',
|
||||
CONTAINS: 'CONTAINS',
|
||||
};
|
||||
|
||||
const enums = {
|
||||
CodeNameCodeSequenceValues,
|
||||
CodingSchemeDesignators,
|
||||
RelationshipType,
|
||||
SCOORDTypes,
|
||||
};
|
||||
|
||||
export default enums;
|
||||
@@ -1,30 +1,37 @@
|
||||
import { SOPClassHandlerName, SOPClassHandlerId } from './id';
|
||||
import { utils, classes, DisplaySetService, Types } from '@ohif/core';
|
||||
import addDICOMSRDisplayAnnotation from './utils/addDICOMSRDisplayAnnotation';
|
||||
import isRehydratable from './utils/isRehydratable';
|
||||
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
|
||||
import addSRAnnotation from './utils/addSRAnnotation';
|
||||
import isRehydratable from './utils/isRehydratable';
|
||||
import {
|
||||
SOPClassHandlerName,
|
||||
SOPClassHandlerId,
|
||||
SOPClassHandlerId3D,
|
||||
SOPClassHandlerName3D,
|
||||
} from './id';
|
||||
import { CodeNameCodeSequenceValues, CodingSchemeDesignators } from './enums';
|
||||
|
||||
const { sopClassDictionary } = utils;
|
||||
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||
const { ImageSet, MetadataProvider: metadataProvider } = classes;
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
type InstanceMetadata = Types.InstanceMetadata;
|
||||
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
const { ImageSet, MetadataProvider: metadataProvider } = classes;
|
||||
|
||||
// TODO ->
|
||||
// Add SR thumbnail
|
||||
// Make viewport
|
||||
// Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport.
|
||||
/**
|
||||
* TODO
|
||||
* - [ ] Add SR thumbnail
|
||||
* - [ ] Make viewport
|
||||
* - [ ] Get stacks from referenced displayInstanceUID and load into wrapped CornerStone viewport
|
||||
*/
|
||||
|
||||
const sopClassUids = [
|
||||
'1.2.840.10008.5.1.4.1.1.88.11', //BASIC_TEXT_SR:
|
||||
'1.2.840.10008.5.1.4.1.1.88.22', //ENHANCED_SR:
|
||||
'1.2.840.10008.5.1.4.1.1.88.33', //COMPREHENSIVE_SR:
|
||||
'1.2.840.10008.5.1.4.1.1.88.34', //COMPREHENSIVE_3D_SR:
|
||||
sopClassDictionary.BasicTextSR,
|
||||
sopClassDictionary.EnhancedSR,
|
||||
sopClassDictionary.ComprehensiveSR,
|
||||
];
|
||||
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
|
||||
const validateSameStudyUID = (uid: string, instances): void => {
|
||||
instances.forEach(it => {
|
||||
if (it.StudyInstanceUID !== uid) {
|
||||
@@ -34,31 +41,6 @@ const validateSameStudyUID = (uid: string, instances): void => {
|
||||
});
|
||||
};
|
||||
|
||||
const CodeNameCodeSequenceValues = {
|
||||
ImagingMeasurementReport: '126000',
|
||||
ImageLibrary: '111028',
|
||||
ImagingMeasurements: '126010',
|
||||
MeasurementGroup: '125007',
|
||||
ImageLibraryGroup: '126200',
|
||||
TrackingUniqueIdentifier: '112040',
|
||||
TrackingIdentifier: '112039',
|
||||
Finding: '121071',
|
||||
FindingSite: 'G-C0E3', // SRT
|
||||
CornerstoneFreeText: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT, //
|
||||
};
|
||||
|
||||
const CodingSchemeDesignators = {
|
||||
SRT: 'SRT',
|
||||
CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'],
|
||||
};
|
||||
|
||||
const RELATIONSHIP_TYPE = {
|
||||
INFERRED_FROM: 'INFERRED FROM',
|
||||
CONTAINS: 'CONTAINS',
|
||||
};
|
||||
|
||||
const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT';
|
||||
|
||||
/**
|
||||
* Adds instances to the DICOM SR series, rather than creating a new
|
||||
* series, so that as SR's are saved, they append to the series, and the
|
||||
@@ -113,22 +95,12 @@ function _getDisplaySetsFromSeries(
|
||||
} = instance;
|
||||
validateSameStudyUID(instance.StudyInstanceUID, instances);
|
||||
|
||||
if (
|
||||
!ConceptNameCodeSequence ||
|
||||
ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.ImagingMeasurementReport
|
||||
) {
|
||||
servicesManager.services.uiNotificationService.show({
|
||||
title: 'DICOM SR',
|
||||
message:
|
||||
'OHIF only supports TID1500 Imaging Measurement Report Structured Reports. The SR you’re trying to view is not supported.',
|
||||
type: 'warning',
|
||||
duration: 6000,
|
||||
});
|
||||
return [];
|
||||
}
|
||||
const is3DSR = SOPClassUID === sopClassDictionary.Comprehensive3DSR;
|
||||
|
||||
const isImagingMeasurementReport =
|
||||
ConceptNameCodeSequence?.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurementReport;
|
||||
|
||||
const displaySet = {
|
||||
//plugin: id,
|
||||
Modality: 'SR',
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
SeriesDescription,
|
||||
@@ -137,13 +109,14 @@ function _getDisplaySetsFromSeries(
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId,
|
||||
SOPClassHandlerId: is3DSR ? SOPClassHandlerId3D : SOPClassHandlerId,
|
||||
SOPClassUID,
|
||||
instances,
|
||||
referencedImages: null,
|
||||
measurements: null,
|
||||
isDerivedDisplaySet: true,
|
||||
isLoaded: false,
|
||||
isImagingMeasurementReport,
|
||||
sopClassUids,
|
||||
instance,
|
||||
addInstances,
|
||||
@@ -154,12 +127,21 @@ function _getDisplaySetsFromSeries(
|
||||
return [displaySet];
|
||||
}
|
||||
|
||||
async function _load(displaySet, servicesManager: AppTypes.ServicesManager, extensionManager) {
|
||||
/**
|
||||
* Loads the display set with the given services and extension manager.
|
||||
* @param srDisplaySet - The display set to load.
|
||||
* @param servicesManager - The services manager containing displaySetService and measurementService.
|
||||
* @param extensionManager - The extension manager containing data sources.
|
||||
*/
|
||||
async function _load(
|
||||
srDisplaySet: Types.DisplaySet,
|
||||
servicesManager: AppTypes.ServicesManager,
|
||||
extensionManager: AppTypes.ExtensionManager
|
||||
) {
|
||||
const { displaySetService, measurementService } = servicesManager.services;
|
||||
const dataSources = extensionManager.getDataSources();
|
||||
const dataSource = dataSources[0];
|
||||
|
||||
const { ContentSequence } = displaySet.instance;
|
||||
const { ContentSequence } = srDisplaySet.instance;
|
||||
|
||||
async function retrieveBulkData(obj, parentObj = null, key = null) {
|
||||
for (const prop in obj) {
|
||||
@@ -170,9 +152,9 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
||||
} else if (prop === 'BulkDataURI') {
|
||||
const value = await dataSource.retrieve.bulkDataURI({
|
||||
BulkDataURI: obj[prop],
|
||||
StudyInstanceUID: displaySet.instance.StudyInstanceUID,
|
||||
SeriesInstanceUID: displaySet.instance.SeriesInstanceUID,
|
||||
SOPInstanceUID: displaySet.instance.SOPInstanceUID,
|
||||
StudyInstanceUID: srDisplaySet.instance.StudyInstanceUID,
|
||||
SeriesInstanceUID: srDisplaySet.instance.SeriesInstanceUID,
|
||||
SOPInstanceUID: srDisplaySet.instance.SOPInstanceUID,
|
||||
});
|
||||
if (parentObj && key) {
|
||||
parentObj[key] = new Float32Array(value);
|
||||
@@ -181,40 +163,47 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
||||
}
|
||||
}
|
||||
|
||||
if (displaySet.isLoaded !== true) {
|
||||
if (srDisplaySet.isLoaded !== true) {
|
||||
await retrieveBulkData(ContentSequence);
|
||||
}
|
||||
|
||||
displaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
||||
displaySet.measurements = _getMeasurements(ContentSequence);
|
||||
if (srDisplaySet.isImagingMeasurementReport) {
|
||||
srDisplaySet.referencedImages = _getReferencedImagesList(ContentSequence);
|
||||
srDisplaySet.measurements = _getMeasurements(ContentSequence);
|
||||
} else {
|
||||
srDisplaySet.referencedImages = [];
|
||||
srDisplaySet.measurements = [];
|
||||
}
|
||||
|
||||
const mappings = measurementService.getSourceMappings(
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||
);
|
||||
|
||||
displaySet.isHydrated = false;
|
||||
displaySet.isRehydratable = isRehydratable(displaySet, mappings);
|
||||
displaySet.isLoaded = true;
|
||||
srDisplaySet.isHydrated = false;
|
||||
srDisplaySet.isRehydratable = isRehydratable(srDisplaySet, mappings);
|
||||
srDisplaySet.isLoaded = true;
|
||||
|
||||
// Check currently added displaySets and add measurements if the sources exist.
|
||||
/** Check currently added displaySets and add measurements if the sources exist */
|
||||
displaySetService.activeDisplaySets.forEach(activeDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
srDisplaySet,
|
||||
activeDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
);
|
||||
});
|
||||
|
||||
// Subscribe to new displaySets as the source may come in after.
|
||||
/** Subscribe to new displaySets as the source may come in after */
|
||||
displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => {
|
||||
const { displaySetsAdded } = data;
|
||||
// If there are still some measurements that have not yet been loaded into cornerstone,
|
||||
// See if we can load them onto any of the new displaySets.
|
||||
/**
|
||||
* If there are still some measurements that have not yet been loaded into cornerstone,
|
||||
* See if we can load them onto any of the new displaySets.
|
||||
*/
|
||||
displaySetsAdded.forEach(newDisplaySet => {
|
||||
_checkIfCanAddMeasurementsToDisplaySet(
|
||||
displaySet,
|
||||
srDisplaySet,
|
||||
newDisplaySet,
|
||||
dataSource,
|
||||
servicesManager
|
||||
@@ -223,6 +212,14 @@ async function _load(displaySet, servicesManager: AppTypes.ServicesManager, exte
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if measurements can be added to a display set.
|
||||
*
|
||||
* @param srDisplaySet - The source display set containing measurements.
|
||||
* @param newDisplaySet - The new display set to check if measurements can be added.
|
||||
* @param dataSource - The data source used to retrieve image IDs.
|
||||
* @param servicesManager - The services manager.
|
||||
*/
|
||||
function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
srDisplaySet,
|
||||
newDisplaySet,
|
||||
@@ -230,108 +227,103 @@ function _checkIfCanAddMeasurementsToDisplaySet(
|
||||
servicesManager: AppTypes.ServicesManager
|
||||
) {
|
||||
const { customizationService } = servicesManager.services;
|
||||
let unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||
|
||||
const unloadedMeasurements = srDisplaySet.measurements.filter(
|
||||
measurement => measurement.loaded === false
|
||||
);
|
||||
|
||||
if (unloadedMeasurements.length === 0) {
|
||||
// All already loaded!
|
||||
if (
|
||||
unloadedMeasurements.length === 0 ||
|
||||
!(newDisplaySet instanceof ImageSet) ||
|
||||
newDisplaySet.unsupported
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!newDisplaySet) instanceof ImageSet) {
|
||||
// This also filters out _this_ displaySet, as it is not an ImageSet.
|
||||
// const { sopClassUids } = newDisplaySet;
|
||||
// Create a Set for faster lookups
|
||||
// const sopClassUidSet = new Set(sopClassUids);
|
||||
|
||||
// Create a Map to efficiently look up ImageIds by SOPInstanceUID and frame number
|
||||
const imageIdMap = new Map<string, string>();
|
||||
const imageIds = dataSource.getImageIdsForDisplaySet(newDisplaySet);
|
||||
|
||||
for (const imageId of imageIds) {
|
||||
const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(imageId);
|
||||
const key = `${SOPInstanceUID}:${frameNumber || 1}`;
|
||||
imageIdMap.set(key, imageId);
|
||||
}
|
||||
|
||||
if (!unloadedMeasurements?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newDisplaySet.unsupported) {
|
||||
return;
|
||||
}
|
||||
const is3DSR = srDisplaySet.SOPClassUID === sopClassDictionary.Comprehensive3DSR;
|
||||
|
||||
const { sopClassUids } = newDisplaySet;
|
||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||
let measurement = unloadedMeasurements[j];
|
||||
|
||||
// Check if any have the newDisplaySet is the correct SOPClass.
|
||||
unloadedMeasurements = unloadedMeasurements.filter(measurement =>
|
||||
measurement.coords.some(coord =>
|
||||
sopClassUids.includes(coord.ReferencedSOPSequence.ReferencedSOPClassUID)
|
||||
)
|
||||
);
|
||||
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
|
||||
'onBeforeSRAddMeasurement'
|
||||
)?.value;
|
||||
|
||||
if (unloadedMeasurements.length === 0) {
|
||||
// New displaySet isn't the correct SOPClass, so can't contain the referenced images.
|
||||
return;
|
||||
}
|
||||
|
||||
const SOPInstanceUIDs = [];
|
||||
|
||||
unloadedMeasurements.forEach(measurement => {
|
||||
const { coords } = measurement;
|
||||
|
||||
coords.forEach(coord => {
|
||||
const SOPInstanceUID = coord.ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||
|
||||
if (!SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||
SOPInstanceUIDs.push(SOPInstanceUID);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(newDisplaySet);
|
||||
|
||||
for (const imageId of imageIdsForDisplaySet) {
|
||||
if (!unloadedMeasurements.length) {
|
||||
// All measurements loaded.
|
||||
return;
|
||||
if (typeof onBeforeSRAddMeasurement === 'function') {
|
||||
measurement = onBeforeSRAddMeasurement({
|
||||
measurement,
|
||||
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
||||
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
|
||||
});
|
||||
}
|
||||
|
||||
const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(imageId);
|
||||
// if it is 3d SR we can just add the SR annotation
|
||||
if (is3DSR) {
|
||||
addSRAnnotation(measurement, null, null);
|
||||
measurement.loaded = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SOPInstanceUIDs.includes(SOPInstanceUID)) {
|
||||
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
|
||||
let measurement = unloadedMeasurements[j];
|
||||
const referencedSOPSequence = measurement.coords[0].ReferencedSOPSequence;
|
||||
if (!referencedSOPSequence) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
|
||||
'onBeforeSRAddMeasurement'
|
||||
)?.value;
|
||||
const { ReferencedSOPInstanceUID } = referencedSOPSequence;
|
||||
const frame = referencedSOPSequence.ReferencedFrameNumber || 1;
|
||||
const key = `${ReferencedSOPInstanceUID}:${frame}`;
|
||||
const imageId = imageIdMap.get(key);
|
||||
|
||||
if (typeof onBeforeSRAddMeasurement === 'function') {
|
||||
measurement = onBeforeSRAddMeasurement({
|
||||
measurement,
|
||||
StudyInstanceUID: srDisplaySet.StudyInstanceUID,
|
||||
SeriesInstanceUID: srDisplaySet.SeriesInstanceUID,
|
||||
});
|
||||
}
|
||||
if (
|
||||
imageId &&
|
||||
_measurementReferencesSOPInstanceUID(measurement, ReferencedSOPInstanceUID, frame)
|
||||
) {
|
||||
addSRAnnotation(measurement, imageId, frame);
|
||||
|
||||
if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) {
|
||||
const frame =
|
||||
(measurement.coords[0].ReferencedSOPSequence &&
|
||||
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
||||
1;
|
||||
// Update measurement properties
|
||||
measurement.loaded = true;
|
||||
measurement.imageId = imageId;
|
||||
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||
measurement.ReferencedSOPInstanceUID = ReferencedSOPInstanceUID;
|
||||
measurement.frameNumber = frame;
|
||||
|
||||
/** Add DICOMSRDisplay annotation for the SR viewport (only) */
|
||||
addDICOMSRDisplayAnnotation(measurement, imageId, frame);
|
||||
|
||||
/** Update measurement properties */
|
||||
measurement.loaded = true;
|
||||
measurement.imageId = imageId;
|
||||
measurement.displaySetInstanceUID = newDisplaySet.displaySetInstanceUID;
|
||||
measurement.ReferencedSOPInstanceUID =
|
||||
measurement.coords[0].ReferencedSOPSequence.ReferencedSOPInstanceUID;
|
||||
measurement.frameNumber = frame;
|
||||
delete measurement.coords;
|
||||
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
}
|
||||
}
|
||||
unloadedMeasurements.splice(j, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a measurement references a specific SOP Instance UID.
|
||||
* @param measurement - The measurement object.
|
||||
* @param SOPInstanceUID - The SOP Instance UID to check against.
|
||||
* @param frameNumber - The frame number to check against (optional).
|
||||
* @returns True if the measurement references the specified SOP Instance UID, false otherwise.
|
||||
*/
|
||||
function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) {
|
||||
const { coords } = measurement;
|
||||
|
||||
// NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
||||
// Standard. But for now, we will support only one ReferenceFrameNumber.
|
||||
/**
|
||||
* NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM
|
||||
* Standard. But for now, we will support only one ReferenceFrameNumber.
|
||||
*/
|
||||
const ReferencedFrameNumber =
|
||||
(measurement.coords[0].ReferencedSOPSequence &&
|
||||
measurement.coords[0].ReferencedSOPSequence?.ReferencedFrameNumber) ||
|
||||
@@ -344,40 +336,62 @@ function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frame
|
||||
for (let j = 0; j < coords.length; j++) {
|
||||
const coord = coords[j];
|
||||
const { ReferencedSOPInstanceUID } = coord.ReferencedSOPSequence;
|
||||
|
||||
if (ReferencedSOPInstanceUID === SOPInstanceUID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the SOP class handler module.
|
||||
*
|
||||
* @param {Object} options - The options for retrieving the SOP class handler module.
|
||||
* @param {Object} options.servicesManager - The services manager.
|
||||
* @param {Object} options.extensionManager - The extension manager.
|
||||
* @returns {Array} An array containing the SOP class handler module.
|
||||
*/
|
||||
function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
const getDisplaySetsFromSeries = instances => {
|
||||
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
name: SOPClassHandlerName,
|
||||
sopClassUids,
|
||||
getDisplaySetsFromSeries,
|
||||
},
|
||||
{
|
||||
name: SOPClassHandlerName3D,
|
||||
sopClassUids: [sopClassDictionary.Comprehensive3DSR],
|
||||
getDisplaySetsFromSeries,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the measurements from the ImagingMeasurementReportContentSequence.
|
||||
*
|
||||
* @param {Array} ImagingMeasurementReportContentSequence - The ImagingMeasurementReportContentSequence array.
|
||||
* @returns {Array} - The array of measurements.
|
||||
*/
|
||||
function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
const ImagingMeasurements = ImagingMeasurementReportContentSequence.find(
|
||||
item =>
|
||||
item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements
|
||||
);
|
||||
|
||||
if (!ImagingMeasurements) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter(
|
||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup
|
||||
);
|
||||
|
||||
const mergedContentSequencesByTrackingUniqueIdentifiers =
|
||||
_getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups);
|
||||
|
||||
const measurements = [];
|
||||
|
||||
Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach(
|
||||
@@ -386,7 +400,6 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier];
|
||||
|
||||
const measurement = _processMeasurement(mergedContentSequence);
|
||||
|
||||
if (measurement) {
|
||||
measurements.push(measurement);
|
||||
}
|
||||
@@ -396,6 +409,12 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) {
|
||||
return measurements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves merged content sequences by tracking unique identifiers.
|
||||
*
|
||||
* @param {Array} MeasurementGroups - The measurement groups.
|
||||
* @returns {Object} - The merged content sequences by tracking unique identifiers.
|
||||
*/
|
||||
function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) {
|
||||
const mergedContentSequencesByTrackingUniqueIdentifiers = {};
|
||||
|
||||
@@ -407,7 +426,6 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
||||
item.ConceptNameCodeSequence.CodeValue ===
|
||||
CodeNameCodeSequenceValues.TrackingUniqueIdentifier
|
||||
);
|
||||
|
||||
if (!TrackingUniqueIdentifierItem) {
|
||||
console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.');
|
||||
}
|
||||
@@ -436,6 +454,15 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups
|
||||
return mergedContentSequencesByTrackingUniqueIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the measurement based on the merged content sequence.
|
||||
* If the merged content sequence contains SCOORD or SCOORD3D value types,
|
||||
* it calls the _processTID1410Measurement function.
|
||||
* Otherwise, it calls the _processNonGeometricallyDefinedMeasurement function.
|
||||
*
|
||||
* @param {Array<Object>} mergedContentSequence - The merged content sequence to process.
|
||||
* @returns {any} - The processed measurement result.
|
||||
*/
|
||||
function _processMeasurement(mergedContentSequence) {
|
||||
if (
|
||||
mergedContentSequence.some(
|
||||
@@ -448,11 +475,21 @@ function _processMeasurement(mergedContentSequence) {
|
||||
return _processNonGeometricallyDefinedMeasurement(mergedContentSequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes TID 1410 style measurements from the mergedContentSequence.
|
||||
* TID 1410 style measurements have a SCOORD or SCOORD3D at the top level,
|
||||
* and non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D.
|
||||
*
|
||||
* @param mergedContentSequence - The merged content sequence containing the measurements.
|
||||
* @returns The measurement object containing the loaded status, labels, coordinates, tracking unique identifier, and tracking identifier.
|
||||
*/
|
||||
function _processTID1410Measurement(mergedContentSequence) {
|
||||
// Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level,
|
||||
// And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D
|
||||
|
||||
const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD');
|
||||
const graphicItem = mergedContentSequence.find(
|
||||
group => group.ValueType === 'SCOORD' || group.ValueType === 'SCOORD3D'
|
||||
);
|
||||
|
||||
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
||||
|
||||
@@ -479,7 +516,6 @@ function _processTID1410Measurement(mergedContentSequence) {
|
||||
|
||||
NUMContentItems.forEach(item => {
|
||||
const { ConceptNameCodeSequence, MeasuredValueSequence } = item;
|
||||
|
||||
if (MeasuredValueSequence) {
|
||||
measurement.labels.push(
|
||||
_getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence)
|
||||
@@ -487,12 +523,29 @@ function _processTID1410Measurement(mergedContentSequence) {
|
||||
}
|
||||
});
|
||||
|
||||
const findingSites = mergedContentSequence.filter(
|
||||
item =>
|
||||
item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SCT &&
|
||||
item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSiteSCT
|
||||
);
|
||||
if (findingSites.length) {
|
||||
measurement.labels.push({
|
||||
label: CodeNameCodeSequenceValues.FindingSiteSCT,
|
||||
value: findingSites[0].ConceptCodeSequence.CodeMeaning,
|
||||
});
|
||||
}
|
||||
|
||||
return measurement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the non-geometrically defined measurement from the merged content sequence.
|
||||
*
|
||||
* @param mergedContentSequence The merged content sequence containing the measurement data.
|
||||
* @returns The processed measurement object.
|
||||
*/
|
||||
function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM');
|
||||
|
||||
const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF');
|
||||
|
||||
const TrackingIdentifierContentItem = mergedContentSequence.find(
|
||||
@@ -522,10 +575,10 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
||||
finding.ConceptCodeSequence.CodingSchemeDesignator
|
||||
) &&
|
||||
finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText
|
||||
finding.ConceptCodeSequence.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
|
||||
) {
|
||||
measurement.labels.push({
|
||||
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
||||
label: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
||||
value: finding.ConceptCodeSequence.CodeMeaning,
|
||||
});
|
||||
}
|
||||
@@ -537,12 +590,13 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
CodingSchemeDesignators.CornerstoneCodeSchemes.includes(
|
||||
FindingSite.ConceptCodeSequence.CodingSchemeDesignator
|
||||
) &&
|
||||
FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText
|
||||
FindingSite.ConceptCodeSequence.CodeValue ===
|
||||
Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
|
||||
);
|
||||
|
||||
if (cornerstoneFreeTextFindingSite) {
|
||||
measurement.labels.push({
|
||||
label: CORNERSTONE_FREETEXT_CODE_VALUE,
|
||||
label: Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT,
|
||||
value: cornerstoneFreeTextFindingSite.ConceptCodeSequence.CodeMeaning,
|
||||
});
|
||||
}
|
||||
@@ -552,15 +606,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item;
|
||||
|
||||
const { ValueType } = ContentSequence;
|
||||
|
||||
if (!ValueType === 'SCOORD') {
|
||||
console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const coords = _getCoordsFromSCOORDOrSCOORD3D(ContentSequence);
|
||||
|
||||
if (coords) {
|
||||
measurement.coords.push(coords);
|
||||
}
|
||||
@@ -575,59 +626,62 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
|
||||
return measurement;
|
||||
}
|
||||
|
||||
function _getCoordsFromSCOORDOrSCOORD3D(item) {
|
||||
const { ValueType, RelationshipType, GraphicType, GraphicData } = item;
|
||||
|
||||
if (
|
||||
!(
|
||||
RelationshipType == RELATIONSHIP_TYPE.INFERRED_FROM ||
|
||||
RelationshipType == RELATIONSHIP_TYPE.CONTAINS
|
||||
)
|
||||
) {
|
||||
console.warn(
|
||||
`Relationshiptype === ${RelationshipType}. Cannot deal with NON TID-1400 SCOORD group with RelationshipType !== "INFERRED FROM" or "CONTAINS"`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts coordinates from a graphic item of type SCOORD or SCOORD3D.
|
||||
* @param {object} graphicItem - The graphic item containing the coordinates.
|
||||
* @returns {object} - The extracted coordinates.
|
||||
*/
|
||||
const _getCoordsFromSCOORDOrSCOORD3D = graphicItem => {
|
||||
const { ValueType, GraphicType, GraphicData } = graphicItem;
|
||||
const coords = { ValueType, GraphicType, GraphicData };
|
||||
|
||||
// ContentSequence has length of 1 as RelationshipType === 'INFERRED FROM'
|
||||
if (ValueType === 'SCOORD') {
|
||||
const { ReferencedSOPSequence } = item.ContentSequence;
|
||||
|
||||
coords.ReferencedSOPSequence = ReferencedSOPSequence;
|
||||
} else if (ValueType === 'SCOORD3D') {
|
||||
const { ReferencedFrameOfReferenceSequence } = item.ContentSequence;
|
||||
|
||||
coords.ReferencedFrameOfReferenceSequence = ReferencedFrameOfReferenceSequence;
|
||||
}
|
||||
|
||||
coords.ReferencedSOPSequence = graphicItem.ContentSequence?.ReferencedSOPSequence;
|
||||
coords.ReferencedFrameOfReferenceSequence =
|
||||
graphicItem.ReferencedFrameOfReferenceUID ||
|
||||
graphicItem.ContentSequence?.ReferencedFrameOfReferenceSequence;
|
||||
return coords;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the label and value from the provided ConceptNameCodeSequence and MeasuredValueSequence.
|
||||
* @param {Object} ConceptNameCodeSequence - The ConceptNameCodeSequence object.
|
||||
* @param {Object} MeasuredValueSequence - The MeasuredValueSequence object.
|
||||
* @returns {Object} - An object containing the label and value.
|
||||
* The label represents the CodeMeaning from the ConceptNameCodeSequence.
|
||||
* The value represents the formatted NumericValue and CodeValue from the MeasuredValueSequence.
|
||||
* Example: { label: 'Long Axis', value: '31.00 mm' }
|
||||
*/
|
||||
function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) {
|
||||
const { CodeMeaning } = ConceptNameCodeSequence;
|
||||
const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence;
|
||||
const { CodeValue } = MeasurementUnitsCodeSequence;
|
||||
|
||||
const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : '';
|
||||
|
||||
return {
|
||||
label: CodeMeaning,
|
||||
value: `${formatedNumericValue} ${CodeValue}`,
|
||||
}; // E.g. Long Axis: 31.0 mm
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of referenced images from the Imaging Measurement Report Content Sequence.
|
||||
*
|
||||
* @param {Array} ImagingMeasurementReportContentSequence - The Imaging Measurement Report Content Sequence.
|
||||
* @returns {Array} - The list of referenced images.
|
||||
*/
|
||||
function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
const ImageLibrary = ImagingMeasurementReportContentSequence.find(
|
||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary
|
||||
);
|
||||
|
||||
if (!ImageLibrary) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find(
|
||||
item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup
|
||||
);
|
||||
if (!ImageLibraryGroup) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const referencedImages = [];
|
||||
|
||||
@@ -651,6 +705,15 @@ function _getReferencedImagesList(ImagingMeasurementReportContentSequence) {
|
||||
return referencedImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a DICOM sequence to an array.
|
||||
* If the sequence is null or undefined, an empty array is returned.
|
||||
* If the sequence is already an array, it is returned as is.
|
||||
* Otherwise, the sequence is wrapped in an array and returned.
|
||||
*
|
||||
* @param {any} sequence - The DICOM sequence to convert.
|
||||
* @returns {any[]} - The converted array.
|
||||
*/
|
||||
function _getSequenceAsArray(sequence) {
|
||||
if (!sequence) {
|
||||
return [];
|
||||
|
||||
@@ -5,4 +5,7 @@ const id = packageJson.name;
|
||||
const SOPClassHandlerName = 'dicom-sr';
|
||||
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
||||
|
||||
export { SOPClassHandlerName, SOPClassHandlerId, id };
|
||||
const SOPClassHandlerName3D = 'dicom-sr-3d';
|
||||
const SOPClassHandlerId3D = `${id}.sopClassHandlerModule.${SOPClassHandlerName3D}`;
|
||||
|
||||
export { SOPClassHandlerName, SOPClassHandlerId, SOPClassHandlerName3D, SOPClassHandlerId3D, id };
|
||||
@@ -8,9 +8,10 @@ import { id } from './id.js';
|
||||
import toolNames from './tools/toolNames';
|
||||
import hydrateStructuredReport from './utils/hydrateStructuredReport';
|
||||
import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet';
|
||||
import Enums from './enums';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport');
|
||||
return import(/* webpackPrefetch: true */ './components/OHIFCornerstoneSRViewport');
|
||||
});
|
||||
|
||||
const OHIFCornerstoneSRViewport = props => {
|
||||
@@ -70,4 +71,4 @@ const dicomSRExtension = {
|
||||
export default dicomSRExtension;
|
||||
|
||||
// Put static exports here so they can be type checked
|
||||
export { hydrateStructuredReport, createReferencedImageDisplaySet, srProtocol };
|
||||
export { hydrateStructuredReport, createReferencedImageDisplaySet, srProtocol, Enums, toolNames };
|
||||
@@ -9,16 +9,28 @@ import {
|
||||
LengthTool,
|
||||
PlanarFreehandROITool,
|
||||
RectangleROITool,
|
||||
utilities as csToolsUtils,
|
||||
} from '@cornerstonejs/tools';
|
||||
import { Types, MeasurementService } from '@ohif/core';
|
||||
import { StackViewport, utilities as csUtils } from '@cornerstonejs/core';
|
||||
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
|
||||
import SCOORD3DPointTool from './tools/SCOORD3DPointTool';
|
||||
import SRSCOOR3DProbeMapper from './utils/SRSCOOR3DProbeMapper';
|
||||
import addToolInstance from './utils/addToolInstance';
|
||||
import { Types } from '@ohif/core';
|
||||
import toolNames from './tools/toolNames';
|
||||
|
||||
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||
|
||||
/**
|
||||
* @param {object} configuration
|
||||
*/
|
||||
export default function init({ configuration = {} }: Types.Extensions.ExtensionParams): void {
|
||||
export default function init({
|
||||
configuration = {},
|
||||
servicesManager,
|
||||
}: Types.Extensions.ExtensionParams): void {
|
||||
const { measurementService, cornerstoneViewportService } = servicesManager.services;
|
||||
|
||||
addToolInstance(toolNames.DICOMSRDisplay, DICOMSRDisplayTool);
|
||||
addToolInstance(toolNames.SRLength, LengthTool);
|
||||
addToolInstance(toolNames.SRBidirectional, BidirectionalTool);
|
||||
@@ -28,10 +40,26 @@ export default function init({ configuration = {} }: Types.Extensions.ExtensionP
|
||||
addToolInstance(toolNames.SRAngle, AngleTool);
|
||||
addToolInstance(toolNames.SRPlanarFreehandROI, PlanarFreehandROITool);
|
||||
addToolInstance(toolNames.SRRectangleROI, RectangleROITool);
|
||||
addToolInstance(toolNames.SRSCOORD3DPoint, SCOORD3DPointTool);
|
||||
|
||||
// TODO - fix the SR display of Cobb Angle, as it joins the two lines
|
||||
addToolInstance(toolNames.SRCobbAngle, CobbAngleTool);
|
||||
|
||||
const csTools3DVer1MeasurementSource = measurementService.getSource(
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_NAME,
|
||||
CORNERSTONE_3D_TOOLS_SOURCE_VERSION
|
||||
);
|
||||
|
||||
const { POINT } = measurementService.VALUE_TYPES;
|
||||
|
||||
measurementService.addMapping(
|
||||
csTools3DVer1MeasurementSource,
|
||||
'SRSCOORD3DPoint',
|
||||
POINT,
|
||||
SRSCOOR3DProbeMapper.toAnnotation,
|
||||
SRSCOOR3DProbeMapper.toMeasurement
|
||||
);
|
||||
|
||||
// Modify annotation tools to use dashed lines on SR
|
||||
const dashedLine = {
|
||||
lineDash: '4,4',
|
||||
@@ -49,4 +77,28 @@ export default function init({ configuration = {} }: Types.Extensions.ExtensionP
|
||||
SRRectangleROI: dashedLine,
|
||||
global: {},
|
||||
});
|
||||
|
||||
measurementService.subscribe(
|
||||
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
|
||||
({ viewportId, measurement, isConsumed }) => {
|
||||
if (isConsumed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const currentViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
const { viewPlaneNormal } = currentViewport.getCamera();
|
||||
const referencedImageId = csToolsUtils.getClosestImageIdForStackViewport(
|
||||
currentViewport as StackViewport,
|
||||
measurement.points[0],
|
||||
viewPlaneNormal
|
||||
);
|
||||
const imageIndex = (currentViewport as StackViewport)
|
||||
.getImageIds()
|
||||
.indexOf(referencedImageId);
|
||||
csUtils.jumpToSlice(currentViewport.element, { imageIndex });
|
||||
} catch (error) {
|
||||
console.warn('Unable to jump to image based on measurement coordinate', error);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { SOPClassHandlerId } from './id';
|
||||
import { SOPClassHandlerId, SOPClassHandlerId3D } from './id';
|
||||
|
||||
export default function onModeEnter({ servicesManager }) {
|
||||
const { displaySetService } = servicesManager.services;
|
||||
const displaySetCache = displaySetService.getDisplaySetCache();
|
||||
|
||||
const srDisplaySets = [...displaySetCache.values()].filter(
|
||||
ds => ds.SOPClassHandlerId === SOPClassHandlerId
|
||||
ds => ds.SOPClassHandlerId === SOPClassHandlerId || ds.SOPClassHandlerId === SOPClassHandlerId3D
|
||||
);
|
||||
|
||||
srDisplaySets.forEach(ds => {
|
||||
|
||||
@@ -7,10 +7,11 @@ import {
|
||||
Types as cs3DToolsTypes,
|
||||
} from '@cornerstonejs/tools';
|
||||
import { getTrackingUniqueIdentifiersForElement } from './modules/dicomSRModule';
|
||||
import SCOORD_TYPES from '../constants/scoordTypes';
|
||||
import { SCOORDTypes } from '../enums';
|
||||
import toolNames from './toolNames';
|
||||
|
||||
export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
static toolName = 'DICOMSRDisplay';
|
||||
static toolName = toolNames.DICOMSRDisplay;
|
||||
|
||||
constructor(
|
||||
toolProps = {},
|
||||
@@ -112,19 +113,19 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
let canvasCoordinatesAdapter;
|
||||
|
||||
switch (GraphicType) {
|
||||
case SCOORD_TYPES.POINT:
|
||||
case SCOORDTypes.POINT:
|
||||
renderMethod = this.renderPoint;
|
||||
break;
|
||||
case SCOORD_TYPES.MULTIPOINT:
|
||||
case SCOORDTypes.MULTIPOINT:
|
||||
renderMethod = this.renderMultipoint;
|
||||
break;
|
||||
case SCOORD_TYPES.POLYLINE:
|
||||
case SCOORDTypes.POLYLINE:
|
||||
renderMethod = this.renderPolyLine;
|
||||
break;
|
||||
case SCOORD_TYPES.CIRCLE:
|
||||
case SCOORDTypes.CIRCLE:
|
||||
renderMethod = this.renderEllipse;
|
||||
break;
|
||||
case SCOORD_TYPES.ELLIPSE:
|
||||
case SCOORDTypes.ELLIPSE:
|
||||
renderMethod = this.renderEllipse;
|
||||
canvasCoordinatesAdapter = utilities.math.ellipse.getCanvasEllipseCorners;
|
||||
break;
|
||||
@@ -229,7 +230,11 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
// This gives us one point for arrow
|
||||
canvasCoordinates.push(viewport.worldToCanvas(point));
|
||||
|
||||
// We get the other point for the arrow by using the image size
|
||||
if (data[1] !== undefined) {
|
||||
canvasCoordinates.push(viewport.worldToCanvas(data[1]));
|
||||
}
|
||||
else{
|
||||
// We get the other point for the arrow by using the image size
|
||||
const imagePixelModule = metaData.get('imagePixelModule', referencedImageId);
|
||||
|
||||
let xOffset = 10;
|
||||
@@ -248,6 +253,9 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
]);
|
||||
|
||||
canvasCoordinates.push(viewport.worldToCanvas(arrowEnd));
|
||||
|
||||
}
|
||||
|
||||
|
||||
const arrowUID = `${index}`;
|
||||
|
||||
@@ -335,7 +343,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
}
|
||||
|
||||
const { annotationUID, data = {} } = annotation;
|
||||
const { label } = data;
|
||||
const { labels } = data;
|
||||
const { color } = options;
|
||||
|
||||
let adaptedCanvasCoordinates = canvasCoordinates;
|
||||
@@ -343,7 +351,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool {
|
||||
if (typeof canvasCoordinatesAdapter === 'function') {
|
||||
adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates);
|
||||
}
|
||||
const textLines = this._getTextBoxLinesFromLabels(label);
|
||||
const textLines = this._getTextBoxLinesFromLabels(labels);
|
||||
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
||||
|
||||
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { Types, metaData, utilities as csUtils } from '@cornerstonejs/core';
|
||||
import {
|
||||
annotation,
|
||||
drawing,
|
||||
utilities,
|
||||
Types as cs3DToolsTypes,
|
||||
AnnotationDisplayTool,
|
||||
} from '@cornerstonejs/tools';
|
||||
import toolNames from './toolNames';
|
||||
import { Annotation } from '@cornerstonejs/tools/dist/types/types';
|
||||
|
||||
export default class SCOORD3DPointTool extends AnnotationDisplayTool {
|
||||
static toolName = toolNames.SRSCOORD3DPoint;
|
||||
|
||||
constructor(
|
||||
toolProps = {},
|
||||
defaultToolProps = {
|
||||
configuration: {},
|
||||
}
|
||||
) {
|
||||
super(toolProps, defaultToolProps);
|
||||
}
|
||||
|
||||
_getTextBoxLinesFromLabels(labels) {
|
||||
// TODO -> max 5 for now (label + shortAxis + longAxis), need a generic solution for this!
|
||||
|
||||
const labelLength = Math.min(labels.length, 5);
|
||||
const lines = [];
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
// This tool should not inherit from AnnotationTool and we should not need
|
||||
// to add the following lines.
|
||||
isPointNearTool = () => null;
|
||||
getHandleNearImagePoint = () => null;
|
||||
|
||||
renderAnnotation = (enabledElement: Types.IEnabledElement, svgDrawingHelper: any): void => {
|
||||
const { viewport } = enabledElement;
|
||||
const { element } = viewport;
|
||||
|
||||
const annotations = annotation.state.getAnnotations(this.getToolName(), element);
|
||||
|
||||
// Todo: We don't need this anymore, filtering happens in triggerAnnotationRender
|
||||
if (!annotations?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter toolData to only render the data for the active SR.
|
||||
const filteredAnnotations = annotations;
|
||||
if (!viewport._actors?.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
const styleSpecifier: cs3DToolsTypes.AnnotationStyle.StyleSpecifier = {
|
||||
toolGroupId: this.toolGroupId,
|
||||
toolName: this.getToolName(),
|
||||
viewportId: enabledElement.viewport.id,
|
||||
};
|
||||
|
||||
for (let i = 0; i < filteredAnnotations.length; i++) {
|
||||
const annotation = filteredAnnotations[i];
|
||||
|
||||
const annotationUID = annotation.annotationUID;
|
||||
const { renderableData } = annotation.data;
|
||||
const { POINT: points } = renderableData;
|
||||
|
||||
styleSpecifier.annotationUID = annotationUID;
|
||||
|
||||
const lineWidth = this.getStyle('lineWidth', styleSpecifier, annotation);
|
||||
const lineDash = this.getStyle('lineDash', styleSpecifier, annotation);
|
||||
const color = this.getStyle('color', styleSpecifier, annotation);
|
||||
|
||||
const options = {
|
||||
color,
|
||||
lineDash,
|
||||
lineWidth,
|
||||
};
|
||||
|
||||
const point = points[0][0];
|
||||
|
||||
// check if viewport can render it
|
||||
const viewable = viewport.isReferenceViewable(
|
||||
{ FrameOfReferenceUID: annotation.metadata.FrameOfReferenceUID, cameraFocalPoint: point },
|
||||
{ asNearbyProjection: true }
|
||||
);
|
||||
|
||||
if (!viewable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// render the point
|
||||
const arrowPointCanvas = viewport.worldToCanvas(point);
|
||||
// Todo: configure this
|
||||
const arrowEndCanvas = [arrowPointCanvas[0] + 20, arrowPointCanvas[1] + 20];
|
||||
const canvasCoordinates = [arrowPointCanvas, arrowEndCanvas];
|
||||
|
||||
drawing.drawArrow(
|
||||
svgDrawingHelper,
|
||||
annotationUID,
|
||||
'1',
|
||||
canvasCoordinates[1],
|
||||
canvasCoordinates[0],
|
||||
{
|
||||
color: options.color,
|
||||
width: options.lineWidth,
|
||||
}
|
||||
);
|
||||
|
||||
this.renderTextBox(
|
||||
svgDrawingHelper,
|
||||
viewport,
|
||||
canvasCoordinates,
|
||||
annotation,
|
||||
styleSpecifier,
|
||||
options
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderTextBox(
|
||||
svgDrawingHelper,
|
||||
viewport,
|
||||
canvasCoordinates,
|
||||
annotation,
|
||||
styleSpecifier,
|
||||
options = {}
|
||||
) {
|
||||
if (!canvasCoordinates || !annotation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { annotationUID, data = {} } = annotation;
|
||||
const { labels } = data;
|
||||
|
||||
const textLines = [];
|
||||
|
||||
for (const label of labels) {
|
||||
// make this generic
|
||||
// fix this
|
||||
if (label.label === '363698007') {
|
||||
textLines.push(`Finding Site: ${label.value}`);
|
||||
}
|
||||
}
|
||||
|
||||
const { color } = options;
|
||||
|
||||
const adaptedCanvasCoordinates = canvasCoordinates;
|
||||
// adapt coordinates if there is an adapter
|
||||
const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates);
|
||||
|
||||
if (!annotation.data?.handles?.textBox?.worldPosition) {
|
||||
annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords);
|
||||
}
|
||||
|
||||
const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition);
|
||||
|
||||
const textBoxUID = '1';
|
||||
const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation);
|
||||
|
||||
const boundingBox = drawing.drawLinkedTextBox(
|
||||
svgDrawingHelper,
|
||||
annotationUID,
|
||||
textBoxUID,
|
||||
textLines,
|
||||
textBoxPosition,
|
||||
canvasCoordinates,
|
||||
{},
|
||||
{
|
||||
...textBoxOptions,
|
||||
color,
|
||||
}
|
||||
);
|
||||
|
||||
const { x: left, y: top, width, height } = boundingBox;
|
||||
|
||||
annotation.data.handles.textBox.worldBoundingBox = {
|
||||
topLeft: viewport.canvasToWorld([left, top]),
|
||||
topRight: viewport.canvasToWorld([left + width, top]),
|
||||
bottomLeft: viewport.canvasToWorld([left, top + height]),
|
||||
bottomRight: viewport.canvasToWorld([left + width, top + height]),
|
||||
};
|
||||
}
|
||||
|
||||
public getLinkedTextBoxStyle(
|
||||
specifications: cs3DToolsTypes.AnnotationStyle.StyleSpecifier,
|
||||
annotation?: Annotation
|
||||
): Record<string, unknown> {
|
||||
// Todo: this function can be used to set different styles for different toolMode
|
||||
// for the textBox.
|
||||
|
||||
return {
|
||||
visibility: this.getStyle('textBoxVisibility', specifications, annotation),
|
||||
fontFamily: this.getStyle('textBoxFontFamily', specifications, annotation),
|
||||
fontSize: this.getStyle('textBoxFontSize', specifications, annotation),
|
||||
color: this.getStyle('textBoxColor', specifications, annotation),
|
||||
shadow: this.getStyle('textBoxShadow', specifications, annotation),
|
||||
background: this.getStyle('textBoxBackground', specifications, annotation),
|
||||
lineWidth: this.getStyle('textBoxLinkLineWidth', specifications, annotation),
|
||||
lineDash: this.getStyle('textBoxLinkLineDash', specifications, annotation),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import DICOMSRDisplayTool from './DICOMSRDisplayTool';
|
||||
|
||||
const toolNames = {
|
||||
DICOMSRDisplay: DICOMSRDisplayTool.toolName,
|
||||
DICOMSRDisplay: 'DICOMSRDisplay',
|
||||
SRLength: 'SRLength',
|
||||
SRBidirectional: 'SRBidirectional',
|
||||
SREllipticalROI: 'SREllipticalROI',
|
||||
@@ -11,6 +9,7 @@ const toolNames = {
|
||||
SRCobbAngle: 'SRCobbAngle',
|
||||
SRRectangleROI: 'SRRectangleROI',
|
||||
SRPlanarFreehandROI: 'SRPlanarFreehandROI',
|
||||
SRSCOORD3DPoint: 'SRSCOORD3DPoint',
|
||||
};
|
||||
|
||||
export default toolNames;
|
||||
@@ -0,0 +1,62 @@
|
||||
const SRSCOOR3DProbe = {
|
||||
toAnnotation: measurement => {},
|
||||
|
||||
/**
|
||||
* Maps cornerstone annotation event data to measurement service format.
|
||||
*
|
||||
* @param {Object} cornerstone Cornerstone event data
|
||||
* @return {Measurement} Measurement instance
|
||||
*/
|
||||
toMeasurement: (
|
||||
csToolsEventDetail,
|
||||
displaySetService,
|
||||
CornerstoneViewportService,
|
||||
getValueTypeFromToolType,
|
||||
customizationService
|
||||
) => {
|
||||
const { annotation } = csToolsEventDetail;
|
||||
const { metadata, data, annotationUID } = annotation;
|
||||
|
||||
if (!metadata || !data) {
|
||||
console.warn('Probe tool: Missing metadata or data');
|
||||
return null;
|
||||
}
|
||||
|
||||
const { toolName } = metadata;
|
||||
const { points } = data.handles;
|
||||
|
||||
const displayText = getDisplayText(annotation);
|
||||
return {
|
||||
uid: annotationUID,
|
||||
points,
|
||||
metadata,
|
||||
toolName: metadata.toolName,
|
||||
label: data.label,
|
||||
displayText: displayText,
|
||||
data: data.cachedStats,
|
||||
type: getValueTypeFromToolType?.(toolName) ?? null,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function getDisplayText(annotation) {
|
||||
const { data } = annotation;
|
||||
|
||||
if (!data) {
|
||||
return [''];
|
||||
}
|
||||
const { labels } = data;
|
||||
|
||||
const displayText = [];
|
||||
|
||||
for (const label of labels) {
|
||||
// make this generic
|
||||
if (label.label === '33636980076') {
|
||||
displayText.push(`Finding Site: ${label.value}`);
|
||||
}
|
||||
}
|
||||
|
||||
return displayText;
|
||||
}
|
||||
|
||||
export default SRSCOOR3DProbe;
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Types, annotation } from '@cornerstonejs/tools';
|
||||
import { metaData } from '@cornerstonejs/core';
|
||||
|
||||
import getRenderableData from './getRenderableData';
|
||||
import toolNames from '../tools/toolNames';
|
||||
|
||||
export default function addSRAnnotation(measurement, imageId, frameNumber) {
|
||||
let toolName = toolNames.DICOMSRDisplay;
|
||||
const renderableData = measurement.coords.reduce((acc, coordProps) => {
|
||||
acc[coordProps.GraphicType] = acc[coordProps.GraphicType] || [];
|
||||
acc[coordProps.GraphicType].push(getRenderableData({ ...coordProps, imageId }));
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const { TrackingUniqueIdentifier } = measurement;
|
||||
const { ValueType: valueType, GraphicType: graphicType } = measurement.coords[0];
|
||||
const graphicTypePoints = renderableData[graphicType];
|
||||
|
||||
/** TODO: Read the tool name from the DICOM SR identification type in the future. */
|
||||
let frameOfReferenceUID = null;
|
||||
|
||||
if (imageId) {
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||
frameOfReferenceUID = imagePlaneModule?.frameOfReferenceUID;
|
||||
}
|
||||
|
||||
if (valueType === 'SCOORD3D') {
|
||||
toolName = toolNames.SRSCOORD3DPoint;
|
||||
|
||||
// get the ReferencedFrameOfReferenceUID from the measurement
|
||||
frameOfReferenceUID = measurement.coords[0].ReferencedFrameOfReferenceSequence;
|
||||
}
|
||||
|
||||
const SRAnnotation: Types.Annotation = {
|
||||
annotationUID: TrackingUniqueIdentifier,
|
||||
highlighted: false,
|
||||
isLocked: false,
|
||||
invalidated: false,
|
||||
metadata: {
|
||||
toolName,
|
||||
valueType,
|
||||
graphicType,
|
||||
FrameOfReferenceUID: frameOfReferenceUID,
|
||||
referencedImageId: imageId,
|
||||
},
|
||||
data: {
|
||||
label: measurement.labels?.[0]?.value || undefined,
|
||||
displayText: measurement.displayText || undefined,
|
||||
handles: {
|
||||
textBox: measurement.textBox ?? {},
|
||||
points: graphicTypePoints[0],
|
||||
},
|
||||
cachedStats: {},
|
||||
frameNumber,
|
||||
renderableData,
|
||||
TrackingUniqueIdentifier,
|
||||
labels: measurement.labels,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* const annotationManager = annotation.annotationState.getAnnotationManager();
|
||||
* was not triggering annotation_added events.
|
||||
*/
|
||||
annotation.state.addAnnotation(SRAnnotation);
|
||||
console.debug('Adding SR annotation:', SRAnnotation);
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import { addTool } from '@cornerstonejs/tools';
|
||||
|
||||
export default function addToolInstance(name: string, toolClass, configuration?): void {
|
||||
export default function addToolInstance(name: string, toolClass, configuration = {}): void {
|
||||
class InstanceClass extends toolClass {
|
||||
static toolName = name;
|
||||
constructor(toolProps, defaultToolProps) {
|
||||
toolProps.configuration = toolProps.configuration
|
||||
? { ...toolProps.configuration, ...configuration }
|
||||
: configuration;
|
||||
super(toolProps, defaultToolProps);
|
||||
}
|
||||
}
|
||||
addTool(InstanceClass);
|
||||
}
|
||||
@@ -61,6 +61,11 @@ const createReferencedImageDisplaySet = (displaySetService, displaySet) => {
|
||||
|
||||
const imageSet = new ImageSet(instances);
|
||||
const instance = instances[0];
|
||||
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
imageSet.setAttributes({
|
||||
displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID
|
||||
SeriesDate: instance.SeriesDate,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { utils } from '@ohif/core';
|
||||
|
||||
/**
|
||||
* Formatters used to format each of the content items (SR "nodes") which can be
|
||||
* text, code, UID ref, number, person name, date, time and date time. Each
|
||||
* formatter must be a function with the following signature:
|
||||
*
|
||||
* [VALUE_TYPE]: (contentItem) => string
|
||||
*
|
||||
*/
|
||||
const contentItemFormatters = {
|
||||
TEXT: contentItem => contentItem.TextValue,
|
||||
CODE: contentItem => contentItem.ConceptCodeSequence?.[0]?.CodeMeaning,
|
||||
UIDREF: contentItem => contentItem.UID,
|
||||
NUM: contentItem => {
|
||||
const measuredValue = contentItem.MeasuredValueSequence?.[0];
|
||||
|
||||
if (!measuredValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { NumericValue, MeasurementUnitsCodeSequence } = measuredValue;
|
||||
const { CodeValue } = MeasurementUnitsCodeSequence;
|
||||
|
||||
return `${NumericValue} ${CodeValue}`;
|
||||
},
|
||||
PNAME: contentItem => {
|
||||
const personName = contentItem.PersonName?.[0];
|
||||
return personName ? utils.formatPN(personName) : undefined;
|
||||
},
|
||||
DATE: contentItem => {
|
||||
const { Date } = contentItem;
|
||||
return Date ? utils.formatDate(Date) : undefined;
|
||||
},
|
||||
TIME: contentItem => {
|
||||
const { Time } = contentItem;
|
||||
return Time ? utils.formatTime(Time) : undefined;
|
||||
},
|
||||
DATETIME: contentItem => {
|
||||
const { DateTime } = contentItem;
|
||||
|
||||
if (typeof DateTime !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 14 characters because it should be something like 20180614113714
|
||||
if (DateTime.length < 14) {
|
||||
return DateTime;
|
||||
}
|
||||
|
||||
const dicomDate = DateTime.substring(0, 8);
|
||||
const dicomTime = DateTime.substring(8, 14);
|
||||
const formattedDate = utils.formatDate(dicomDate);
|
||||
const formattedTime = utils.formatTime(dicomTime);
|
||||
|
||||
return `${formattedDate} ${formattedTime}`;
|
||||
},
|
||||
};
|
||||
|
||||
function formatContentItemValue(contentItem) {
|
||||
const { ValueType } = contentItem;
|
||||
const fnFormat = contentItemFormatters[ValueType];
|
||||
|
||||
return fnFormat ? fnFormat(contentItem) : `[${ValueType} is not supported]`;
|
||||
}
|
||||
|
||||
export { formatContentItemValue as default, formatContentItemValue };
|
||||
@@ -1,3 +1,7 @@
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
|
||||
const { CodeScheme: Cornerstone3DCodeScheme } = adaptersSR.Cornerstone3D;
|
||||
|
||||
/**
|
||||
* Extracts the label from the toolData imported from dcmjs. We need to do this
|
||||
* as dcmjs does not depeend on OHIF/the measurementService, it just produces data for cornestoneTools.
|
||||
@@ -9,13 +13,15 @@
|
||||
export default function getLabelFromDCMJSImportedToolData(toolData) {
|
||||
const { findingSites = [], finding } = toolData;
|
||||
|
||||
let freeTextLabel = findingSites.find(fs => fs.CodeValue === 'CORNERSTONEFREETEXT');
|
||||
let freeTextLabel = findingSites.find(
|
||||
fs => fs.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT
|
||||
);
|
||||
|
||||
if (freeTextLabel) {
|
||||
return freeTextLabel.CodeMeaning;
|
||||
}
|
||||
|
||||
if (finding && finding.CodeValue === 'CORNERSTONEFREETEXT') {
|
||||
if (finding && finding.CodeValue === Cornerstone3DCodeScheme.codeValues.CORNERSTONEFREETEXT) {
|
||||
return finding.CodeMeaning;
|
||||
}
|
||||
}
|
||||
+35
-97
@@ -1,106 +1,49 @@
|
||||
import { vec3 } from 'gl-matrix';
|
||||
import { Types, annotation } from '@cornerstonejs/tools';
|
||||
import { metaData, utilities, Types as csTypes } from '@cornerstonejs/core';
|
||||
|
||||
import toolNames from '../tools/toolNames';
|
||||
import SCOORD_TYPES from '../constants/scoordTypes';
|
||||
import { SCOORDTypes } from '../enums';
|
||||
|
||||
const EPSILON = 1e-4;
|
||||
|
||||
export default function addDICOMSRDisplayAnnotation(measurement, imageId, frameNumber) {
|
||||
const toolName = toolNames.DICOMSRDisplay;
|
||||
|
||||
const measurementData = {
|
||||
TrackingUniqueIdentifier: measurement.TrackingUniqueIdentifier,
|
||||
renderableData: {},
|
||||
labels: measurement.labels,
|
||||
imageId,
|
||||
};
|
||||
|
||||
measurement.coords.forEach(coord => {
|
||||
const { GraphicType, GraphicData } = coord;
|
||||
|
||||
if (measurementData.renderableData[GraphicType] === undefined) {
|
||||
measurementData.renderableData[GraphicType] = [];
|
||||
const getRenderableCoords = ({ GraphicData, ValueType, imageId }) => {
|
||||
const renderableData = [];
|
||||
if (ValueType === 'SCOORD3D') {
|
||||
for (let i = 0; i < GraphicData.length; i += 3) {
|
||||
renderableData.push([GraphicData[i], GraphicData[i + 1], GraphicData[i + 2]]);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
const worldPos = utilities.imageToWorldCoords(imageId, [GraphicData[i], GraphicData[i + 1]]);
|
||||
renderableData.push(worldPos);
|
||||
}
|
||||
}
|
||||
return renderableData;
|
||||
};
|
||||
|
||||
measurementData.renderableData[GraphicType].push(
|
||||
_getRenderableData(GraphicType, GraphicData, imageId)
|
||||
);
|
||||
});
|
||||
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||
|
||||
/**
|
||||
* This annotation (DICOMSRDisplay) is only used by the SR viewport.
|
||||
* This is used before the annotation is hydrated. If hydrated the measurement will be added
|
||||
* to the measurement service and will be available for the other viewports.
|
||||
*/
|
||||
const SRAnnotation: Types.Annotation = {
|
||||
annotationUID: measurement.TrackingUniqueIdentifier,
|
||||
highlighted: false,
|
||||
isLocked: false,
|
||||
invalidated: false,
|
||||
metadata: {
|
||||
toolName: toolName,
|
||||
FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID,
|
||||
referencedImageId: imageId,
|
||||
},
|
||||
data: {
|
||||
label: measurement.labels,
|
||||
handles: {
|
||||
textBox: measurement.textBox ?? {},
|
||||
},
|
||||
cachedStats: {},
|
||||
TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier,
|
||||
renderableData: measurementData.renderableData,
|
||||
frameNumber,
|
||||
},
|
||||
};
|
||||
const annotationManager = annotation.state.getAnnotationManager();
|
||||
annotationManager.addAnnotation(SRAnnotation);
|
||||
}
|
||||
|
||||
function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||
let renderableData: csTypes.Point3[];
|
||||
function getRenderableData({ GraphicType, GraphicData, ValueType, imageId }) {
|
||||
let renderableData = [];
|
||||
|
||||
switch (GraphicType) {
|
||||
case SCOORD_TYPES.POINT:
|
||||
case SCOORD_TYPES.MULTIPOINT:
|
||||
case SCOORD_TYPES.POLYLINE:
|
||||
renderableData = [];
|
||||
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
||||
GraphicData[i],
|
||||
GraphicData[i + 1],
|
||||
]);
|
||||
|
||||
renderableData.push(worldPos);
|
||||
}
|
||||
|
||||
case SCOORDTypes.POINT:
|
||||
case SCOORDTypes.MULTIPOINT:
|
||||
case SCOORDTypes.POLYLINE: {
|
||||
renderableData = getRenderableCoords({ GraphicData, ValueType, imageId });
|
||||
break;
|
||||
case SCOORD_TYPES.CIRCLE: {
|
||||
const pointsWorld = [];
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
||||
GraphicData[i],
|
||||
GraphicData[i + 1],
|
||||
]);
|
||||
|
||||
pointsWorld.push(worldPos);
|
||||
}
|
||||
|
||||
}
|
||||
case SCOORDTypes.CIRCLE: {
|
||||
const pointsWorld: csTypes.Point3[] = getRenderableCoords({
|
||||
GraphicData,
|
||||
ValueType,
|
||||
imageId,
|
||||
});
|
||||
// We do not have an explicit draw circle svg helper in Cornerstone3D at
|
||||
// this time, but we can use the ellipse svg helper to draw a circle, so
|
||||
// here we reshape the data for that purpose.
|
||||
const center = pointsWorld[0];
|
||||
const onPerimeter = pointsWorld[1];
|
||||
|
||||
const radius = vec3.distance(center, onPerimeter);
|
||||
|
||||
const imagePlaneModule = metaData.get('imagePlaneModule', imageId);
|
||||
|
||||
if (!imagePlaneModule) {
|
||||
throw new Error('No imagePlaneModule found');
|
||||
}
|
||||
@@ -115,14 +58,12 @@ function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||
|
||||
// we need to get major/minor axis (which are both the same size major = minor)
|
||||
|
||||
// first axisStart
|
||||
const firstAxisStart = vec3.create();
|
||||
vec3.scaleAndAdd(firstAxisStart, center, columnCosines, radius);
|
||||
|
||||
const firstAxisEnd = vec3.create();
|
||||
vec3.scaleAndAdd(firstAxisEnd, center, columnCosines, -radius);
|
||||
|
||||
// second axisStart
|
||||
const secondAxisStart = vec3.create();
|
||||
vec3.scaleAndAdd(secondAxisStart, center, rowCosines, radius);
|
||||
|
||||
@@ -138,21 +79,16 @@ function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||
|
||||
break;
|
||||
}
|
||||
case SCOORD_TYPES.ELLIPSE: {
|
||||
case SCOORDTypes.ELLIPSE: {
|
||||
// GraphicData is ordered as [majorAxisStartX, majorAxisStartY, majorAxisEndX, majorAxisEndY, minorAxisStartX, minorAxisStartY, minorAxisEndX, minorAxisEndY]
|
||||
// But Cornerstone3D points are ordered as top, bottom, left, right for the
|
||||
// ellipse so we need to identify if the majorAxis is horizontal or vertical
|
||||
// and then choose the correct points to use for the ellipse.
|
||||
|
||||
const pointsWorld: csTypes.Point3[] = [];
|
||||
for (let i = 0; i < GraphicData.length; i += 2) {
|
||||
const worldPos = utilities.imageToWorldCoords(imageId, [
|
||||
GraphicData[i],
|
||||
GraphicData[i + 1],
|
||||
]);
|
||||
|
||||
pointsWorld.push(worldPos);
|
||||
}
|
||||
const pointsWorld: csTypes.Point3[] = getRenderableCoords({
|
||||
GraphicData,
|
||||
ValueType,
|
||||
imageId,
|
||||
});
|
||||
|
||||
const majorAxisStart = vec3.fromValues(...pointsWorld[0]);
|
||||
const majorAxisEnd = vec3.fromValues(...pointsWorld[1]);
|
||||
@@ -202,3 +138,5 @@ function _getRenderableData(GraphicType, GraphicData, imageId) {
|
||||
|
||||
return renderableData;
|
||||
}
|
||||
|
||||
export default getRenderableData;
|
||||
@@ -3,14 +3,12 @@ import OHIF, { DicomMetadataStore } from '@ohif/core';
|
||||
import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData';
|
||||
import { adaptersSR } from '@cornerstonejs/adapters';
|
||||
import { annotation as CsAnnotation } from '@cornerstonejs/tools';
|
||||
const { locking } = CsAnnotation;
|
||||
import { Enums as CSExtensionEnums } from '@ohif/extension-cornerstone';
|
||||
|
||||
const { locking } = CsAnnotation;
|
||||
const { guid } = OHIF.utils;
|
||||
const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D;
|
||||
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
|
||||
const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
|
||||
|
||||
const { CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION } = CSExtensionEnums;
|
||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||
|
||||
const convertCode = (codingValues, code) => {
|
||||
@@ -47,11 +45,19 @@ export default function hydrateStructuredReport(
|
||||
displaySetInstanceUID
|
||||
) {
|
||||
const annotationManager = CsAnnotation.state.getAnnotationManager();
|
||||
const disableEditing = appConfig?.disableEditing;
|
||||
const dataSource = extensionManager.getActiveDataSource()[0];
|
||||
const { measurementService, displaySetService, customizationService } = servicesManager.services;
|
||||
|
||||
const codingValues = customizationService.getCustomization('codingValues', {});
|
||||
|
||||
const { disableEditing } = customizationService.getCustomization(
|
||||
'PanelMeasurement.disableEditing',
|
||||
{
|
||||
id: 'default.disableEditing',
|
||||
disableEditing: false,
|
||||
}
|
||||
);
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
// TODO -> We should define a strict versioning somewhere.
|
||||
|
||||
@@ -4,7 +4,7 @@ const cornerstoneAdapters =
|
||||
adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
|
||||
|
||||
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
|
||||
const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG;
|
||||
const CORNERSTONE_3D_TAG = adaptersSR.Cornerstone3D.CORNERSTONE_3D_TAG;
|
||||
|
||||
/**
|
||||
* Checks if the given `displaySet`can be rehydrated into the `measurementService`.
|
||||
|
||||
@@ -3,6 +3,463 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OHIF 3.9 Recovery 3 ([#4697](https://github.com/OHIF/Viewers/issues/4697)) ([5d4f2e2](https://github.com/OHIF/Viewers/commit/5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **multiframe:** metadata handling of NM studies and loading order ([#4540](https://github.com/OHIF/Viewers/issues/4540)) ([71f06dc](https://github.com/OHIF/Viewers/commit/71f06dcd3e6d24f1e3d931f7db756c26038927da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* video playback ([#4497](https://github.com/OHIF/Viewers/issues/4497)) ([610faa5](https://github.com/OHIF/Viewers/commit/610faa5a2738da5eabc40e57e338c359f481e852))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Measurement Tracking: Various UI and functionality improvements ([#4481](https://github.com/OHIF/Viewers/issues/4481)) ([62b2748](https://github.com/OHIF/Viewers/commit/62b27488471c9d5979142e2d15872a85778b90ed))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bugs:** Update dependencies and enhance UI components ([#4478](https://github.com/OHIF/Viewers/issues/4478)) ([05d41c5](https://github.com/OHIF/Viewers/commit/05d41c52068a3b7ba249f15ecdf71838c352fd30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tmtv:** fix toggle one up weird behaviours ([#4473](https://github.com/OHIF/Viewers/issues/4473)) ([aa2b649](https://github.com/OHIF/Viewers/commit/aa2b649444eb4fe5422e72ea7830a709c4d24a90))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build ([#4471](https://github.com/OHIF/Viewers/issues/4471)) ([3d11ef2](https://github.com/OHIF/Viewers/commit/3d11ef28f213361ec7586809317bd219fa70e742))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **new-study-panel:** default to list view for non thumbnail series, change default fitler to all, and add more menu to thumbnail items with a dicom tag browser ([#4417](https://github.com/OHIF/Viewers/issues/4417)) ([a7fd9fa](https://github.com/OHIF/Viewers/commit/a7fd9fa5bfff7a1b533d99cb96f7147a35fd528f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **SR:** SCOORD3D point annotations support for stack viewports ([#4315](https://github.com/OHIF/Viewers/issues/4315)) ([ac1cad2](https://github.com/OHIF/Viewers/commit/ac1cad25af12ee0f7d508647e3134ed724d9b4d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **studies-panel:** New OHIF study panel - under experimental flag ([#4254](https://github.com/OHIF/Viewers/issues/4254)) ([7a96406](https://github.com/OHIF/Viewers/commit/7a96406a116e46e62c396855fa64f434e2984b58))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add CS3D WSI and Video Viewports and add annotation navigation for MPR ([#4182](https://github.com/OHIF/Viewers/issues/4182)) ([7599ec9](https://github.com/OHIF/Viewers/commit/7599ec9421129dcade94e6fa6ec7908424ab3134))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone-dynamic-volume
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone-dynamic-volume",
|
||||
"version": "3.9.0-beta.62",
|
||||
"version": "3.9.3",
|
||||
"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.9.0-beta.62",
|
||||
"@ohif/extension-cornerstone": "3.9.0-beta.62",
|
||||
"@ohif/extension-default": "3.9.0-beta.62",
|
||||
"@ohif/i18n": "3.9.0-beta.62",
|
||||
"@ohif/ui": "3.9.0-beta.62",
|
||||
"@ohif/core": "3.9.3",
|
||||
"@ohif/extension-cornerstone": "3.9.3",
|
||||
"@ohif/extension-default": "3.9.3",
|
||||
"@ohif/i18n": "3.9.3",
|
||||
"@ohif/ui": "3.9.3",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -42,9 +42,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/core": "^1.82.0",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^1.82.0",
|
||||
"@cornerstonejs/tools": "^1.82.0",
|
||||
"@cornerstonejs/core": "^2.2.20",
|
||||
"@cornerstonejs/tools": "^2.2.20",
|
||||
"classnames": "^2.3.2"
|
||||
}
|
||||
}
|
||||
+21
-25
@@ -110,7 +110,12 @@ function _getTimePointsData(volume) {
|
||||
return { timePoints, timePointsUnit };
|
||||
}
|
||||
|
||||
function _getSegmentationData(segmentation, volumesTimePointsCache, displaySetService) {
|
||||
function _getSegmentationData(
|
||||
segmentation,
|
||||
volumesTimePointsCache,
|
||||
{ servicesManager }: { servicesManager: AppTypes.ServicesManager }
|
||||
) {
|
||||
const { displaySetService, segmentationService, viewportGridService } = servicesManager.services;
|
||||
const displaySets = displaySetService.getActiveDisplaySets();
|
||||
|
||||
const dynamic4DDisplaySet = displaySets.find(displaySet => {
|
||||
@@ -145,8 +150,11 @@ function _getSegmentationData(segmentation, volumesTimePointsCache, displaySetSe
|
||||
referencedDynamicVolume.imageIds[0]
|
||||
);
|
||||
|
||||
const segmentationVolume = segmentationService.getLabelmapVolume(segmentation.segmentationId);
|
||||
const maskVolumeId = segmentationVolume?.volumeId;
|
||||
|
||||
const [timeData, _] = csToolsUtils.dynamicVolume.getDataInTime(referencedDynamicVolume, {
|
||||
maskVolumeId: segmentation.id,
|
||||
maskVolumeId,
|
||||
}) as number[][];
|
||||
|
||||
const pixelCount = timeData.length;
|
||||
@@ -155,27 +163,15 @@ function _getSegmentationData(segmentation, volumesTimePointsCache, displaySetSe
|
||||
return [];
|
||||
}
|
||||
|
||||
// since we only use one segmentation representation per segmentationId
|
||||
// it is fine to pick the first one
|
||||
const segmentationRepresentations = csTools.segmentation.state.getSegmentationIdRepresentations(
|
||||
segmentation.id
|
||||
);
|
||||
|
||||
const segmentationRepresentationUID =
|
||||
segmentationRepresentations[0].segmentationRepresentationUID;
|
||||
|
||||
const toolGroupId = csTools.segmentation.state.getToolGroupIdFromSegmentationRepresentationUID(
|
||||
segmentationRepresentationUID
|
||||
);
|
||||
|
||||
// Todo: this is useless we should be able to grab color with just segRepUID and segmentIndex
|
||||
const color = csTools.segmentation.config.color.getColorForSegmentIndex(
|
||||
toolGroupId,
|
||||
segmentationRepresentationUID,
|
||||
1 // segmentIndex
|
||||
);
|
||||
// const color = csTools.segmentation.config.color.getSegmentIndexColor(
|
||||
// segmentationRepresentationUID,
|
||||
// 1 // segmentIndex
|
||||
// );
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const color = segmentationService.getSegmentColor(viewportId, segmentation.segmentationId, 1);
|
||||
|
||||
const hexColor = cs.utilities.color.rgbToHex(...color);
|
||||
const hexColor = cs.utilities.color.rgbToHex(color[0], color[1], color[2]);
|
||||
let timePointsData = volumesTimePointsCache.get(referencedDynamicVolume);
|
||||
|
||||
if (!timePointsData) {
|
||||
@@ -219,14 +215,14 @@ function _getSegmentationData(segmentation, volumesTimePointsCache, displaySetSe
|
||||
};
|
||||
}
|
||||
|
||||
function _getInstanceFromSegmentations(segmentations, displaySetService) {
|
||||
function _getInstanceFromSegmentations(segmentations, { servicesManager }) {
|
||||
if (!segmentations.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const volumesTimePointsCache = new WeakMap();
|
||||
const segmentationsData = segmentations.map(segmentation =>
|
||||
_getSegmentationData(segmentation, volumesTimePointsCache, displaySetService)
|
||||
_getSegmentationData(segmentation, volumesTimePointsCache, { servicesManager })
|
||||
);
|
||||
|
||||
const { date: seriesDate, time: seriesTime } = _getDateTimeStr();
|
||||
@@ -266,10 +262,10 @@ function _getInstanceFromSegmentations(segmentations, displaySetService) {
|
||||
}
|
||||
|
||||
function updateSegmentationsChartDisplaySet({ servicesManager }: withAppTypes): void {
|
||||
const { segmentationService, displaySetService } = servicesManager.services;
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const segmentations = segmentationService.getSegmentations();
|
||||
const { seriesMetadata, instance } =
|
||||
_getInstanceFromSegmentations(segmentations, displaySetService) ?? {};
|
||||
_getInstanceFromSegmentations(segmentations, { servicesManager }) ?? {};
|
||||
|
||||
if (seriesMetadata && instance) {
|
||||
// An event is triggered after adding the instance and the displaySet is created
|
||||
|
||||
@@ -84,11 +84,11 @@ const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
|
||||
const stdDeviation = Math.sqrt(variance);
|
||||
return stdDeviation;
|
||||
}
|
||||
|
||||
// Iterate through each segmentation to get the timeData and ijkCoords
|
||||
segmentations.forEach((segmentation, segmentationIndex) => {
|
||||
segmentations.forEach(segmentation => {
|
||||
const volume = segmentationService.getLabelmapVolume(segmentation.segmentationId);
|
||||
const [timeData, ijkCoords] = utilities.dynamicVolume.getDataInTime(dynamicVolume, {
|
||||
maskVolumeId: segmentation.id,
|
||||
maskVolumeId: volume.volumeId,
|
||||
}) as number[][];
|
||||
|
||||
if (summaryStats) {
|
||||
@@ -113,17 +113,33 @@ const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
|
||||
for (let timeIndex = 0; timeIndex < maxLength; timeIndex++) {
|
||||
// for each voxel in the ROI, get the value at the current time point
|
||||
const voxelValues = [];
|
||||
let sum = 0;
|
||||
let minValue = Infinity;
|
||||
let maxValue = -Infinity;
|
||||
let minIndex = 0;
|
||||
let maxIndex = 0;
|
||||
|
||||
// Single pass through the data to collect all needed values
|
||||
for (let voxelIndex = 0; voxelIndex < numVoxels; voxelIndex++) {
|
||||
voxelValues.push(timeData[voxelIndex][timeIndex]);
|
||||
const value = timeData[voxelIndex][timeIndex];
|
||||
voxelValues.push(value);
|
||||
sum += value;
|
||||
|
||||
if (value < minValue) {
|
||||
minValue = value;
|
||||
minIndex = voxelIndex;
|
||||
}
|
||||
if (value > maxValue) {
|
||||
maxValue = value;
|
||||
maxIndex = voxelIndex;
|
||||
}
|
||||
}
|
||||
|
||||
mean.push(voxelValues.reduce((acc, value) => acc + value, 0) / numVoxels);
|
||||
const minimum = Math.min(...voxelValues);
|
||||
min.push(minimum);
|
||||
minIJK.push(ijkCoords[voxelValues.indexOf(minimum)]);
|
||||
const maximum = Math.max(...voxelValues);
|
||||
max.push(maximum);
|
||||
maxIJK.push(ijkCoords[voxelValues.indexOf(maximum)]);
|
||||
mean.push(sum / numVoxels);
|
||||
min.push(minValue);
|
||||
minIJK.push(ijkCoords[minIndex]);
|
||||
max.push(maxValue);
|
||||
maxIJK.push(ijkCoords[maxIndex]);
|
||||
std.push(calculateStandardDeviation(voxelValues));
|
||||
}
|
||||
|
||||
@@ -340,30 +356,19 @@ const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
|
||||
throw new Error('No reference display set found based on the dynamic data');
|
||||
}
|
||||
|
||||
const segmentationId = await segmentationService.createSegmentationForDisplaySet(
|
||||
referenceDisplaySet.displaySetInstanceUID,
|
||||
{ label }
|
||||
const displaySet = displaySetService.getDisplaySetByUID(
|
||||
referenceDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
// Add Segmentation to all toolGroupIds in the viewer
|
||||
const toolGroupIds = Array.from(
|
||||
viewports.values(),
|
||||
viewport => viewport.viewportOptions.toolGroupId
|
||||
);
|
||||
const segmentationId = await segmentationService.createLabelmapForDisplaySet(displaySet, {
|
||||
label,
|
||||
});
|
||||
|
||||
const representationType = LABELMAP;
|
||||
const firstViewport = viewports.values().next().value;
|
||||
|
||||
for (const toolGroupId of toolGroupIds) {
|
||||
const hydrateSegmentation = true;
|
||||
await segmentationService.addSegmentationRepresentationToToolGroup(
|
||||
toolGroupId,
|
||||
segmentationId,
|
||||
hydrateSegmentation,
|
||||
representationType
|
||||
);
|
||||
|
||||
segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
|
||||
}
|
||||
await segmentationService.addSegmentationRepresentation(firstViewport.viewportId, {
|
||||
segmentationId,
|
||||
});
|
||||
|
||||
return segmentationId;
|
||||
},
|
||||
|
||||
@@ -147,8 +147,8 @@ function getFusionViewports() {
|
||||
name: DEFAULT_COLORMAP,
|
||||
opacity: [
|
||||
{ value: 0, opacity: 0 },
|
||||
{ value: 0.1, opacity: 0.3 },
|
||||
{ value: 1, opacity: 0.3 },
|
||||
{ value: 0.1, opacity: 0.8 },
|
||||
{ value: 1, opacity: 0.8 },
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -191,6 +191,15 @@ function getFusionViewports() {
|
||||
syncInvertState: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
options: {
|
||||
matchingRules: ['sameFOR'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
@@ -240,6 +249,15 @@ function getFusionViewports() {
|
||||
syncInvertState: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
options: {
|
||||
matchingRules: ['sameFOR'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
@@ -289,6 +307,15 @@ function getFusionViewports() {
|
||||
syncInvertState: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'hydrateseg',
|
||||
id: 'sameFORId',
|
||||
source: true,
|
||||
target: true,
|
||||
options: {
|
||||
matchingRules: ['sameFOR'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
displaySets: [
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { DynamicDataPanel } from './panels';
|
||||
import { Toolbox } from '@ohif/ui';
|
||||
import { Toolbox } from '@ohif/ui-next';
|
||||
import { PanelSegmentation } from '@ohif/extension-cornerstone';
|
||||
import DynamicExport from './panels/DynamicExport';
|
||||
|
||||
function getPanelModule({ commandsManager, extensionManager, servicesManager }) {
|
||||
function getPanelModule({ commandsManager, extensionManager, servicesManager, configuration }) {
|
||||
const wrappedDynamicDataPanel = () => {
|
||||
return (
|
||||
<DynamicDataPanel
|
||||
@@ -14,7 +15,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
);
|
||||
};
|
||||
|
||||
const wrappedDynamicToolbox = () => {
|
||||
const wrappedDynamicSegmentation = () => {
|
||||
return (
|
||||
<>
|
||||
<Toolbox
|
||||
@@ -24,18 +25,17 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
buttonSectionId="dynamic-toolbox"
|
||||
title="Threshold Tools"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const wrappedDynamicExport = () => {
|
||||
return (
|
||||
<>
|
||||
<DynamicExport
|
||||
commandsManager={commandsManager}
|
||||
<PanelSegmentation
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
extensionManager={extensionManager}
|
||||
/>
|
||||
configuration={configuration}
|
||||
>
|
||||
<DynamicExport
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
</PanelSegmentation>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -49,18 +49,11 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager })
|
||||
component: wrappedDynamicDataPanel,
|
||||
},
|
||||
{
|
||||
name: 'dynamic-toolbox',
|
||||
iconName: 'tab-4d',
|
||||
iconLabel: '4D Workflow',
|
||||
label: 'Dynamic Toolbox',
|
||||
component: wrappedDynamicToolbox,
|
||||
},
|
||||
{
|
||||
name: 'dynamic-export',
|
||||
iconName: 'tab-4d',
|
||||
iconLabel: '4D Workflow',
|
||||
label: '4D Workflow',
|
||||
component: wrappedDynamicExport,
|
||||
name: 'dynamic-segmentation',
|
||||
iconName: 'tab-segmentation',
|
||||
iconLabel: 'Segmentation',
|
||||
label: 'Segmentation',
|
||||
component: wrappedDynamicSegmentation,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import React from 'react';
|
||||
import PanelGenerateImage from './PanelGenerateImage';
|
||||
|
||||
function DynamicDataPanel({ servicesManager, commandsManager }) {
|
||||
function DynamicDataPanel({ servicesManager, commandsManager, tab }: withAppTypes) {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col text-white"
|
||||
data-cy={'dynamic-volume-panel'}
|
||||
>
|
||||
<PanelGenerateImage
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
></PanelGenerateImage>
|
||||
</div>
|
||||
<>
|
||||
<div
|
||||
className="flex flex-col text-white"
|
||||
data-cy={'dynamic-volume-panel'}
|
||||
>
|
||||
<PanelGenerateImage
|
||||
commandsManager={commandsManager}
|
||||
servicesManager={servicesManager}
|
||||
></PanelGenerateImage>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,73 +1,52 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ActionButtons } from '@ohif/ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React from 'react';
|
||||
import { Button, Icons } from '@ohif/ui-next';
|
||||
import { useSegmentations } from '@ohif/extension-cornerstone';
|
||||
|
||||
function DynamicExport({ commandsManager, servicesManager, extensionManager }: withAppTypes) {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
const { t } = useTranslation('dynamicExport');
|
||||
function DynamicExport({ commandsManager, servicesManager }: withAppTypes) {
|
||||
const segmentations = useSegmentations({ servicesManager });
|
||||
|
||||
const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations());
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: 'Export Time Data',
|
||||
onClick: () => {
|
||||
commandsManager.runCommand('exportTimeReportCSV', {
|
||||
segmentations,
|
||||
options: {
|
||||
filename: 'TimeData.csv',
|
||||
},
|
||||
});
|
||||
},
|
||||
disabled: !segmentations?.length,
|
||||
},
|
||||
{
|
||||
label: 'Export ROI Stats',
|
||||
onClick: () => {
|
||||
commandsManager.runCommand('exportTimeReportCSV', {
|
||||
segmentations,
|
||||
summaryStats: true,
|
||||
options: {
|
||||
filename: 'ROIStats.csv',
|
||||
},
|
||||
});
|
||||
},
|
||||
disabled: !segmentations?.length,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Update UI based on segmentation changes (added, removed, updated)
|
||||
*/
|
||||
useEffect(() => {
|
||||
// ~~ Subscription
|
||||
const added = segmentationService.EVENTS.SEGMENTATION_ADDED;
|
||||
const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED;
|
||||
const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED;
|
||||
const subscriptions = [];
|
||||
|
||||
[added, updated, removed].forEach(evt => {
|
||||
const { unsubscribe } = segmentationService.subscribe(evt, () => {
|
||||
const segmentations = segmentationService.getSegmentations();
|
||||
setSegmentations(segmentations);
|
||||
});
|
||||
subscriptions.push(unsubscribe);
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach(unsub => {
|
||||
unsub();
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
if (!segmentations?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-3 flex justify-center px-2">
|
||||
<ActionButtons
|
||||
actions={actions}
|
||||
t={t}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex h-8 w-full items-center rounded pr-0.5">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="pl-1.5"
|
||||
onClick={() => {
|
||||
commandsManager.runCommand('exportTimeReportCSV', {
|
||||
segmentations,
|
||||
options: {
|
||||
filename: 'TimeData.csv',
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icons.Export />
|
||||
<span className="pl-1">Time Data</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex h-8 w-full items-center rounded pr-0.5">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="pl-1.5"
|
||||
onClick={() => {
|
||||
commandsManager.runCommand('exportTimeReportCSV', {
|
||||
segmentations,
|
||||
summaryStats: true,
|
||||
options: {
|
||||
filename: 'ROIStats.csv',
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icons.Export />
|
||||
<span className="pl-1">ROI Stats</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
InputDoubleRange,
|
||||
Button,
|
||||
PanelSection,
|
||||
ButtonGroup,
|
||||
@@ -10,6 +9,8 @@ import {
|
||||
Tooltip,
|
||||
} from '@ohif/ui';
|
||||
|
||||
import { DoubleSlider } from '@ohif/ui-next';
|
||||
|
||||
import { Enums } from '@cornerstonejs/core';
|
||||
|
||||
const controlClassNames = {
|
||||
@@ -55,21 +56,15 @@ const DynamicVolumeControls = ({
|
||||
|
||||
const [computeViewMode, setComputeViewMode] = useState(Enums.DynamicOperatorType.SUM);
|
||||
|
||||
const [sliderRangeValues, setSliderRangeValues] = useState([framesLength / 4, framesLength / 2]);
|
||||
|
||||
useEffect(() => {
|
||||
setSliderRangeValues([framesLength / 4, framesLength / 2]);
|
||||
}, [framesLength]);
|
||||
const [sliderRangeValues, setSliderRangeValues] = useState([0, framesLength - 1]);
|
||||
|
||||
const handleSliderChange = newValues => {
|
||||
onDoubleRangeChange(newValues);
|
||||
|
||||
if (newValues[0] === sliderRangeValues[0] && newValues[1] === sliderRangeValues[1]) {
|
||||
return;
|
||||
}
|
||||
setSliderRangeValues(newValues);
|
||||
};
|
||||
|
||||
const formatLabel = value => Math.round(value);
|
||||
|
||||
return (
|
||||
<div className="flex select-none flex-col">
|
||||
<PanelSection
|
||||
@@ -134,7 +129,7 @@ const DynamicVolumeControls = ({
|
||||
}
|
||||
/>
|
||||
<ButtonGroup
|
||||
className={`mt-2 w-full `}
|
||||
className={`mt-2 w-full`}
|
||||
separated={true}
|
||||
>
|
||||
<button
|
||||
@@ -156,15 +151,15 @@ const DynamicVolumeControls = ({
|
||||
{Enums.DynamicOperatorType.SUBTRACT.toString().toUpperCase()}
|
||||
</button>
|
||||
</ButtonGroup>
|
||||
<div className="w-ful mt-2">
|
||||
<InputDoubleRange
|
||||
values={sliderRangeValues}
|
||||
onChange={handleSliderChange}
|
||||
minValue={0}
|
||||
showLabel={true}
|
||||
allowNumberEdit={true}
|
||||
maxValue={framesLength}
|
||||
<div className="mt-2 w-full">
|
||||
<DoubleSlider
|
||||
min={0}
|
||||
max={framesLength - 1}
|
||||
step={1}
|
||||
defaultValue={sliderRangeValues}
|
||||
onValueChange={handleSliderChange}
|
||||
formatLabel={formatLabel}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
@@ -216,14 +211,14 @@ function FrameControls({
|
||||
>
|
||||
<Icon
|
||||
name={getPlayPauseIconName()}
|
||||
className=" active:text-primary-light hover:bg-customblue-300 h-[24px] w-[24px] cursor-pointer text-white"
|
||||
className="active:text-primary-light hover:bg-customblue-300 h-[24px] w-[24px] cursor-pointer text-white"
|
||||
/>
|
||||
</IconButton>
|
||||
<InputNumber
|
||||
value={currentFrameIndex}
|
||||
onChange={onFrameChange}
|
||||
minValue={0}
|
||||
maxValue={framesLength}
|
||||
maxValue={framesLength - 1}
|
||||
label="Frame"
|
||||
{...controlClassNames}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useCine, useViewportGrid } from '@ohif/ui';
|
||||
import { cache, utilities as csUtils, volumeLoader, eventTarget } from '@cornerstonejs/core';
|
||||
import { Enums } from '@cornerstonejs/streaming-image-volume-loader';
|
||||
import { utilities as csUtils, volumeLoader, eventTarget, Enums, cache } from '@cornerstonejs/core';
|
||||
import { utilities as cstUtils } from '@cornerstonejs/tools';
|
||||
import DynamicVolumeControls from './DynamicVolumeControls';
|
||||
|
||||
@@ -16,8 +14,8 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
|
||||
const [{ activeViewportId }] = useViewportGrid();
|
||||
|
||||
//
|
||||
const [timePointsRange, setTimePointsRange] = useState([]);
|
||||
const [timePointsRangeToUseForGenerate, setTimePointsRangeToUseForGenerate] = useState([]);
|
||||
const [timePointsRange, setTimePointsRange] = useState([0, 0]);
|
||||
const [timePointsRangeToUseForGenerate, setTimePointsRangeToUseForGenerate] = useState([0, 0]);
|
||||
const [computedDisplaySet, setComputedDisplaySet] = useState(null);
|
||||
const [dynamicVolume, setDynamicVolume] = useState(null);
|
||||
const [frameRate, setFrameRate] = useState(20);
|
||||
@@ -31,47 +29,62 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
|
||||
const computedVolumeId = `cornerstoneStreamingImageVolume:${uuidComputedVolume.current}`;
|
||||
|
||||
useEffect(() => {
|
||||
const evt = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED;
|
||||
const viewportDataChangedEvt = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED;
|
||||
const cineStateChangedEvt = servicesManager.services.cineService.EVENTS.CINE_STATE_CHANGED;
|
||||
|
||||
const { unsubscribe } = cornerstoneViewportService.subscribe(evt, evtDetails => {
|
||||
const viewportDataChangedCallback = evtDetails => {
|
||||
evtDetails.viewportData.data.forEach(volumeData => {
|
||||
if (volumeData.volume.isDynamicVolume()) {
|
||||
if (volumeData.volume?.isDynamicVolume()) {
|
||||
setDynamicVolume(volumeData.volume);
|
||||
uuidDynamicVolume.current = volumeData.displaySetInstanceUID;
|
||||
setTimePointsRange([1, volumeData.volume.numTimePoints]);
|
||||
const newRange = [1, volumeData.volume.numTimePoints];
|
||||
setTimePointsRange(newRange);
|
||||
setTimePointsRangeToUseForGenerate(newRange);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const cineStateChangedCallback = evt => {
|
||||
setIsPlaying(evt.isPlaying);
|
||||
};
|
||||
|
||||
const { unsubscribe: unsubscribeViewportData } = cornerstoneViewportService.subscribe(
|
||||
viewportDataChangedEvt,
|
||||
viewportDataChangedCallback
|
||||
);
|
||||
const { unsubscribe: unsubscribeCineState } = servicesManager.services.cineService.subscribe(
|
||||
cineStateChangedEvt,
|
||||
cineStateChangedCallback
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
unsubscribeViewportData();
|
||||
unsubscribeCineState();
|
||||
};
|
||||
}, [cornerstoneViewportService, cineService, servicesManager.services.cineService]);
|
||||
|
||||
useEffect(() => {
|
||||
const evt = Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED;
|
||||
|
||||
const callback = evt => {
|
||||
setTimePointRendered(evt.detail.timePointIndex);
|
||||
};
|
||||
|
||||
eventTarget.addEventListener(evt, callback);
|
||||
|
||||
return () => {
|
||||
eventTarget.removeEventListener(evt, callback);
|
||||
};
|
||||
}, [cornerstoneViewportService]);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = servicesManager.services.cineService.subscribe(
|
||||
servicesManager.services.cineService.EVENTS.CINE_STATE_CHANGED,
|
||||
evt => {
|
||||
setIsPlaying(evt.isPlaying);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [cineService]);
|
||||
|
||||
useEffect(() => {
|
||||
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(activeViewportId);
|
||||
|
||||
if (!displaySetUIDs || displaySetUIDs.length === 0) {
|
||||
if (!displaySetUIDs?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const displaySets = displaySetUIDs.map(displaySetUID =>
|
||||
displaySetService.getDisplaySetByUID(displaySetUID)
|
||||
);
|
||||
|
||||
const displaySets = displaySetUIDs.map(displaySetService.getDisplaySetByUID);
|
||||
const dynamicVolumeDisplaySet = displaySets.find(displaySet => displaySet.isDynamicVolume);
|
||||
|
||||
if (!dynamicVolumeDisplaySet) {
|
||||
@@ -88,23 +101,16 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
|
||||
|
||||
setDynamicVolume(dynamicVolume);
|
||||
uuidDynamicVolume.current = dynamicVolumeDisplaySet.displaySetInstanceUID;
|
||||
setTimePointsRange([1, dynamicVolume.numTimePoints]);
|
||||
}, [activeViewportId, cornerstoneViewportService]);
|
||||
|
||||
useEffect(() => {
|
||||
// ~~ Subscription
|
||||
const evt = Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED;
|
||||
|
||||
const callback = evt => {
|
||||
setTimePointRendered(evt.detail.timePointIndex);
|
||||
};
|
||||
|
||||
eventTarget.addEventListener(evt, callback);
|
||||
|
||||
return () => {
|
||||
eventTarget.removeEventListener(evt, callback);
|
||||
};
|
||||
}, [cornerstoneViewportService]);
|
||||
const newRange = [1, dynamicVolume.numTimePoints];
|
||||
setTimePointsRange(newRange);
|
||||
setTimePointsRangeToUseForGenerate(newRange);
|
||||
}, [
|
||||
activeViewportId,
|
||||
viewportGridService,
|
||||
displaySetService,
|
||||
cornerstoneViewportService,
|
||||
cineService,
|
||||
]);
|
||||
|
||||
function renderGeneratedImage(displaySet) {
|
||||
commandsManager.runCommand('swapDynamicWithComputedDisplaySet', {
|
||||
@@ -130,26 +136,19 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
|
||||
let computedVolume = cache.getVolume(computedVolumeId);
|
||||
|
||||
if (!computedVolume) {
|
||||
await createComputedVolume(dynamicVolumeId, computedVolumeId);
|
||||
computedVolume = cache.getVolume(computedVolumeId);
|
||||
computedVolume = await volumeLoader.createAndCacheDerivedVolume(dynamicVolumeId, {
|
||||
volumeId: computedVolumeId,
|
||||
});
|
||||
}
|
||||
const [start, end] = timePointsRangeToUseForGenerate;
|
||||
const frameNumbers = Array.from({ length: end - start + 1 }, (_, i) => i + start - 1);
|
||||
|
||||
const vals = timePointsRangeToUseForGenerate;
|
||||
const options = {
|
||||
frameNumbers: operationName === 'SUBTRACT' ? [start, end - 1] : frameNumbers,
|
||||
targetVolume: computedVolume,
|
||||
};
|
||||
|
||||
const targets = Array.from({ length: vals[1] - vals[0] + 1 }, (_, i) => i + vals[0]);
|
||||
|
||||
const dataInTime = cstUtils.dynamicVolume.generateImageFromTimeData(
|
||||
dynamicVolume,
|
||||
operationName,
|
||||
operationName === 'SUBTRACT' ? vals : targets
|
||||
);
|
||||
|
||||
// Add loadStatus.loaded to computed volume and set to true
|
||||
computedVolume.loadStatus = {};
|
||||
computedVolume.loadStatus.loaded = true;
|
||||
// Set computed scalar data to volume
|
||||
const scalarData = computedVolume.getScalarData();
|
||||
scalarData.set(dataInTime);
|
||||
cstUtils.dynamicVolume.updateVolumeFromTimeData(dynamicVolume, operationName, options);
|
||||
|
||||
// If computed display set does not exist, create an object to be used as
|
||||
// the displaySet. If it does exist, update the image data and vtkTexture
|
||||
@@ -166,6 +165,7 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
|
||||
madeInClient: true,
|
||||
FrameOfReferenceUID: dynamicVolume.metadata.FrameOfReferenceUID,
|
||||
isDerived: true,
|
||||
imageIds: computedVolume.imageIds,
|
||||
};
|
||||
setComputedDisplaySet(displaySet);
|
||||
renderGeneratedImage(displaySet);
|
||||
@@ -173,19 +173,8 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
|
||||
commandsManager.runCommand('updateVolumeData', {
|
||||
volume: computedVolume,
|
||||
});
|
||||
// Check if viewport is currently displaying the computed volume, if so,
|
||||
// call render on the viewports to update the image, if not, call
|
||||
// renderGeneratedImage
|
||||
// if (!cache.getVolume(dynamicVolumeId)) {
|
||||
// for (const viewportId of viewports.keys()) {
|
||||
// const viewportForRendering =
|
||||
// cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
// viewportForRendering.render();
|
||||
// }
|
||||
// } else {
|
||||
cornerstoneViewportService.getRenderingEngine().render();
|
||||
renderGeneratedImage(computedDisplaySet);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,28 +237,7 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
|
||||
onGenerate={onGenerateImage}
|
||||
onDynamicClick={displayingComputed ? () => renderDynamicImage(computedDisplaySet) : null}
|
||||
onDoubleRangeChange={handleSliderChange}
|
||||
initialRangeValues={timePointsRangeToUseForGenerate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
async function createComputedVolume(dynamicVolumeId, computedVolumeId) {
|
||||
if (!cache.getVolume(computedVolumeId)) {
|
||||
const computedVolume = await volumeLoader.createAndCacheDerivedVolume(dynamicVolumeId, {
|
||||
volumeId: computedVolumeId,
|
||||
});
|
||||
return computedVolume;
|
||||
}
|
||||
}
|
||||
|
||||
PanelGenerateImage.propTypes = {
|
||||
servicesManager: PropTypes.shape({
|
||||
services: PropTypes.shape({
|
||||
measurementService: PropTypes.shape({
|
||||
getMeasurements: PropTypes.func.isRequired,
|
||||
subscribe: PropTypes.func.isRequired,
|
||||
EVENTS: PropTypes.object.isRequired,
|
||||
VALUE_TYPES: PropTypes.object.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
@@ -3,6 +3,479 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.9.3](https://github.com/OHIF/Viewers/compare/v3.9.2...v3.9.3) (2025-01-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* OHIF 3.9 Recovery 3 ([#4697](https://github.com/OHIF/Viewers/issues/4697)) ([5d4f2e2](https://github.com/OHIF/Viewers/commit/5d4f2e2a8e62f8de97d56a4e3f17be6018e9f2eb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.2](https://github.com/OHIF/Viewers/compare/v3.9.1...v3.9.2) (2024-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modes:** don't attempt to retrieve a stage index if HPs ([#4552](https://github.com/OHIF/Viewers/issues/4552)) ([e5286d9](https://github.com/OHIF/Viewers/commit/e5286d9fc2449da50b738e7e6208d489cce1633e))
|
||||
* **multiframe:** metadata handling of NM studies and loading order ([#4540](https://github.com/OHIF/Viewers/issues/4540)) ([71f06dc](https://github.com/OHIF/Viewers/commit/71f06dcd3e6d24f1e3d931f7db756c26038927da))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.9.1](https://github.com/OHIF/Viewers/compare/v3.9.0...v3.9.1) (2024-11-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* video playback ([#4497](https://github.com/OHIF/Viewers/issues/4497)) ([610faa5](https://github.com/OHIF/Viewers/commit/610faa5a2738da5eabc40e57e338c359f481e852))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.111...v3.9.0) (2024-11-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.110...v3.9.0-beta.111) (2024-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Measurement Tracking: Various UI and functionality improvements ([#4481](https://github.com/OHIF/Viewers/issues/4481)) ([62b2748](https://github.com/OHIF/Viewers/commit/62b27488471c9d5979142e2d15872a85778b90ed))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.109...v3.9.0-beta.110) (2024-11-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bugs:** Update dependencies and enhance UI components ([#4478](https://github.com/OHIF/Viewers/issues/4478)) ([05d41c5](https://github.com/OHIF/Viewers/commit/05d41c52068a3b7ba249f15ecdf71838c352fd30))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.108...v3.9.0-beta.109) (2024-11-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.107...v3.9.0-beta.108) (2024-11-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tmtv:** fix toggle one up weird behaviours ([#4473](https://github.com/OHIF/Viewers/issues/4473)) ([aa2b649](https://github.com/OHIF/Viewers/commit/aa2b649444eb4fe5422e72ea7830a709c4d24a90))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.106...v3.9.0-beta.107) (2024-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build ([#4471](https://github.com/OHIF/Viewers/issues/4471)) ([3d11ef2](https://github.com/OHIF/Viewers/commit/3d11ef28f213361ec7586809317bd219fa70e742))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.105...v3.9.0-beta.106) (2024-11-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.104...v3.9.0-beta.105) (2024-11-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.103...v3.9.0-beta.104) (2024-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** show ui notification on displaySet load error ([#4447](https://github.com/OHIF/Viewers/issues/4447)) ([4f20523](https://github.com/OHIF/Viewers/commit/4f20523109ecbb7ec5a6d5f2c97f7e73f81cda09))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.102...v3.9.0-beta.103) (2024-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** display error in ui while loading seg ([#4433](https://github.com/OHIF/Viewers/issues/4433)) ([2e96371](https://github.com/OHIF/Viewers/commit/2e96371b0631a9e5d411b0142300708ab8ba7d27))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.101...v3.9.0-beta.102) (2024-10-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.100...v3.9.0-beta.101) (2024-10-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.99...v3.9.0-beta.100) (2024-10-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.98...v3.9.0-beta.99) (2024-10-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **3d-viewport:** exception was being thrown and 3d viewpot not getting resized. ([#4366](https://github.com/OHIF/Viewers/issues/4366)) ([433cc80](https://github.com/OHIF/Viewers/commit/433cc8089db6aa218c9075bd0eeb7952a7e4f028))
|
||||
* **hp-presets:** select the active displaySet when toggling an HP preset, not a random one from the series panel ([#4365](https://github.com/OHIF/Viewers/issues/4365)) ([ace67b3](https://github.com/OHIF/Viewers/commit/ace67b3bbb6be4e8c78e613e20d3e10b93762bf7))
|
||||
* **tools:** check if seriesNumber is an undefined properly ([#4338](https://github.com/OHIF/Viewers/issues/4338)) ([307b144](https://github.com/OHIF/Viewers/commit/307b14476be41b10b861d6a8474f7386b5107618))
|
||||
* **typo:** type in fourup preset ([#4426](https://github.com/OHIF/Viewers/issues/4426)) ([03aad4e](https://github.com/OHIF/Viewers/commit/03aad4eba24e33a266a6d91eaf74df52dc2a550e))
|
||||
* **updateIndex:** getNumberOfSlices is defined when used with a 3D viewport ([#4424](https://github.com/OHIF/Viewers/issues/4424)) ([d5bcf54](https://github.com/OHIF/Viewers/commit/d5bcf54e23ef68abd85c5f0ea671feca637c4f49))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **SR:** SCOORD3D point annotations support for stack viewports ([#4315](https://github.com/OHIF/Viewers/issues/4315)) ([ac1cad2](https://github.com/OHIF/Viewers/commit/ac1cad25af12ee0f7d508647e3134ed724d9b4d3))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.97...v3.9.0-beta.98) (2024-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.96...v3.9.0-beta.97) (2024-10-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.95...v3.9.0-beta.96) (2024-10-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.94...v3.9.0-beta.95) (2024-10-08)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.93...v3.9.0-beta.94) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.92...v3.9.0-beta.93) (2024-10-04)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.91...v3.9.0-beta.92) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.90...v3.9.0-beta.91) (2024-10-01)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.89...v3.9.0-beta.90) (2024-09-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.88...v3.9.0-beta.89) (2024-09-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.87...v3.9.0-beta.88) (2024-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.86...v3.9.0-beta.87) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.85...v3.9.0-beta.86) (2024-09-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.84...v3.9.0-beta.85) (2024-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.83...v3.9.0-beta.84) (2024-09-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.82...v3.9.0-beta.83) (2024-09-11)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.81...v3.9.0-beta.82) (2024-09-05)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.80...v3.9.0-beta.81) (2024-08-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.79...v3.9.0-beta.80) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.78...v3.9.0-beta.79) (2024-08-16)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.77...v3.9.0-beta.78) (2024-08-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add CS3D WSI and Video Viewports and add annotation navigation for MPR ([#4182](https://github.com/OHIF/Viewers/issues/4182)) ([7599ec9](https://github.com/OHIF/Viewers/commit/7599ec9421129dcade94e6fa6ec7908424ab3134))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.76...v3.9.0-beta.77) (2024-08-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.75...v3.9.0-beta.76) (2024-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* unexpected mpr measurements ([#4332](https://github.com/OHIF/Viewers/issues/4332)) ([ab6e341](https://github.com/OHIF/Viewers/commit/ab6e341731652a4fa894fcb576eb23dc95aefa11))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.74...v3.9.0-beta.75) (2024-08-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.73...v3.9.0-beta.74) (2024-08-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.72...v3.9.0-beta.73) (2024-08-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.71...v3.9.0-beta.72) (2024-07-31)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.70...v3.9.0-beta.71) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.69...v3.9.0-beta.70) (2024-07-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.68...v3.9.0-beta.69) (2024-07-27)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.67...v3.9.0-beta.68) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.66...v3.9.0-beta.67) (2024-07-26)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.65...v3.9.0-beta.66) (2024-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **pmap:** added support for parametric map ([#4284](https://github.com/OHIF/Viewers/issues/4284)) ([fc0064f](https://github.com/OHIF/Viewers/commit/fc0064fd9d8cdc8fde81b81f0e71fd5d077ca22b))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.64...v3.9.0-beta.65) (2024-07-23)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.63...v3.9.0-beta.64) (2024-07-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.62...v3.9.0-beta.63) (2024-07-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.9.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.9.0-beta.61...v3.9.0-beta.62) (2024-07-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "3.9.0-beta.62",
|
||||
"version": "3.9.3",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -36,12 +36,12 @@
|
||||
"peerDependencies": {
|
||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.2",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.2",
|
||||
"@cornerstonejs/dicom-image-loader": "^1.82.0",
|
||||
"@cornerstonejs/codec-openjpeg": "^1.2.4",
|
||||
"@cornerstonejs/codec-openjph": "^2.4.5",
|
||||
"@cornerstonejs/dicom-image-loader": "^2.2.20",
|
||||
"@icr/polyseg-wasm": "^0.4.0",
|
||||
"@ohif/core": "3.9.0-beta.62",
|
||||
"@ohif/ui": "3.9.0-beta.62",
|
||||
"@ohif/core": "3.9.3",
|
||||
"@ohif/ui": "3.9.3",
|
||||
"dcmjs": "*",
|
||||
"dicom-parser": "^1.8.21",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -55,12 +55,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@cornerstonejs/adapters": "^1.82.0",
|
||||
"@cornerstonejs/core": "^1.82.0",
|
||||
"@cornerstonejs/streaming-image-volume-loader": "^1.82.0",
|
||||
"@cornerstonejs/tools": "^1.82.0",
|
||||
"@cornerstonejs/adapters": "^2.2.20",
|
||||
"@cornerstonejs/core": "^2.2.20",
|
||||
"@cornerstonejs/tools": "^2.2.20",
|
||||
"@icr/polyseg-wasm": "^0.4.0",
|
||||
"@kitware/vtk.js": "30.4.1",
|
||||
"@kitware/vtk.js": "32.1.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.merge": "^4.6.2",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,15 +2,17 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
import PropTypes from 'prop-types';
|
||||
import { metaData, Enums, utilities } from '@cornerstonejs/core';
|
||||
import { ImageSliceData } from '@cornerstonejs/core/dist/esm/types';
|
||||
import type { ImageSliceData } from '@cornerstonejs/core/types';
|
||||
import { ViewportOverlay } from '@ohif/ui';
|
||||
import { InstanceMetadata } from '@ohif/core/src/types';
|
||||
import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||
import type { InstanceMetadata } from '@ohif/core/src/types';
|
||||
import { formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
|
||||
import { utils } from '@ohif/core';
|
||||
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
|
||||
|
||||
import './CustomizableViewportOverlay.css';
|
||||
|
||||
const EPSILON = 1e-4;
|
||||
const { formatPN } = utils;
|
||||
|
||||
type ViewportData = StackViewportData | VolumeViewportData;
|
||||
|
||||
@@ -45,14 +47,14 @@ const OverlayItemComponents = {
|
||||
'ohif.overlayItem.instanceNumber': InstanceNumberOverlayItem,
|
||||
};
|
||||
|
||||
|
||||
const studyDateItem = {
|
||||
id: 'StudyDate',
|
||||
customizationType: 'ohif.overlayItem',
|
||||
label: '',
|
||||
title: 'Study date',
|
||||
condition: ({ referenceInstance }) => referenceInstance?.StudyDate,
|
||||
contentF: ({ referenceInstance, formatters: { formatDate } }) => formatDate(referenceInstance.StudyDate),
|
||||
contentF: ({ referenceInstance, formatters: { formatDate } }) =>
|
||||
formatDate(referenceInstance.StudyDate),
|
||||
};
|
||||
|
||||
const seriesDescriptionItem = {
|
||||
@@ -63,15 +65,19 @@ const seriesDescriptionItem = {
|
||||
condition: ({ referenceInstance }) => {
|
||||
return referenceInstance && referenceInstance.SeriesDescription;
|
||||
},
|
||||
contentF: ({ referenceInstance }) => referenceInstance.SeriesDescription
|
||||
contentF: ({ referenceInstance }) => referenceInstance.SeriesDescription,
|
||||
};
|
||||
|
||||
const topLeftItems = { id: 'cornerstoneOverlayTopLeft', items: [studyDateItem, seriesDescriptionItem] };
|
||||
const topLeftItems = {
|
||||
id: 'cornerstoneOverlayTopLeft',
|
||||
items: [studyDateItem, seriesDescriptionItem],
|
||||
};
|
||||
|
||||
const topRightItems = { id: 'cornerstoneOverlayTopRight', items: [] };
|
||||
|
||||
const bottomLeftItems = {
|
||||
id: 'cornerstoneOverlayBottomLeft', items: [
|
||||
id: 'cornerstoneOverlayBottomLeft',
|
||||
items: [
|
||||
{
|
||||
id: 'WindowLevel',
|
||||
customizationType: 'ohif.overlayItem.windowLevel',
|
||||
@@ -79,12 +85,12 @@ const bottomLeftItems = {
|
||||
{
|
||||
id: 'ZoomLevel',
|
||||
customizationType: 'ohif.overlayItem.zoomLevel',
|
||||
condition: (props) => {
|
||||
condition: props => {
|
||||
const activeToolName = props.toolGroupService.getActiveToolForViewport(props.viewportId);
|
||||
return activeToolName === 'Zoom';
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
const bottomRightItems = {
|
||||
@@ -94,7 +100,7 @@ const bottomRightItems = {
|
||||
id: 'InstanceNumber',
|
||||
customizationType: 'ohif.overlayItem.instanceNumber',
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -145,19 +151,18 @@ function CustomizableViewportOverlay({
|
||||
// append functionality. This code enables the historical usage, but
|
||||
// the recommended functionality is to append to the default values in
|
||||
// cornerstoneOverlay rather than defining individual items.
|
||||
const topLeftCustomization = customizationService.getCustomization(
|
||||
'cornerstoneOverlayTopLeft'
|
||||
) || cornerstoneOverlay?.topLeftItems;
|
||||
const topRightCustomization = customizationService.getCustomization(
|
||||
'cornerstoneOverlayTopRight'
|
||||
) || cornerstoneOverlay?.topRightItems;
|
||||
const bottomLeftCustomization = customizationService.getCustomization(
|
||||
'cornerstoneOverlayBottomLeft'
|
||||
) || cornerstoneOverlay?.bottomLeftItems;
|
||||
const bottomRightCustomization = customizationService.getCustomization(
|
||||
'cornerstoneOverlayBottomRight'
|
||||
) || cornerstoneOverlay?.bottomRightItems;
|
||||
|
||||
const topLeftCustomization =
|
||||
customizationService.getCustomization('cornerstoneOverlayTopLeft') ||
|
||||
cornerstoneOverlay?.topLeftItems;
|
||||
const topRightCustomization =
|
||||
customizationService.getCustomization('cornerstoneOverlayTopRight') ||
|
||||
cornerstoneOverlay?.topRightItems;
|
||||
const bottomLeftCustomization =
|
||||
customizationService.getCustomization('cornerstoneOverlayBottomLeft') ||
|
||||
cornerstoneOverlay?.bottomLeftItems;
|
||||
const bottomRightCustomization =
|
||||
customizationService.getCustomization('cornerstoneOverlayBottomRight') ||
|
||||
cornerstoneOverlay?.bottomRightItems;
|
||||
|
||||
const instanceNumber = useMemo(
|
||||
() =>
|
||||
@@ -177,13 +182,12 @@ function CustomizableViewportOverlay({
|
||||
return {
|
||||
displaySets,
|
||||
displaySet,
|
||||
instance: instances[imageIndex],
|
||||
instance: instances?.[imageIndex],
|
||||
instances,
|
||||
referenceInstance,
|
||||
};
|
||||
}, [viewportData, viewportId, instanceNumber, cornerstoneViewportService]);
|
||||
|
||||
|
||||
/**
|
||||
* Updating the VOI when the viewport changes its voi
|
||||
*/
|
||||
@@ -307,7 +311,8 @@ function CustomizableViewportOverlay({
|
||||
<>
|
||||
{items.map((item, index) => (
|
||||
<div key={`${keyPrefix}_${index}`}>
|
||||
{(!item?.condition || item.condition(props)) && _renderOverlayItem(item, props) || null}
|
||||
{((!item?.condition || item.condition(props)) && _renderOverlayItem(item, props)) ||
|
||||
null}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@@ -316,7 +321,6 @@ function CustomizableViewportOverlay({
|
||||
[_renderOverlayItem]
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<ViewportOverlay
|
||||
topLeft={getContent(topLeftCustomization, 'topLeftOverlayItem')}
|
||||
@@ -335,7 +339,9 @@ function getDisplaySets(viewportData, displaySetService) {
|
||||
if (!viewportData?.data?.length) {
|
||||
return null;
|
||||
}
|
||||
const displaySets = viewportData.data.map(datum => displaySetService.getDisplaySetByUID(datum.displaySetInstanceUID)).filter(it => !!it);
|
||||
const displaySets = viewportData.data
|
||||
.map(datum => displaySetService.getDisplaySetByUID(datum.displaySetInstanceUID))
|
||||
.filter(it => !!it);
|
||||
if (!displaySets.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -382,7 +388,6 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) {
|
||||
return parseInt(instanceNumber);
|
||||
}
|
||||
|
||||
|
||||
// Since volume viewports can be in any view direction, they can render
|
||||
// a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber
|
||||
// Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber
|
||||
@@ -400,6 +405,11 @@ function _getInstanceNumberFromVolume(
|
||||
|
||||
// Todo: support fusion of acquisition plane which has instanceNumber
|
||||
const { volume } = volumes[0];
|
||||
|
||||
if (!volume) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { direction, imageIds } = volume;
|
||||
|
||||
const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
@@ -443,9 +453,10 @@ function OverlayItem(props) {
|
||||
style={{ color, background }}
|
||||
title={title}
|
||||
>
|
||||
{label ? (<span className="mr-1 shrink-0">{label}</span>) : null}
|
||||
{label ? <span className="mr-1 shrink-0">{label}</span> : null}
|
||||
<span className="ml-1 mr-2 shrink-0">{value}</span>
|
||||
</div>);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Enums, Types, utilities } from '@cornerstonejs/core';
|
||||
import { utilities as csToolsUtils } from '@cornerstonejs/tools';
|
||||
import { Enums, VolumeViewport3D, utilities as csUtils } from '@cornerstonejs/core';
|
||||
import { ImageScrollbar } from '@ohif/ui';
|
||||
|
||||
function CornerstoneImageScrollbar({
|
||||
@@ -12,7 +11,9 @@ function CornerstoneImageScrollbar({
|
||||
setImageSliceData,
|
||||
scrollbarHeight,
|
||||
servicesManager,
|
||||
}: withAppTypes) {
|
||||
}: withAppTypes<{
|
||||
element: HTMLElement;
|
||||
}>) {
|
||||
const { cineService, cornerstoneViewportService } = servicesManager.services;
|
||||
|
||||
const onImageScrollbarChange = (imageIndex, viewportId) => {
|
||||
@@ -26,7 +27,7 @@ function CornerstoneImageScrollbar({
|
||||
cineService.setCine({ id: viewportId, isPlaying: false });
|
||||
}
|
||||
|
||||
csToolsUtils.jumpToSlice(viewport.element, {
|
||||
csUtils.jumpToSlice(viewport.element, {
|
||||
imageIndex,
|
||||
debounceLoading: true,
|
||||
});
|
||||
@@ -39,71 +40,47 @@ function CornerstoneImageScrollbar({
|
||||
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport) {
|
||||
if (!viewport || viewport instanceof VolumeViewport3D) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewportData.viewportType === Enums.ViewportType.STACK) {
|
||||
const imageIndex = viewport.getCurrentImageIdIndex();
|
||||
const imageIndex = viewport.getCurrentImageIdIndex();
|
||||
const numberOfSlices = viewport.getNumberOfSlices();
|
||||
|
||||
setImageSliceData({
|
||||
imageIndex: imageIndex,
|
||||
numberOfSlices: viewportData.data[0].imageIds.length,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
const sliceData = utilities.getImageSliceDataForVolumeViewport(
|
||||
viewport as Types.IVolumeViewport
|
||||
);
|
||||
|
||||
if (!sliceData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { imageIndex, numberOfSlices } = sliceData;
|
||||
setImageSliceData({ imageIndex, numberOfSlices });
|
||||
}
|
||||
setImageSliceData({
|
||||
imageIndex: imageIndex,
|
||||
numberOfSlices,
|
||||
});
|
||||
}, [viewportId, viewportData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewportData?.viewportType !== Enums.ViewportType.STACK) {
|
||||
if (!viewportData) {
|
||||
return;
|
||||
}
|
||||
const { viewportType } = viewportData;
|
||||
const eventId =
|
||||
(viewportType === Enums.ViewportType.STACK && Enums.Events.STACK_VIEWPORT_SCROLL) ||
|
||||
(viewportType === Enums.ViewportType.ORTHOGRAPHIC && Enums.Events.VOLUME_NEW_IMAGE) ||
|
||||
Enums.Events.IMAGE_RENDERED;
|
||||
|
||||
const updateStackIndex = event => {
|
||||
const { newImageIdIndex } = event.detail;
|
||||
const updateIndex = event => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
if (!viewport || viewport instanceof VolumeViewport3D) {
|
||||
return;
|
||||
}
|
||||
const { imageIndex, newImageIdIndex = imageIndex } = event.detail;
|
||||
const numberOfSlices = viewport.getNumberOfSlices();
|
||||
// find the index of imageId in the imageIds
|
||||
setImageSliceData({
|
||||
imageIndex: newImageIdIndex,
|
||||
numberOfSlices: viewportData.data[0].imageIds.length,
|
||||
numberOfSlices,
|
||||
});
|
||||
};
|
||||
|
||||
element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex);
|
||||
element.addEventListener(eventId, updateIndex);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex);
|
||||
};
|
||||
}, [viewportData, element]);
|
||||
|
||||
useEffect(() => {
|
||||
if (viewportData?.viewportType !== Enums.ViewportType.ORTHOGRAPHIC) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateVolumeIndex = event => {
|
||||
const { imageIndex, numberOfSlices } = event.detail;
|
||||
// find the index of imageId in the imageIds
|
||||
setImageSliceData({ imageIndex, numberOfSlices });
|
||||
};
|
||||
|
||||
element.addEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
|
||||
element.removeEventListener(eventId, updateIndex);
|
||||
};
|
||||
}, [viewportData, element]);
|
||||
|
||||
@@ -112,7 +89,7 @@ function CornerstoneImageScrollbar({
|
||||
onChange={evt => onImageScrollbarChange(evt, viewportId)}
|
||||
max={imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0}
|
||||
height={scrollbarHeight}
|
||||
value={imageSliceData.imageIndex}
|
||||
value={imageSliceData.imageIndex || 0}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
metaData,
|
||||
Enums,
|
||||
Types,
|
||||
getEnabledElement,
|
||||
utilities as csUtils,
|
||||
} from '@cornerstonejs/core';
|
||||
import { metaData, Enums, Types, getEnabledElement } from '@cornerstonejs/core';
|
||||
import { utilities } from '@cornerstonejs/tools';
|
||||
import PropTypes from 'prop-types';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
|
||||
import './ViewportOrientationMarkers.css';
|
||||
@@ -31,8 +24,9 @@ function ViewportOrientationMarkers({
|
||||
|
||||
useEffect(() => {
|
||||
const cameraModifiedListener = (evt: Types.EventTypes.CameraModifiedEvent) => {
|
||||
const { rotation, previousCamera, camera } = evt.detail;
|
||||
const { previousCamera, camera } = evt.detail;
|
||||
|
||||
const { rotation } = camera;
|
||||
if (rotation !== undefined) {
|
||||
setRotation(rotation);
|
||||
}
|
||||
@@ -74,7 +68,12 @@ function ViewportOrientationMarkers({
|
||||
return false;
|
||||
}
|
||||
|
||||
({ rowCosines, columnCosines, isDefaultValueSetForColumnCosine, isDefaultValueSetForColumnCosine } = metaData.get('imagePlaneModule', imageId) || {});
|
||||
({
|
||||
rowCosines,
|
||||
columnCosines,
|
||||
isDefaultValueSetForColumnCosine,
|
||||
isDefaultValueSetForColumnCosine,
|
||||
} = metaData.get('imagePlaneModule', imageId) || {});
|
||||
} else {
|
||||
if (!element || !getEnabledElement(element)) {
|
||||
return '';
|
||||
@@ -90,7 +89,13 @@ function ViewportOrientationMarkers({
|
||||
rowCosines = viewRight;
|
||||
}
|
||||
|
||||
if (!rowCosines || !columnCosines || rotation === undefined || isDefaultValueSetForRowCosine || isDefaultValueSetForColumnCosine) {
|
||||
if (
|
||||
!rowCosines ||
|
||||
!columnCosines ||
|
||||
rotation === undefined ||
|
||||
isDefaultValueSetForRowCosine ||
|
||||
isDefaultValueSetForColumnCosine
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
@@ -52,31 +52,6 @@ export function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
|
||||
return moment(time, 'HH:mm:ss').format(strFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a patient name for display purposes
|
||||
*
|
||||
* @param {string} name
|
||||
* @returns {string} formatted name.
|
||||
*/
|
||||
export function formatPN(name) {
|
||||
if (!name) {
|
||||
return '';
|
||||
}
|
||||
if (typeof name === 'object') {
|
||||
name = name.Alphabetic;
|
||||
if (!name) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const cleaned = name
|
||||
.split('^')
|
||||
.filter(s => !!s)
|
||||
.join(', ')
|
||||
.trim();
|
||||
return cleaned === ',' || cleaned === '' ? '' : cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets compression type
|
||||
*
|
||||
|
||||
@@ -12,15 +12,23 @@ import {
|
||||
utilities as cstUtils,
|
||||
ReferenceLinesTool,
|
||||
} from '@cornerstonejs/tools';
|
||||
|
||||
import { Types as OhifTypes } from '@ohif/core';
|
||||
import {
|
||||
callLabelAutocompleteDialog,
|
||||
showLabelAnnotationPopup,
|
||||
createReportAsync,
|
||||
callInputDialog,
|
||||
colorPickerDialog,
|
||||
} from '@ohif/extension-default';
|
||||
import { vec3, mat4 } from 'gl-matrix';
|
||||
|
||||
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
|
||||
import { callLabelAutocompleteDialog, showLabelAnnotationPopup } from './utils/callInputDialog';
|
||||
import toggleImageSliceSync from './utils/imageSliceSync/toggleImageSliceSync';
|
||||
import { getFirstAnnotationSelected } from './utils/measurementServiceMappings/utils/selection';
|
||||
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
|
||||
import toggleVOISliceSync from './utils/toggleVOISliceSync';
|
||||
import { usePositionPresentationStore, useSegmentationPresentationStore } from './stores';
|
||||
|
||||
const toggleSyncFunctions = {
|
||||
imageSlice: toggleImageSliceSync,
|
||||
@@ -29,6 +37,7 @@ const toggleSyncFunctions = {
|
||||
|
||||
function commandsModule({
|
||||
servicesManager,
|
||||
extensionManager,
|
||||
commandsManager,
|
||||
}: OhifTypes.Extensions.ExtensionParams): OhifTypes.Extensions.CommandsModule {
|
||||
const {
|
||||
@@ -101,7 +110,47 @@ function commandsModule({
|
||||
|
||||
commandsManager.run(options, optionsToUse);
|
||||
},
|
||||
updateStoredSegmentationPresentation: ({ displaySet, type }) => {
|
||||
const { addSegmentationPresentationItem } = useSegmentationPresentationStore.getState();
|
||||
|
||||
const referencedDisplaySetInstanceUID = displaySet.referencedDisplaySetInstanceUID;
|
||||
addSegmentationPresentationItem(referencedDisplaySetInstanceUID, {
|
||||
segmentationId: displaySet.displaySetInstanceUID,
|
||||
hydrated: true,
|
||||
type,
|
||||
});
|
||||
},
|
||||
updateStoredPositionPresentation: ({ viewportId, displaySetInstanceUID }) => {
|
||||
const presentations = cornerstoneViewportService.getPresentations(viewportId);
|
||||
const { positionPresentationStore, setPositionPresentation, getPositionPresentationId } =
|
||||
usePositionPresentationStore.getState();
|
||||
|
||||
// Look inside positionPresentationStore and find the key that includes the displaySetInstanceUID
|
||||
// and the value has viewportId as activeViewportId.
|
||||
const previousReferencedDisplaySetStoreKey = Object.entries(positionPresentationStore).find(
|
||||
([key, value]) => key.includes(displaySetInstanceUID) && value.viewportId === viewportId
|
||||
)?.[0];
|
||||
|
||||
if (previousReferencedDisplaySetStoreKey) {
|
||||
setPositionPresentation(
|
||||
previousReferencedDisplaySetStoreKey,
|
||||
presentations.positionPresentation
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if not found means we have not visited that referencedDisplaySetInstanceUID before
|
||||
// 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],
|
||||
viewportId,
|
||||
});
|
||||
|
||||
setPositionPresentation(positionPresentationId, presentations.positionPresentation);
|
||||
},
|
||||
getNearbyToolData({ nearbyToolData, element, canvasCoordinates }) {
|
||||
return nearbyToolData ?? cstUtils.getAnnotationNearPoint(element, canvasCoordinates);
|
||||
},
|
||||
@@ -435,9 +484,10 @@ function commandsModule({
|
||||
viewport.setCamera({ viewUp: rotatedViewUp as CoreTypes.Point3 });
|
||||
viewport.render();
|
||||
} else if (viewport.getRotation !== undefined) {
|
||||
const currentRotation = viewport.getRotation();
|
||||
const presentation = viewport.getViewPresentation();
|
||||
const { rotation: currentRotation } = presentation;
|
||||
const newRotation = (currentRotation + rotation) % 360;
|
||||
viewport.setProperties({ rotation: newRotation });
|
||||
viewport.setViewPresentation({ rotation: newRotation });
|
||||
viewport.render();
|
||||
}
|
||||
},
|
||||
@@ -554,7 +604,7 @@ function commandsModule({
|
||||
|
||||
// Set slice to last slice
|
||||
const options = { imageIndex: jumpIndex };
|
||||
cstUtils.jumpToSlice(viewport.element, options);
|
||||
csUtils.jumpToSlice(viewport.element, options);
|
||||
},
|
||||
scroll: ({ direction }) => {
|
||||
const enabledElement = _getActiveViewportEnabledElement();
|
||||
@@ -566,7 +616,7 @@ function commandsModule({
|
||||
const { viewport } = enabledElement;
|
||||
const options = { delta: direction };
|
||||
|
||||
cstUtils.scroll(viewport, options);
|
||||
csUtils.scroll(viewport, options);
|
||||
},
|
||||
setViewportColormap: ({
|
||||
viewportId,
|
||||
@@ -577,7 +627,6 @@ function commandsModule({
|
||||
}) => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
const actorEntries = viewport.getActors();
|
||||
let hpOpacity;
|
||||
// Retrieve active protocol's viewport match details
|
||||
const { viewportMatchDetails } = hangingProtocolService.getActiveProtocol();
|
||||
@@ -596,14 +645,8 @@ function commandsModule({
|
||||
// HP takes priority over the default opacity
|
||||
colormap = { ...colormap, opacity: hpOpacity || opacity };
|
||||
|
||||
const setViewportProperties = (viewport, uid) => {
|
||||
const actorEntry = actorEntries.find(entry => entry.uid.includes(uid));
|
||||
const { actor: volumeActor, uid: volumeId } = actorEntry;
|
||||
viewport.setProperties({ colormap, volumeActor }, volumeId);
|
||||
};
|
||||
|
||||
if (viewport instanceof StackViewport) {
|
||||
setViewportProperties(viewport, viewportId);
|
||||
viewport.setProperties({ colormap });
|
||||
}
|
||||
|
||||
if (viewport instanceof VolumeViewport) {
|
||||
@@ -611,7 +654,9 @@ function commandsModule({
|
||||
const { viewports } = viewportGridService.getState();
|
||||
displaySetInstanceUID = viewports.get(viewportId)?.displaySetInstanceUIDs[0];
|
||||
}
|
||||
setViewportProperties(viewport, displaySetInstanceUID);
|
||||
|
||||
const volumeId = viewport.getVolumeId();
|
||||
viewport.setProperties({ colormap }, volumeId);
|
||||
}
|
||||
|
||||
if (immediate) {
|
||||
@@ -824,9 +869,352 @@ function commandsModule({
|
||||
}
|
||||
|
||||
crosshairInstances.forEach(ins => {
|
||||
ins?.resetCrosshairs();
|
||||
ins?.computeToolCenter();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Creates a labelmap for the active viewport
|
||||
*/
|
||||
createLabelmapForViewport: async ({ viewportId, options = {} }) => {
|
||||
const { viewportGridService, displaySetService, segmentationService } =
|
||||
servicesManager.services;
|
||||
const { viewports } = viewportGridService.getState();
|
||||
const targetViewportId = viewportId;
|
||||
|
||||
const viewport = viewports.get(targetViewportId);
|
||||
|
||||
// Todo: add support for multiple display sets
|
||||
const displaySetInstanceUID =
|
||||
options.displaySetInstanceUID || viewport.displaySetInstanceUIDs[0];
|
||||
|
||||
const segs = segmentationService.getSegmentations();
|
||||
|
||||
const label = options.label || `Segmentation ${segs.length + 1}`;
|
||||
const segmentationId = options.segmentationId || `${csUtils.uuidv4()}`;
|
||||
|
||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||
|
||||
const generatedSegmentationId = await segmentationService.createLabelmapForDisplaySet(
|
||||
displaySet,
|
||||
{
|
||||
label,
|
||||
segmentationId,
|
||||
segments: options.createInitialSegment
|
||||
? {
|
||||
1: {
|
||||
label: 'Segment 1',
|
||||
active: true,
|
||||
},
|
||||
}
|
||||
: {},
|
||||
}
|
||||
);
|
||||
|
||||
await segmentationService.addSegmentationRepresentation(viewportId, {
|
||||
segmentationId,
|
||||
type: Enums.SegmentationRepresentations.Labelmap,
|
||||
});
|
||||
|
||||
return generatedSegmentationId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the active segmentation for a viewport
|
||||
* @param props.segmentationId - The ID of the segmentation to set as active
|
||||
*/
|
||||
setActiveSegmentation: ({ segmentationId }) => {
|
||||
const { viewportGridService, segmentationService } = servicesManager.services;
|
||||
segmentationService.setActiveSegmentation(
|
||||
viewportGridService.getActiveViewportId(),
|
||||
segmentationId
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new segment to a segmentation
|
||||
* @param props.segmentationId - The ID of the segmentation to add the segment to
|
||||
*/
|
||||
addSegmentCommand: ({ segmentationId }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.addSegment(segmentationId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the active segment and jumps to its center
|
||||
* @param props.segmentationId - The ID of the segmentation
|
||||
* @param props.segmentIndex - The index of the segment to activate
|
||||
*/
|
||||
setActiveSegmentAndCenterCommand: ({ segmentationId, segmentIndex }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.setActiveSegment(segmentationId, segmentIndex);
|
||||
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the visibility of a segment
|
||||
* @param props.segmentationId - The ID of the segmentation
|
||||
* @param props.segmentIndex - The index of the segment
|
||||
* @param props.type - The type of visibility to toggle
|
||||
*/
|
||||
toggleSegmentVisibilityCommand: ({ segmentationId, segmentIndex, type }) => {
|
||||
const { segmentationService, viewportGridService } = servicesManager.services;
|
||||
segmentationService.toggleSegmentVisibility(
|
||||
viewportGridService.getActiveViewportId(),
|
||||
segmentationId,
|
||||
segmentIndex,
|
||||
type
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the lock state of a segment
|
||||
* @param props.segmentationId - The ID of the segmentation
|
||||
* @param props.segmentIndex - The index of the segment
|
||||
*/
|
||||
toggleSegmentLockCommand: ({ segmentationId, segmentIndex }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.toggleSegmentLocked(segmentationId, segmentIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the visibility of a segmentation representation
|
||||
* @param props.segmentationId - The ID of the segmentation
|
||||
* @param props.type - The type of representation
|
||||
*/
|
||||
toggleSegmentationVisibilityCommand: ({ segmentationId, type }) => {
|
||||
const { segmentationService, viewportGridService } = servicesManager.services;
|
||||
segmentationService.toggleSegmentationRepresentationVisibility(
|
||||
viewportGridService.getActiveViewportId(),
|
||||
{ segmentationId, type }
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Downloads a segmentation
|
||||
* @param props.segmentationId - The ID of the segmentation to download
|
||||
*/
|
||||
downloadSegmentationCommand: ({ segmentationId }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.downloadSegmentation(segmentationId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores a segmentation and shows it in the viewport
|
||||
* @param props.segmentationId - The ID of the segmentation to store
|
||||
*/
|
||||
storeSegmentationCommand: async ({ segmentationId }) => {
|
||||
const { segmentationService, viewportGridService } = servicesManager.services;
|
||||
const datasources = extensionManager.getActiveDataSource();
|
||||
|
||||
const displaySetInstanceUIDs = await createReportAsync({
|
||||
servicesManager,
|
||||
getReport: () =>
|
||||
commandsManager.runCommand('storeSegmentation', {
|
||||
segmentationId,
|
||||
dataSource: datasources[0],
|
||||
}),
|
||||
reportType: 'Segmentation',
|
||||
});
|
||||
|
||||
if (displaySetInstanceUIDs) {
|
||||
segmentationService.remove(segmentationId);
|
||||
viewportGridService.setDisplaySetsForViewport({
|
||||
viewportId: viewportGridService.getActiveViewportId(),
|
||||
displaySetInstanceUIDs,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Downloads a segmentation as RTSS
|
||||
* @param props.segmentationId - The ID of the segmentation
|
||||
*/
|
||||
downloadRTSSCommand: ({ segmentationId }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.downloadRTSS(segmentationId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the style for a segmentation
|
||||
* @param props.segmentationId - The ID of the segmentation
|
||||
* @param props.type - The type of style
|
||||
* @param props.key - The style key to set
|
||||
* @param props.value - The style value
|
||||
*/
|
||||
setSegmentationStyleCommand: ({ type, key, value }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.setStyle({ type }, { [key]: value });
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a segment from a segmentation
|
||||
* @param props.segmentationId - The ID of the segmentation
|
||||
* @param props.segmentIndex - The index of the segment to delete
|
||||
*/
|
||||
deleteSegmentCommand: ({ segmentationId, segmentIndex }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.removeSegment(segmentationId, segmentIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes an entire segmentation
|
||||
* @param props.segmentationId - The ID of the segmentation to delete
|
||||
*/
|
||||
deleteSegmentationCommand: ({ segmentationId }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.remove(segmentationId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a segmentation from the viewport
|
||||
* @param props.segmentationId - The ID of the segmentation to remove
|
||||
*/
|
||||
removeSegmentationFromViewportCommand: ({ segmentationId }) => {
|
||||
const { segmentationService, viewportGridService } = servicesManager.services;
|
||||
segmentationService.removeSegmentationRepresentations(
|
||||
viewportGridService.getActiveViewportId(),
|
||||
{ segmentationId }
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles rendering of inactive segmentations
|
||||
*/
|
||||
toggleRenderInactiveSegmentationsCommand: () => {
|
||||
const { segmentationService, viewportGridService } = servicesManager.services;
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const renderInactive = segmentationService.getRenderInactiveSegmentations(viewportId);
|
||||
segmentationService.setRenderInactiveSegmentations(viewportId, !renderInactive);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the fill alpha value for a segmentation type
|
||||
* @param props.type - The type of segmentation
|
||||
* @param props.value - The alpha value to set
|
||||
*/
|
||||
setFillAlphaCommand: ({ type, value }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.setStyle({ type }, { fillAlpha: value });
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the outline width for a segmentation type
|
||||
* @param props.type - The type of segmentation
|
||||
* @param props.value - The width value to set
|
||||
*/
|
||||
setOutlineWidthCommand: ({ type, value }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.setStyle({ type }, { outlineWidth: value });
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets whether to render fill for a segmentation type
|
||||
* @param props.type - The type of segmentation
|
||||
* @param props.value - Whether to render fill
|
||||
*/
|
||||
setRenderFillCommand: ({ type, value }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.setStyle({ type }, { renderFill: value });
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets whether to render outline for a segmentation type
|
||||
* @param props.type - The type of segmentation
|
||||
* @param props.value - Whether to render outline
|
||||
*/
|
||||
setRenderOutlineCommand: ({ type, value }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.setStyle({ type }, { renderOutline: value });
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the fill alpha for inactive segmentations
|
||||
* @param props.type - The type of segmentation
|
||||
* @param props.value - The alpha value to set
|
||||
*/
|
||||
setFillAlphaInactiveCommand: ({ type, value }) => {
|
||||
const { segmentationService } = servicesManager.services;
|
||||
segmentationService.setStyle({ type }, { fillAlphaInactive: value });
|
||||
},
|
||||
|
||||
editSegmentLabel: ({ segmentationId, segmentIndex }) => {
|
||||
const { segmentationService, uiDialogService } = servicesManager.services;
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
if (!segmentation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const segment = segmentation.segments[segmentIndex];
|
||||
const { label } = segment;
|
||||
|
||||
const callback = (label, actionId) => {
|
||||
if (label === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
|
||||
};
|
||||
|
||||
callInputDialog(uiDialogService, label, callback, false, {
|
||||
dialogTitle: 'Edit Segment Label',
|
||||
inputLabel: 'Enter new label',
|
||||
});
|
||||
},
|
||||
|
||||
editSegmentationLabel: ({ segmentationId }) => {
|
||||
const { segmentationService, uiDialogService } = servicesManager.services;
|
||||
const segmentation = segmentationService.getSegmentation(segmentationId);
|
||||
|
||||
if (!segmentation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { label } = segmentation;
|
||||
|
||||
const callback = (label, actionId) => {
|
||||
if (label === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
segmentationService.addOrUpdateSegmentation({ segmentationId, label });
|
||||
};
|
||||
|
||||
callInputDialog(uiDialogService, label, callback, false, {
|
||||
dialogTitle: 'Edit Segmentation Label',
|
||||
inputLabel: 'Enter new label',
|
||||
});
|
||||
},
|
||||
|
||||
editSegmentColor: ({ segmentationId, segmentIndex }) => {
|
||||
const { segmentationService, uiDialogService, viewportGridService } =
|
||||
servicesManager.services;
|
||||
const viewportId = viewportGridService.getActiveViewportId();
|
||||
const color = segmentationService.getSegmentColor(viewportId, segmentationId, segmentIndex);
|
||||
|
||||
const rgbaColor = {
|
||||
r: color[0],
|
||||
g: color[1],
|
||||
b: color[2],
|
||||
a: color[3] / 255.0,
|
||||
};
|
||||
|
||||
colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
|
||||
if (actionId === 'cancel') {
|
||||
return;
|
||||
}
|
||||
|
||||
const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
|
||||
segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
|
||||
});
|
||||
},
|
||||
|
||||
getRenderInactiveSegmentations: () => {
|
||||
const { segmentationService, viewportGridService } = servicesManager.services;
|
||||
return segmentationService.getRenderInactiveSegmentations(
|
||||
viewportGridService.getActiveViewportId()
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
@@ -988,6 +1376,84 @@ function commandsModule({
|
||||
toggleActiveDisabledToolbar: {
|
||||
commandFn: actions.toggleActiveDisabledToolbar,
|
||||
},
|
||||
updateStoredPositionPresentation: {
|
||||
commandFn: actions.updateStoredPositionPresentation,
|
||||
},
|
||||
updateStoredSegmentationPresentation: {
|
||||
commandFn: actions.updateStoredSegmentationPresentation,
|
||||
},
|
||||
createLabelmapForViewport: {
|
||||
commandFn: actions.createLabelmapForViewport,
|
||||
},
|
||||
setActiveSegmentation: {
|
||||
commandFn: actions.setActiveSegmentation,
|
||||
},
|
||||
addSegment: {
|
||||
commandFn: actions.addSegmentCommand,
|
||||
},
|
||||
setActiveSegmentAndCenter: {
|
||||
commandFn: actions.setActiveSegmentAndCenterCommand,
|
||||
},
|
||||
toggleSegmentVisibility: {
|
||||
commandFn: actions.toggleSegmentVisibilityCommand,
|
||||
},
|
||||
toggleSegmentLock: {
|
||||
commandFn: actions.toggleSegmentLockCommand,
|
||||
},
|
||||
toggleSegmentationVisibility: {
|
||||
commandFn: actions.toggleSegmentationVisibilityCommand,
|
||||
},
|
||||
downloadSegmentation: {
|
||||
commandFn: actions.downloadSegmentationCommand,
|
||||
},
|
||||
storeSegmentation: {
|
||||
commandFn: actions.storeSegmentationCommand,
|
||||
},
|
||||
downloadRTSS: {
|
||||
commandFn: actions.downloadRTSSCommand,
|
||||
},
|
||||
setSegmentationStyle: {
|
||||
commandFn: actions.setSegmentationStyleCommand,
|
||||
},
|
||||
deleteSegment: {
|
||||
commandFn: actions.deleteSegmentCommand,
|
||||
},
|
||||
deleteSegmentation: {
|
||||
commandFn: actions.deleteSegmentationCommand,
|
||||
},
|
||||
removeSegmentationFromViewport: {
|
||||
commandFn: actions.removeSegmentationFromViewportCommand,
|
||||
},
|
||||
toggleRenderInactiveSegmentations: {
|
||||
commandFn: actions.toggleRenderInactiveSegmentationsCommand,
|
||||
},
|
||||
setFillAlpha: {
|
||||
commandFn: actions.setFillAlphaCommand,
|
||||
},
|
||||
setOutlineWidth: {
|
||||
commandFn: actions.setOutlineWidthCommand,
|
||||
},
|
||||
setRenderFill: {
|
||||
commandFn: actions.setRenderFillCommand,
|
||||
},
|
||||
setRenderOutline: {
|
||||
commandFn: actions.setRenderOutlineCommand,
|
||||
},
|
||||
setFillAlphaInactive: {
|
||||
commandFn: actions.setFillAlphaInactiveCommand,
|
||||
},
|
||||
editSegmentLabel: {
|
||||
commandFn: actions.editSegmentLabel,
|
||||
},
|
||||
editSegmentationLabel: {
|
||||
commandFn: actions.editSegmentationLabel,
|
||||
},
|
||||
editSegmentColor: {
|
||||
commandFn: actions.editSegmentColor,
|
||||
},
|
||||
getRenderInactiveSegmentations: {
|
||||
commandFn: actions.getRenderInactiveSegmentations,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { CinePlayer, useCine } from '@ohif/ui';
|
||||
import { Enums, eventTarget, cache } from '@cornerstonejs/core';
|
||||
import { Enums as StreamingEnums } from '@cornerstonejs/streaming-image-volume-loader';
|
||||
import { useAppConfig } from '@state';
|
||||
|
||||
function WrappedCinePlayer({
|
||||
@@ -101,7 +100,7 @@ function WrappedCinePlayer({
|
||||
return;
|
||||
}
|
||||
|
||||
eventTarget.addEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, newDisplaySetHandler);
|
||||
enabledVPElement.addEventListener(Enums.Events.VIEWPORT_NEW_IMAGE_SET, newDisplaySetHandler);
|
||||
// this doesn't makes sense that we are listening to this event on viewport element
|
||||
enabledVPElement.addEventListener(
|
||||
Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
|
||||
@@ -111,7 +110,10 @@ function WrappedCinePlayer({
|
||||
return () => {
|
||||
cineService.setCine({ id: viewportId, isPlaying: false });
|
||||
|
||||
eventTarget.removeEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, newDisplaySetHandler);
|
||||
enabledVPElement.removeEventListener(
|
||||
Enums.Events.VIEWPORT_NEW_IMAGE_SET,
|
||||
newDisplaySetHandler
|
||||
);
|
||||
enabledVPElement.removeEventListener(
|
||||
Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME,
|
||||
newDisplaySetHandler
|
||||
@@ -181,13 +183,13 @@ function RenderCinePlayer({
|
||||
};
|
||||
|
||||
eventTarget.addEventListener(
|
||||
StreamingEnums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED,
|
||||
Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED,
|
||||
handleTimePointIndexChange
|
||||
);
|
||||
|
||||
return () => {
|
||||
eventTarget.removeEventListener(
|
||||
StreamingEnums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED,
|
||||
Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED,
|
||||
handleTimePointIndexChange
|
||||
);
|
||||
};
|
||||
@@ -199,7 +201,7 @@ function RenderCinePlayer({
|
||||
}
|
||||
|
||||
const { volumeId, timePointIndex, numTimePoints, splittingTag } = dynamicInfo || {};
|
||||
const volume = cache.getVolume(volumeId);
|
||||
const volume = cache.getVolume(volumeId, true);
|
||||
volume.timePointIndex = timePointIndex;
|
||||
|
||||
setDynamicInfo({ volumeId, timePointIndex, numTimePoints, label: splittingTag });
|
||||
@@ -207,7 +209,7 @@ function RenderCinePlayer({
|
||||
|
||||
const updateDynamicInfo = useCallback(props => {
|
||||
const { volumeId, timePointIndex } = props;
|
||||
const volume = cache.getVolume(volumeId);
|
||||
const volume = cache.getVolume(volumeId, true);
|
||||
volume.timePointIndex = timePointIndex;
|
||||
}, []);
|
||||
|
||||
@@ -223,6 +225,7 @@ function RenderCinePlayer({
|
||||
isPlaying: false,
|
||||
});
|
||||
cineService.setIsCineEnabled(false);
|
||||
cineService.setViewportCineClosed(viewportId);
|
||||
}}
|
||||
onPlayPauseChange={isPlaying => {
|
||||
cineService.setCine({
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
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
@@ -0,0 +1,83 @@
|
||||
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' };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { ViewportSegmentationMenuWrapper } from './ViewportSegmentationMenuWrapper';
|
||||
|
||||
export function getViewportDataOverlaySettingsMenu(
|
||||
props: withAppTypes<{
|
||||
viewportId: string;
|
||||
element: HTMLElement;
|
||||
}>
|
||||
): ReactNode {
|
||||
return <ViewportSegmentationMenuWrapper {...props} />;
|
||||
}
|
||||
+61
-211
@@ -1,10 +1,15 @@
|
||||
import React, { useEffect, useCallback, useState, ReactElement } from 'react';
|
||||
import React, { useEffect, useCallback, useState, ReactElement, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { PanelSection, WindowLevel } from '@ohif/ui';
|
||||
import vtkColorMaps from '@kitware/vtk.js/Rendering/Core/ColorTransferFunction/ColorMaps';
|
||||
import { Enums, eventTarget, cache as cs3DCache, utilities as csUtils } from '@cornerstonejs/core';
|
||||
import { getViewportVolumeHistogram } from './getViewportVolumeHistogram';
|
||||
import { Enums, eventTarget } from '@cornerstonejs/core';
|
||||
import { useActiveViewportDisplaySets } from '@ohif/core';
|
||||
import {
|
||||
getNodeOpacity,
|
||||
isPetVolumeWithDefaultOpacity,
|
||||
isVolumeWithConstantOpacity,
|
||||
getWindowLevelsData,
|
||||
} from './utils';
|
||||
|
||||
const { Events } = Enums;
|
||||
|
||||
@@ -16,19 +21,16 @@ const ViewportWindowLevel = ({
|
||||
}>): ReactElement => {
|
||||
const { cornerstoneViewportService } = servicesManager.services;
|
||||
const [windowLevels, setWindowLevels] = useState([]);
|
||||
const [cachedHistograms, setCachedHistograms] = useState({});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const displaySets = useActiveViewportDisplaySets({ servicesManager });
|
||||
|
||||
/**
|
||||
* Looks for all viewports that has exactly all volumeIds passed as parameter.
|
||||
*/
|
||||
const getViewportsWithVolumeIds = useCallback(
|
||||
(volumeIds: string[]) => {
|
||||
const renderingEngine = cornerstoneViewportService.getRenderingEngine();
|
||||
const viewports = renderingEngine.getVolumeViewports();
|
||||
|
||||
return viewports.filter(vp => {
|
||||
const viewportVolumeIds = vp.getActors().map(actor => actor.uid);
|
||||
|
||||
const viewportVolumeIds = vp.getActors().map(actor => actor.referencedId);
|
||||
return (
|
||||
volumeIds.length === viewportVolumeIds.length &&
|
||||
volumeIds.every(volumeId => viewportVolumeIds.includes(volumeId))
|
||||
@@ -38,97 +40,10 @@ const ViewportWindowLevel = ({
|
||||
[cornerstoneViewportService]
|
||||
);
|
||||
|
||||
const getNodeOpacity = (volumeActor, nodeIndex) => {
|
||||
const volumeOpacity = volumeActor.getProperty().getScalarOpacity(0);
|
||||
const nodeValue = [];
|
||||
|
||||
volumeOpacity.getNodeValue(nodeIndex, nodeValue);
|
||||
|
||||
return nodeValue[1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the opacity applied to the PET volume is something like
|
||||
* [{x: 0, y: 0}, {x: 0.1, y: [C]}, {x: [ANY], y: [C]}] where C is a
|
||||
* constant opacity value for all x's greater than 0.1
|
||||
*/
|
||||
const isPetVolumeWithDefaultOpacity = (volumeId, volumeActor) => {
|
||||
const volume = cs3DCache.getVolume(volumeId);
|
||||
|
||||
if (!volume) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const modality = volume.metadata.Modality;
|
||||
|
||||
if (modality !== 'PT') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const volumeOpacity = volumeActor.getProperty().getScalarOpacity(0);
|
||||
|
||||
// It must have at least two points (0 and 0.1)
|
||||
if (volumeOpacity.getSize() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const node1Value = [];
|
||||
const node2Value = [];
|
||||
|
||||
volumeOpacity.getNodeValue(0, node1Value);
|
||||
volumeOpacity.getNodeValue(1, node2Value);
|
||||
|
||||
// First node must be (x:0, y:0} and the second one {x:0.1, y:any}
|
||||
if (node1Value[0] !== 0 || node1Value[1] !== 0 || node2Value[0] !== 0.1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const expectedOpacity = node2Value[1];
|
||||
const opacitySize = volumeOpacity.getSize();
|
||||
const currentNodeValue = [];
|
||||
|
||||
// Any point after 0.1 must have the same opacity
|
||||
for (let i = 2; i < opacitySize; i++) {
|
||||
volumeOpacity.getNodeValue(i, currentNodeValue);
|
||||
|
||||
if (currentNodeValue[1] !== expectedOpacity) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the opacity function has a constance opacity value for all x's
|
||||
*/
|
||||
const isVolumeWithConstantOpacity = volumeActor => {
|
||||
const volumeOpacity = volumeActor.getProperty().getScalarOpacity(0);
|
||||
const opacitySize = volumeOpacity.getSize();
|
||||
const firstNodeValue = [];
|
||||
|
||||
volumeOpacity.getNodeValue(0, firstNodeValue);
|
||||
|
||||
const firstNodeOpacity = firstNodeValue[1];
|
||||
|
||||
for (let i = 0; i < opacitySize; i++) {
|
||||
const currentNodeValue = [];
|
||||
|
||||
volumeOpacity.getNodeValue(0, currentNodeValue);
|
||||
|
||||
if (currentNodeValue[1] !== firstNodeOpacity) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const getVolumeOpacity = useCallback((viewport, volumeId) => {
|
||||
const volumeActor = viewport.getActor(volumeId).actor;
|
||||
const volumeActor = viewport.getActors().find(actor => actor.referencedId === volumeId)?.actor;
|
||||
|
||||
if (isPetVolumeWithDefaultOpacity(volumeId, volumeActor)) {
|
||||
// Get the opacity from the second node at 0.1
|
||||
return getNodeOpacity(volumeActor, 1);
|
||||
} else if (isVolumeWithConstantOpacity(volumeActor)) {
|
||||
return getNodeOpacity(volumeActor, 0);
|
||||
@@ -137,85 +52,14 @@ const ViewportWindowLevel = ({
|
||||
return undefined;
|
||||
}, []);
|
||||
|
||||
const getWindowLevelsData = useCallback(
|
||||
async (viewportId: number) => {
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
|
||||
if (!viewport) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||
|
||||
const volumeIds = viewport.getActors().map(actor => actor.uid);
|
||||
const viewportProperties = viewport.getProperties();
|
||||
const { voiRange } = viewportProperties;
|
||||
const viewportVoi = voiRange
|
||||
? {
|
||||
windowWidth: voiRange.upper - voiRange.lower,
|
||||
windowCenter: voiRange.lower + (voiRange.upper - voiRange.lower) / 2,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const windowLevels = await Promise.all(
|
||||
volumeIds.map(async (volumeId, volumeIndex) => {
|
||||
const volume = cs3DCache.getVolume(volumeId);
|
||||
|
||||
if (!volume) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const opacity = getVolumeOpacity(viewport, volumeId);
|
||||
const { metadata, scaling } = volume;
|
||||
const modality = metadata.Modality;
|
||||
|
||||
// TODO: find a proper way to fix the histogram
|
||||
const options = {
|
||||
min: modality === 'PT' ? 0.1 : -999,
|
||||
max: modality === 'PT' ? 5 : 10000,
|
||||
};
|
||||
|
||||
const histogram =
|
||||
cachedHistograms[volumeId] ??
|
||||
(await getViewportVolumeHistogram(viewport, volume, options));
|
||||
const { voi: displaySetVOI, colormap: displaySetColormap } =
|
||||
viewportInfo.displaySetOptions[volumeIndex];
|
||||
let colormap;
|
||||
if (displaySetColormap) {
|
||||
colormap =
|
||||
csUtils.colormap.getColormap(displaySetColormap.name) ??
|
||||
vtkColorMaps.getPresetByName(displaySetColormap.name);
|
||||
}
|
||||
|
||||
const voi = !volumeIndex ? viewportVoi ?? displaySetVOI : displaySetVOI;
|
||||
|
||||
return {
|
||||
viewportId,
|
||||
modality,
|
||||
volumeId,
|
||||
volumeIndex,
|
||||
voi,
|
||||
histogram,
|
||||
colormap,
|
||||
step: scaling?.PT ? 0.05 : 1,
|
||||
opacity,
|
||||
showOpacitySlider: volumeIndex === 1 && opacity !== undefined,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const data = windowLevels.filter(Boolean);
|
||||
return data;
|
||||
},
|
||||
|
||||
[cachedHistograms, cornerstoneViewportService, getVolumeOpacity]
|
||||
);
|
||||
|
||||
const updateViewportHistograms = useCallback(() => {
|
||||
getWindowLevelsData(viewportId).then(windowLevels => {
|
||||
setWindowLevels(windowLevels);
|
||||
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
|
||||
|
||||
getWindowLevelsData(viewport, viewportInfo, getVolumeOpacity).then(data => {
|
||||
setWindowLevels(data);
|
||||
});
|
||||
}, [viewportId, getWindowLevelsData]);
|
||||
}, [viewportId, cornerstoneViewportService, getVolumeOpacity]);
|
||||
|
||||
const handleCornerstoneVOIModified = useCallback(
|
||||
e => {
|
||||
@@ -252,8 +96,8 @@ const ViewportWindowLevel = ({
|
||||
[windowLevels]
|
||||
);
|
||||
|
||||
const debouncedHandleCornerstoneVOIModified = useCallback(
|
||||
debounce(handleCornerstoneVOIModified, 100),
|
||||
const debouncedHandleCornerstoneVOIModified = useMemo(
|
||||
() => debounce(handleCornerstoneVOIModified, 100),
|
||||
[handleCornerstoneVOIModified]
|
||||
);
|
||||
|
||||
@@ -280,7 +124,7 @@ const ViewportWindowLevel = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportVolumeIds = viewport.getActors().map(actor => actor.uid);
|
||||
const viewportVolumeIds = viewport.getActors().map(actor => actor.referencedId);
|
||||
const viewports = getViewportsWithVolumeIds(viewportVolumeIds);
|
||||
|
||||
viewports.forEach(vp => {
|
||||
@@ -291,49 +135,50 @@ const ViewportWindowLevel = ({
|
||||
[getViewportsWithVolumeIds, cornerstoneViewportService]
|
||||
);
|
||||
|
||||
// Listen to windowLevels changes and caches all the new ones
|
||||
// New function to handle image volume loading completion
|
||||
const handleImageVolumeLoadingCompleted = useCallback(() => {
|
||||
setIsLoading(false);
|
||||
updateViewportHistograms();
|
||||
}, [updateViewportHistograms]);
|
||||
|
||||
// Listen to cornerstone events and set up interval for histogram updates
|
||||
useEffect(() => {
|
||||
const newVolumeHistograms = windowLevels
|
||||
.filter(windowLevel => !cachedHistograms[windowLevel.volumeId] && windowLevel.histogram)
|
||||
.reduce((volumeHistograms, windowLevel) => {
|
||||
// If the histogram exists, add it to the volumeHistograms object
|
||||
if (windowLevel?.histogram) {
|
||||
volumeHistograms[windowLevel.volumeId] = windowLevel.histogram;
|
||||
}
|
||||
return volumeHistograms;
|
||||
}, {});
|
||||
|
||||
if (Object.keys(newVolumeHistograms).length) {
|
||||
setCachedHistograms(prev => ({ ...prev, ...newVolumeHistograms }));
|
||||
}
|
||||
}, [windowLevels, cachedHistograms]);
|
||||
|
||||
// Updates the histogram when the viewport index prop has changed
|
||||
useEffect(() => updateViewportHistograms(), [viewportId, updateViewportHistograms]);
|
||||
|
||||
// Listen to cornerstone events on "eventTarget" and at the document level
|
||||
useEffect(() => {
|
||||
eventTarget.addEventListener(Events.IMAGE_VOLUME_LOADING_COMPLETED, updateViewportHistograms);
|
||||
|
||||
document.addEventListener(Events.VOI_MODIFIED, debouncedHandleCornerstoneVOIModified, true);
|
||||
eventTarget.addEventListener(
|
||||
Events.IMAGE_VOLUME_LOADING_COMPLETED,
|
||||
handleImageVolumeLoadingCompleted
|
||||
);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
if (isLoading) {
|
||||
updateViewportHistograms();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
eventTarget.removeEventListener(
|
||||
Events.IMAGE_VOLUME_LOADING_COMPLETED,
|
||||
updateViewportHistograms
|
||||
);
|
||||
|
||||
document.removeEventListener(
|
||||
Events.VOI_MODIFIED,
|
||||
debouncedHandleCornerstoneVOIModified,
|
||||
true
|
||||
);
|
||||
eventTarget.removeEventListener(
|
||||
Events.IMAGE_VOLUME_LOADING_COMPLETED,
|
||||
handleImageVolumeLoadingCompleted
|
||||
);
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [updateViewportHistograms, debouncedHandleCornerstoneVOIModified]);
|
||||
}, [
|
||||
updateViewportHistograms,
|
||||
debouncedHandleCornerstoneVOIModified,
|
||||
handleImageVolumeLoadingCompleted,
|
||||
isLoading,
|
||||
]);
|
||||
|
||||
// Create a memoized version of displaySet IDs for comparison
|
||||
const displaySetIds = useMemo(() => {
|
||||
return displaySets?.map(ds => ds.displaySetInstanceUID).sort() || [];
|
||||
}, [displaySets]);
|
||||
|
||||
// Updates the viewport when the context of the viewport has changed. This is
|
||||
// necessary when moving across different stages because the viewport index
|
||||
// may not change but the volumes loaded on it may change.
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = cornerstoneViewportService.subscribe(
|
||||
cornerstoneViewportService.EVENTS.VIEWPORT_VOLUMES_CHANGED,
|
||||
@@ -344,10 +189,15 @@ const ViewportWindowLevel = ({
|
||||
}
|
||||
);
|
||||
|
||||
// Only update if displaySets actually changed and are loaded
|
||||
if (displaySetIds.length && !isLoading) {
|
||||
updateViewportHistograms();
|
||||
}
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [viewportId, cornerstoneViewportService, updateViewportHistograms]);
|
||||
}, [viewportId, cornerstoneViewportService, updateViewportHistograms, displaySetIds, isLoading]);
|
||||
|
||||
return (
|
||||
<PanelSection title="Window Level">
|
||||
|
||||
+13
-13
@@ -20,10 +20,6 @@ const workerFn = () => {
|
||||
const getViewportVolumeHistogram = async (viewport, volume, options?) => {
|
||||
workerManager.registerWorker('histogram-worker', workerFn, WorkerOptions);
|
||||
|
||||
if (!volume?.loadStatus.loaded) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const volumeImageData = viewport.getImageData(volume.volumeId);
|
||||
|
||||
if (!volumeImageData) {
|
||||
@@ -32,12 +28,15 @@ const getViewportVolumeHistogram = async (viewport, volume, options?) => {
|
||||
|
||||
let scalarData = volume.scalarData;
|
||||
|
||||
let prevTimePoint;
|
||||
if (volume.numTimePoints > 1) {
|
||||
prevTimePoint = volume.timePointIndex;
|
||||
const middleTimePoint = Math.round(volume.numTimePoints / 2);
|
||||
volume.timePointIndex = middleTimePoint;
|
||||
scalarData = volume.getScalarData(middleTimePoint);
|
||||
const targetTimePoint = volume.numTimePoints - 1; // or any other time point you need
|
||||
scalarData = volume.voxelManager.getTimePointScalarData(targetTimePoint);
|
||||
} else {
|
||||
scalarData = volume.voxelManager.getCompleteScalarDataArray();
|
||||
}
|
||||
|
||||
if (!scalarData?.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { dimensions, origin, direction, spacing } = volume;
|
||||
@@ -50,11 +49,12 @@ const getViewportVolumeHistogram = async (viewport, volume, options?) => {
|
||||
scalarData,
|
||||
});
|
||||
|
||||
// after we calculate the range let's reset the timePointIndex
|
||||
if (volume.numTimePoints > 1) {
|
||||
volume.timePointIndex = prevTimePoint;
|
||||
}
|
||||
const { minimum: min, maximum: max } = range;
|
||||
|
||||
if (min === Infinity || max === -Infinity) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const calcHistOptions = {
|
||||
numBins: 256,
|
||||
min: Math.max(min, options?.min ?? min),
|
||||
|
||||
Loaded 100 of 1335 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user