Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5210c80369 | ||
|
|
f57725ac8b | ||
|
|
138e0fb3bf | ||
|
|
be494a7376 | ||
|
|
ab23feefbb | ||
|
|
4c079044f6 | ||
|
|
81fcfc7370 | ||
|
|
6ec6446787 | ||
|
|
4b5a43641a | ||
|
|
2fb0b01fe2 | ||
|
|
3632e5d495 | ||
|
|
dfe566e2aa | ||
|
|
6c5ad9e98d | ||
|
|
aeb7d5edcc | ||
|
|
d5b89748c9 | ||
|
|
b161016edb | ||
|
|
2fff2d511f | ||
|
|
cc92f91c1f | ||
|
|
d2e777bafc | ||
|
|
c0c3ab307d | ||
|
|
5121913ee6 | ||
|
|
6ef2b19925 | ||
|
|
f2450be255 | ||
|
|
04ca32a93e | ||
|
|
c92de6c647 | ||
|
|
5b302843d4 | ||
|
|
7eb8c4c641 | ||
|
|
619f361510 | ||
|
|
330eeaa216 | ||
|
|
d627dee3b2 | ||
|
|
d8714ea9c4 | ||
|
|
3c05920705 | ||
|
|
c6629bda75 | ||
|
|
588bcc9d17 | ||
|
|
c31f8f9d65 | ||
|
|
c0280e28ca | ||
|
|
7c3ac387ef | ||
|
|
d6f79d9ddf | ||
|
|
9448993cf0 | ||
|
|
1c556b4cc3 | ||
|
|
94a9067fe3 | ||
|
|
cfba4df70e | ||
|
|
11c1d12872 | ||
|
|
91364db882 | ||
|
|
bea31d5fec | ||
|
|
48ecbc261a | ||
|
|
8bc467fb07 | ||
|
|
cffc44f0f4 | ||
|
|
ea0fc07e30 | ||
|
|
b8d241fa33 | ||
|
|
ccdfcd4a85 | ||
|
|
6cf72d1757 | ||
|
|
bb1cdbda3a | ||
|
|
fab122dd8f | ||
|
|
42803b0ae8 | ||
|
|
5a78da8ff6 | ||
|
|
0ef88e88eb | ||
|
|
a7b5f1847a | ||
|
|
5cb6e63b88 | ||
|
|
48eb4a9ec3 | ||
|
|
d9231fd431 | ||
|
|
e5b578bd8e | ||
|
|
f38979037d | ||
|
|
45ca88f5b8 | ||
|
|
a1dee16a3d | ||
|
|
6a37a2a661 | ||
|
|
a8caf4bba4 |
No files matched your search
+45
-22
@@ -13,11 +13,23 @@ version: 2.1
|
||||
##
|
||||
orbs:
|
||||
codecov: codecov/codecov@1.0.5
|
||||
cypress: cypress-io/cypress@1.13.0
|
||||
cypress: cypress-io/cypress@1.26.0
|
||||
executors:
|
||||
# Custom executor to override Cypress config
|
||||
deploy-to-prod-executor:
|
||||
docker:
|
||||
- image: 'cypress/browsers:node14.15.0-chrome86-ff82'
|
||||
environment:
|
||||
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
|
||||
chrome-and-pacs:
|
||||
docker:
|
||||
# Primary container image where all steps run.
|
||||
- image: 'cypress/browsers:node14.15.0-chrome86-ff82'
|
||||
- image: 'ohif/viewer-testdata:0.1-test'
|
||||
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: circleci/node:12.9.1
|
||||
- image: circleci/node:14.15.0
|
||||
environment:
|
||||
TERM: xterm # Enable colors in term
|
||||
QUICK_BUILD: true
|
||||
@@ -170,7 +182,7 @@ jobs:
|
||||
|
||||
DEPLOY_TO_DEV:
|
||||
docker:
|
||||
- image: circleci/node:12.9.1
|
||||
- image: circleci/node:14.15.0
|
||||
environment:
|
||||
TERM: xterm
|
||||
NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77
|
||||
@@ -185,7 +197,7 @@ jobs:
|
||||
|
||||
DEPLOY_TO_STAGING:
|
||||
docker:
|
||||
- image: circleci/node:12.9.1
|
||||
- image: circleci/node:14.15.0
|
||||
environment:
|
||||
TERM: xterm
|
||||
NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969
|
||||
@@ -200,7 +212,7 @@ jobs:
|
||||
|
||||
DEPLOY_TO_PRODUCTION:
|
||||
docker:
|
||||
- image: circleci/node:12.9.1
|
||||
- image: circleci/node:14.15.0
|
||||
environment:
|
||||
TERM: xterm
|
||||
NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0
|
||||
@@ -257,7 +269,12 @@ jobs:
|
||||
paths: .
|
||||
|
||||
DOCS_PUBLISH:
|
||||
<<: *defaults
|
||||
docker:
|
||||
# Gitbook fails due to graceful-fs updates above this node version :-(
|
||||
- image: circleci/node:12.9.1
|
||||
environment:
|
||||
TERM: xterm # Enable colors in term
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
@@ -317,16 +334,19 @@ workflows:
|
||||
# E2E: PWA
|
||||
- cypress/run:
|
||||
name: 'E2E: PWA'
|
||||
executor: cypress/browsers-chrome76
|
||||
executor: chrome-and-pacs
|
||||
browser: chrome
|
||||
pre-steps:
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||
add wait-on' # Use yarn latest
|
||||
- run: |
|
||||
# Clear yarn cache; update to latest
|
||||
rm -rf ~/.yarn
|
||||
npm i -g yarn
|
||||
yarn -v
|
||||
yarn: true
|
||||
record: false
|
||||
store_artifacts: false
|
||||
record: true
|
||||
store_artifacts: true
|
||||
working_directory: platform/viewer
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build
|
||||
start: yarn run test:e2e:serve
|
||||
spec: 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'
|
||||
wait-on: 'http://localhost:3000'
|
||||
@@ -337,21 +357,22 @@ workflows:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
- store_test_results:
|
||||
path: platform/viewer/cypress/results
|
||||
requires:
|
||||
- UNIT_TESTS
|
||||
# E2E: script-tag
|
||||
- cypress/run:
|
||||
name: 'E2E: Script Tag'
|
||||
executor: cypress/browsers-chrome76
|
||||
executor: chrome-and-pacs
|
||||
browser: chrome
|
||||
pre-steps:
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||
add wait-on' # Use yarn latest
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v' # Use yarn latest
|
||||
yarn: true
|
||||
record: false
|
||||
store_artifacts: false
|
||||
record: true
|
||||
store_artifacts: true
|
||||
working_directory: platform/viewer
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build:package
|
||||
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build:package
|
||||
start: yarn run test:e2e:serve
|
||||
spec: 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'
|
||||
wait-on: 'http://localhost:3000'
|
||||
@@ -362,6 +383,8 @@ workflows:
|
||||
path: platform/viewer/cypress/screenshots
|
||||
- store_artifacts:
|
||||
path: platform/viewer/cypress/videos
|
||||
- store_test_results:
|
||||
path: platform/viewer/cypress/results
|
||||
requires:
|
||||
- UNIT_TESTS
|
||||
|
||||
@@ -372,7 +395,7 @@ workflows:
|
||||
# Update hub.docker.org
|
||||
- cypress/run:
|
||||
name: 'Generate Percy Snapshots'
|
||||
executor: cypress/browsers-chrome76
|
||||
executor: chrome-and-pacs
|
||||
browser: chrome
|
||||
pre-steps:
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||
@@ -380,7 +403,7 @@ workflows:
|
||||
yarn: true
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build
|
||||
# start server --> verify running --> percy + chrome + cypress
|
||||
command: yarn run test:e2e:dist
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
@@ -449,7 +472,7 @@ workflows:
|
||||
# and record a Cypress dashboard test run
|
||||
- cypress/run:
|
||||
name: 'Generate Percy Snapshots'
|
||||
executor: cypress/browsers-chrome76
|
||||
executor: chrome-and-pacs
|
||||
browser: chrome
|
||||
pre-steps:
|
||||
- run: 'rm -rf ~/.yarn && npm i -g yarn && yarn -v && yarn global
|
||||
@@ -457,7 +480,7 @@ workflows:
|
||||
yarn: true
|
||||
store_artifacts: false
|
||||
working_directory: platform/viewer
|
||||
build: npx cross-env QUICK_BUILD=true yarn run build
|
||||
build: npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js yarn run build
|
||||
# start server --> verify running --> percy + chrome + cypress
|
||||
command: yarn run test:e2e:dist
|
||||
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
|
||||
|
||||
@@ -28,6 +28,12 @@ if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
|
||||
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
|
||||
fi
|
||||
|
||||
if [ -n "${PORT}" ]
|
||||
then
|
||||
echo "Changing port to ${PORT}..."
|
||||
sed -i -e "s/listen 80/listen ${PORT}/g" /etc/nginx/conf.d/default.conf
|
||||
fi
|
||||
|
||||
echo "Starting Nginx to serve the OHIF Viewer..."
|
||||
|
||||
exec "$@"
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
# https://github.com/apps/stale
|
||||
#
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 21
|
||||
daysUntilStale: 180
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
daysUntilClose: 9000
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- 'Story :raised_hands:'
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.9.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.5...@ohif/extension-cornerstone@2.9.6) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.4...@ohif/extension-cornerstone@2.9.5) (2020-09-29)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.3...@ohif/extension-cornerstone@2.9.4) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.2...@ohif/extension-cornerstone@2.9.3) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.1...@ohif/extension-cornerstone@2.9.2) (2020-08-20)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "2.9.2",
|
||||
"version": "2.9.6",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -36,7 +36,7 @@
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
@@ -51,6 +51,6 @@
|
||||
"classnames": "^2.2.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-cornerstone-viewport": "4.0.2"
|
||||
"react-cornerstone-viewport": "4.0.4"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import init from './init.js';
|
||||
import commandsModule from './commandsModule.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./OHIFCornerstoneViewport');
|
||||
@@ -24,6 +25,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'cornerstone',
|
||||
version,
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.5...@ohif/extension-debugging@0.1.6) (2020-10-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-debugging
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.4...@ohif/extension-debugging@0.1.5) (2020-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-debugging
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.3...@ohif/extension-debugging@0.1.4) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-debugging
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 0.1.3 (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-debugging
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.1...@ohif/extension-dicom-p10-downloader@0.1.2) (2020-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-p10-downloader",
|
||||
"version": "0.1.2",
|
||||
"description": "OHIF extension for downloading DICOM P10 files",
|
||||
"name": "@ohif/extension-debugging",
|
||||
"version": "0.1.6",
|
||||
"description": "OHIF extension for debugging.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"detect-browser": "5.1.1",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"jszip": "^3.2.2"
|
||||
@@ -0,0 +1,14 @@
|
||||
.debugReportModalHeader {
|
||||
padding: 10px 0 10px;
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.debug-report-modal-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.debug-report-modal-buttons-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
import React from 'react';
|
||||
import { detect } from 'detect-browser';
|
||||
import './DebugReportModal.css';
|
||||
import { ToolbarButton } from '@ohif/ui';
|
||||
|
||||
const DubugReportModal = ({
|
||||
viewports,
|
||||
studies,
|
||||
servers,
|
||||
extensionManager,
|
||||
mailTo,
|
||||
debugModalMessage,
|
||||
}) => {
|
||||
const copyDebugDataToClipboard = () => {
|
||||
const body = getEmailBody();
|
||||
|
||||
const textArea = document.createElement('textarea');
|
||||
|
||||
textArea.value = body;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
};
|
||||
|
||||
const getEmailBody = () => {
|
||||
let body = `Enter the description of your problem here: \n\n\n`;
|
||||
|
||||
body += `============= SESSION INFO =============\n\n`;
|
||||
|
||||
// App version
|
||||
|
||||
body += '== App ==\n';
|
||||
body += `version\t${window.version}\n\n`;
|
||||
|
||||
// Extensions Versions
|
||||
|
||||
body += '== Extensions Versions ==\n';
|
||||
|
||||
const { registeredExtensionVesions } = extensionManager;
|
||||
|
||||
Object.keys(registeredExtensionVesions).forEach(extensionId => {
|
||||
const version = registeredExtensionVesions[extensionId];
|
||||
|
||||
body += `${extensionId}\t${version}\n`;
|
||||
});
|
||||
|
||||
body += '\n';
|
||||
|
||||
// Browser Info
|
||||
|
||||
const browser = detect();
|
||||
|
||||
const { name, os, type, version } = browser;
|
||||
|
||||
body += '== Browser Info ==\n';
|
||||
body += `name\t ${name}\n`;
|
||||
body += `os\t ${os}\n`;
|
||||
body += `type\t ${type}\n`;
|
||||
body += `version\t ${version}\n\n`;
|
||||
|
||||
// Study URL
|
||||
body += '== URL ==\n';
|
||||
body += `URL\t ${window.location.href}\n\n`;
|
||||
|
||||
// Layout
|
||||
const { numRows, numColumns, viewportSpecificData } = viewports;
|
||||
|
||||
body += '== Viewport Layout ==\n';
|
||||
body += `Rows\t${numRows}\n`;
|
||||
body += `Columns\t${numColumns}\n\n`;
|
||||
body += '== Viewports ==\n';
|
||||
|
||||
Object.keys(viewportSpecificData).forEach(viewportIndex => {
|
||||
const vsd = viewportSpecificData[viewportIndex];
|
||||
|
||||
const [row, column] = _viewportIndexToViewportPosition(
|
||||
viewportIndex,
|
||||
numColumns
|
||||
);
|
||||
|
||||
body += `[${row},${column}]\t${vsd.SeriesInstanceUID}\n`;
|
||||
});
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
const mailToFunction = () => {
|
||||
const StudyInstanceUID = Object.keys(studies.studyData)[0];
|
||||
|
||||
const subject = encodeURI(`Issue with Study: ${StudyInstanceUID}`);
|
||||
|
||||
let body = getEmailBody();
|
||||
|
||||
// TODO Text dump of rest of stuff.
|
||||
|
||||
body = encodeURI(body);
|
||||
|
||||
window.location.href = `mailto:${mailTo}?subject=${subject}&body=${body}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="debug-report-modal-container">
|
||||
{debugModalMessage ? <p>{debugModalMessage}</p> : null}
|
||||
<div className="debug-report-modal-buttons-container">
|
||||
{mailTo ? (
|
||||
<div>
|
||||
<ToolbarButton
|
||||
label={'Send Bug Report'}
|
||||
onClick={mailToFunction}
|
||||
icon={'envelope-square'}
|
||||
isActive={false}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<ToolbarButton
|
||||
label={'Copy To Clipboard'}
|
||||
onClick={copyDebugDataToClipboard}
|
||||
icon={'clipboard'}
|
||||
isActive={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<table>
|
||||
{getAppVersion()}
|
||||
{getExtensionVersions(extensionManager)}
|
||||
{getBrowserInfo()}
|
||||
{getCurrentStudyUrl()}
|
||||
{getLayout(viewports)}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getAppVersion = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<tr>
|
||||
<th className="debugReportModalHeader">App</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{window.version}</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getCurrentStudyUrl = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<tr>
|
||||
<th className="debugReportModalHeader">URL</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>{window.location.href}</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getExtensionVersions = extensionManager => {
|
||||
const { registeredExtensionVesions } = extensionManager;
|
||||
|
||||
const lineItems = Object.keys(registeredExtensionVesions).map(extensionId => {
|
||||
const version = registeredExtensionVesions[extensionId];
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{extensionId}</td>
|
||||
<td>{version}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<th className="debugReportModalHeader">Extensions</th>
|
||||
{lineItems}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getLayout = viewports => {
|
||||
const { numRows, numColumns } = viewports;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<tr>
|
||||
<th className="debugReportModalHeader">Viewports</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Layout</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rows</td>
|
||||
<td>{numRows}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Columns</td>
|
||||
<td>{numColumns}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>SeriesInstanceUIDs</th>
|
||||
</tr>
|
||||
{getSeriesInstanceUIDsPerRow(viewports)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getBrowserInfo = () => {
|
||||
const browser = detect();
|
||||
|
||||
const { name, os, type, version } = browser;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<tr>
|
||||
<th className="debugReportModalHeader">Platform</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>{name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>os</td>
|
||||
<td>{os}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>type</td>
|
||||
<td>{type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>version</td>
|
||||
<td>{version}</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getSeriesInstanceUIDsPerRow = viewports => {
|
||||
const { viewportSpecificData, numColumns } = viewports;
|
||||
|
||||
// NOTE viewportSpecificData is actually an object with numerical keys.
|
||||
return Object.keys(viewportSpecificData).map(viewportIndex => {
|
||||
const vsd = viewportSpecificData[viewportIndex];
|
||||
|
||||
const [row, column] = _viewportIndexToViewportPosition(
|
||||
viewportIndex,
|
||||
numColumns
|
||||
);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{`[${row},${column}]`}</td>
|
||||
<td>{vsd.SeriesInstanceUID}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const _viewportIndexToViewportPosition = (viewportIndex, numColumns) => {
|
||||
const row = Math.floor(viewportIndex / numColumns);
|
||||
const column = viewportIndex % numColumns;
|
||||
|
||||
return [row, column];
|
||||
};
|
||||
|
||||
export default DubugReportModal;
|
||||
+30
-1
@@ -8,12 +8,16 @@ import {
|
||||
getSOPInstanceReferencesFromViewports,
|
||||
} from './utils';
|
||||
import _downloadAndZip, { downloadInstances } from './downloadAndZip';
|
||||
import DebugReportModal from './DebugReportModal';
|
||||
import React from 'react';
|
||||
|
||||
import state from './state';
|
||||
|
||||
const {
|
||||
utils: { Queue },
|
||||
} = OHIF;
|
||||
|
||||
export function getCommands(context) {
|
||||
export function getCommands(context, servicesManager, extensionManager) {
|
||||
const queue = new Queue(1);
|
||||
const actions = {
|
||||
/**
|
||||
@@ -87,9 +91,34 @@ export function getCommands(context) {
|
||||
serverConfig
|
||||
);
|
||||
},
|
||||
openDebugInfoModal({ viewports, studies, servers }) {
|
||||
const { UIModalService } = servicesManager.services;
|
||||
|
||||
const WrappedDebugReportModal = function() {
|
||||
return (
|
||||
<DebugReportModal
|
||||
viewports={viewports}
|
||||
studies={studies}
|
||||
servers={servers}
|
||||
extensionManager={extensionManager}
|
||||
mailTo={state.mailTo}
|
||||
debugModalMessage={state.debugModalMessage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
UIModalService.show({
|
||||
content: WrappedDebugReportModal,
|
||||
title: `Debugging Information`,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
openDebugInfoModal: {
|
||||
commandFn: actions.openDebugInfoModal,
|
||||
storeContexts: ['viewports', 'servers', 'studies'],
|
||||
},
|
||||
downloadAndZip: {
|
||||
commandFn: queue.bindSafe(actions.downloadAndZip, error),
|
||||
storeContexts: ['servers'],
|
||||
File renamed without changes.
@@ -0,0 +1,61 @@
|
||||
import { getDicomWebClientFromConfig } from './utils';
|
||||
import { getCommands } from './commandsModule';
|
||||
import { version } from '../package.json';
|
||||
import toolbarModule from './toolbarModule';
|
||||
import state from './state';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
|
||||
const sharedContext = {
|
||||
dicomWebClient: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'dicom-p10-downloader',
|
||||
version,
|
||||
|
||||
/**
|
||||
* LIFECYCLE HOOKS
|
||||
*/
|
||||
|
||||
preRegistration({ appConfig, configuration }) {
|
||||
const dicomWebClient = getDicomWebClientFromConfig(appConfig);
|
||||
if (dicomWebClient) {
|
||||
sharedContext.dicomWebClient = dicomWebClient;
|
||||
}
|
||||
|
||||
if (configuration) {
|
||||
if (configuration.mailTo) {
|
||||
state.mailTo = configuration.mailTo;
|
||||
}
|
||||
|
||||
if (configuration.debugModalMessage) {
|
||||
state.debugModalMessage = configuration.debugModalMessage;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* MODULE GETTERS
|
||||
*/
|
||||
|
||||
getCommandsModule({ servicesManager, extensionManager }) {
|
||||
return getCommands(sharedContext, servicesManager, extensionManager);
|
||||
},
|
||||
|
||||
getToolbarModule() {
|
||||
return toolbarModule;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
const state = { mailTo: undefined, message: undefined };
|
||||
|
||||
export default state;
|
||||
@@ -0,0 +1,20 @@
|
||||
const TOOLBAR_BUTTON_TYPES = {
|
||||
COMMAND: 'command',
|
||||
};
|
||||
|
||||
const definitions = [
|
||||
{
|
||||
id: 'Debug Info',
|
||||
label: 'Debug Info',
|
||||
icon: 'cog',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
commandName: 'openDebugInfoModal',
|
||||
context: 'VIEWER',
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
definitions,
|
||||
defaultContext: 'VIEWER',
|
||||
};
|
||||
File renamed without changes.
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.8...@ohif/extension-dicom-html@1.2.9) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.7...@ohif/extension-dicom-html@1.2.8) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.6...@ohif/extension-dicom-html@1.2.7) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.5...@ohif/extension-dicom-html@1.2.6) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.4...@ohif/extension-dicom-html@1.2.5) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-html",
|
||||
"version": "1.2.5",
|
||||
"version": "1.2.9",
|
||||
"description": "OHIF extension for rendering structured reports to HTML",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -22,11 +22,13 @@ const OHIFDicomHtmlSopClassHandler = {
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
const metadata = instance.getData().metadata;
|
||||
const {
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
} = instance._instance.metadata;
|
||||
} = metadata;
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
@@ -37,6 +39,8 @@ const OHIFDicomHtmlSopClassHandler = {
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
SeriesDescription,
|
||||
metadata,
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./OHIFDicomHtmlViewport');
|
||||
@@ -18,6 +19,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'html',
|
||||
version,
|
||||
|
||||
getViewportModule() {
|
||||
return OHIFDicomHtmlViewport;
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.2...@ohif/extension-dicom-microscopy@0.51.3) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.1...@ohif/extension-dicom-microscopy@0.51.2) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.51.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.0...@ohif/extension-dicom-microscopy@0.51.1) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-microscopy",
|
||||
"version": "0.51.1",
|
||||
"version": "0.51.3",
|
||||
"description": "OHIF extension for Dicom Microscopy",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -12,11 +12,13 @@ const DicomMicroscopySopClassHandler = {
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
const metadata = instance.getData().metadata;
|
||||
const {
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
ContentDate,
|
||||
ContentTime,
|
||||
SeriesNumber,
|
||||
} = instance._instance.metadata;
|
||||
} = metadata;
|
||||
|
||||
// Note: We are passing the dicomweb client into each viewport!
|
||||
|
||||
@@ -28,9 +30,11 @@ const DicomMicroscopySopClassHandler = {
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
SeriesDescription,
|
||||
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
|
||||
SeriesTime: ContentTime,
|
||||
SeriesNumber,
|
||||
metadata,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./DicomMicroscopyViewport');
|
||||
@@ -18,6 +19,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'microscopy',
|
||||
version,
|
||||
|
||||
getViewportModule() {
|
||||
return DicomMicroscopyViewport;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { getDicomWebClientFromConfig } from './utils';
|
||||
import { getCommands } from './commandsModule';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
|
||||
const sharedContext = {
|
||||
dicomWebClient: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'dicom-p10-downloader',
|
||||
|
||||
/**
|
||||
* LIFECYCLE HOOKS
|
||||
*/
|
||||
|
||||
preRegistration({ appConfig }) {
|
||||
const dicomWebClient = getDicomWebClientFromConfig(appConfig);
|
||||
if (dicomWebClient) {
|
||||
sharedContext.dicomWebClient = dicomWebClient;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* MODULE GETTERS
|
||||
*/
|
||||
|
||||
getCommandsModule() {
|
||||
return getCommands(sharedContext);
|
||||
},
|
||||
};
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.0.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.5...@ohif/extension-dicom-pdf@1.0.6) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.4...@ohif/extension-dicom-pdf@1.0.5) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.3...@ohif/extension-dicom-pdf@1.0.4) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-pdf",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.6",
|
||||
"description": "OHIF extension for Dicom PDF",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -12,11 +12,13 @@ const OHIFDicomPDFSopClassHandler = {
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
const metadata = instance.getData().metadata;
|
||||
const {
|
||||
ContentDate,
|
||||
ContentTime,
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
} = instance._instance.metadata;
|
||||
} = metadata;
|
||||
|
||||
return {
|
||||
plugin: 'pdf',
|
||||
@@ -27,9 +29,11 @@ const OHIFDicomPDFSopClassHandler = {
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
SeriesDescription,
|
||||
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
|
||||
SeriesTime: ContentTime,
|
||||
SeriesNumber,
|
||||
metadata,
|
||||
authorizationHeaders: authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./ConnectedOHIFDicomPDFViewer');
|
||||
@@ -18,6 +19,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'pdf',
|
||||
version,
|
||||
getViewportModule() {
|
||||
return ConnectedOHIFDicomPDFViewer;
|
||||
},
|
||||
|
||||
@@ -3,6 +3,70 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.8...@ohif/extension-dicom-rt@0.4.9) (2020-10-12)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.7...@ohif/extension-dicom-rt@0.4.8) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.6...@ohif/extension-dicom-rt@0.4.7) (2020-10-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.5...@ohif/extension-dicom-rt@0.4.6) (2020-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.4...@ohif/extension-dicom-rt@0.4.5) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.3...@ohif/extension-dicom-rt@0.4.4) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.2...@ohif/extension-dicom-rt@0.4.3) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.1...@ohif/extension-dicom-rt@0.4.2) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.0...@ohif/extension-dicom-rt@0.4.1) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-rt",
|
||||
"version": "0.4.1",
|
||||
"version": "0.4.9",
|
||||
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -31,10 +31,12 @@
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"gl-matrix": "^3.3.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
"react-dom": "^16.8.6",
|
||||
"react-tooltip": "4.2.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
|
||||
@@ -56,6 +56,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
SeriesDescription,
|
||||
metadata,
|
||||
};
|
||||
|
||||
if (!metadata.ReferencedSeriesSequence) {
|
||||
|
||||
@@ -12,6 +12,9 @@ import StructureSetItem from '../StructureSetItem/StructureSetItem';
|
||||
import RTPanelSettings from '../RTSettings/RTSettings';
|
||||
import PanelSection from '../PanelSection/PanelSection';
|
||||
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator';
|
||||
import TOOL_NAMES from '../../utils/toolNames';
|
||||
|
||||
const { RTSTRUCT_DISPLAY_TOOL } = TOOL_NAMES;
|
||||
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
@@ -144,13 +147,16 @@ const RTPanel = ({
|
||||
if (isCornerstone()) {
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
const element = enabledElements[activeIndex].element;
|
||||
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
||||
const stackToolState = cornerstoneTools.getToolState(
|
||||
element,
|
||||
'stack'
|
||||
);
|
||||
|
||||
if (!toolState) {
|
||||
if (!stackToolState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageIds = toolState.data[0].imageIds;
|
||||
const imageIds = stackToolState.data[0].imageIds;
|
||||
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
|
||||
@@ -159,6 +165,18 @@ const RTPanel = ({
|
||||
imageIds
|
||||
);
|
||||
|
||||
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||
const imageIdSpecificToolState = toolState[imageId];
|
||||
|
||||
const rtstructData =
|
||||
imageIdSpecificToolState[RTSTRUCT_DISPLAY_TOOL];
|
||||
|
||||
const specificData = rtstructData.data.find(
|
||||
rtData => rtData.ROINumber === ROINumber
|
||||
);
|
||||
|
||||
specificData.highlight = true;
|
||||
|
||||
const frameIndex = imageIds.indexOf(imageId);
|
||||
const SOPInstanceUID = cornerstone.metaData.get(
|
||||
'SOPInstanceUID',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TableListItem, Icon, Tooltip, OverlayTrigger } from '@ohif/ui';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
import './StructureSetItem.css';
|
||||
|
||||
@@ -68,7 +69,18 @@ const StructureSetItem = ({
|
||||
>
|
||||
<div>
|
||||
<div className="item-label" style={{ marginBottom: 4 }}>
|
||||
<span>{label}</span>
|
||||
<a data-tip data-for={`StructureHover${index}`}>
|
||||
<span>{label}</span>
|
||||
</a>
|
||||
<ReactTooltip
|
||||
id={`StructureHover${index}`}
|
||||
delayShow={250}
|
||||
place="right"
|
||||
border={true}
|
||||
type="light"
|
||||
>
|
||||
<span>{label}</span>
|
||||
</ReactTooltip>
|
||||
{!isDisabled && (
|
||||
<Icon
|
||||
className={`eye-icon ${isVisible && '--visible'}`}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import init from './init.js';
|
||||
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
|
||||
import id from './id.js';
|
||||
import RTPanel from './components/RTPanel/RTPanel';
|
||||
import { version } from '../package.json';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id,
|
||||
version,
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@ import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import transformPointsToImagePlane from './utils/transformPointsToImagePlane';
|
||||
import TOOL_NAMES from './utils/toolNames';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
|
||||
const dicomlab2RGB = dcmjs.data.Colors.dicomlab2RGB;
|
||||
const globalImageIdSpecificToolStateManager =
|
||||
@@ -86,7 +87,14 @@ export default async function loadRTStruct(
|
||||
ContourGeometricType,
|
||||
} = ContourSequenceArray[c];
|
||||
|
||||
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
|
||||
const sopInstanceUID = ContourImageSequence
|
||||
? ContourImageSequence.ReferencedSOPInstanceUID
|
||||
: _getClosestSOPInstanceUID(
|
||||
ContourData,
|
||||
ContourGeometricType,
|
||||
NumberOfContourPoints,
|
||||
imageIdSopInstanceUidPairs
|
||||
);
|
||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
||||
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
|
||||
toolState,
|
||||
@@ -324,3 +332,78 @@ function _getImageIdSopInstanceUidPairsForDisplaySet(
|
||||
function _toArray(objOrArray) {
|
||||
return Array.isArray(objOrArray) ? objOrArray : [objOrArray];
|
||||
}
|
||||
|
||||
function _getClosestSOPInstanceUID(
|
||||
ContourData,
|
||||
ContourGeometricType,
|
||||
NumberOfContourPoints,
|
||||
imageIdSopInstanceUidPairs
|
||||
) {
|
||||
const closest = {
|
||||
distance: Infinity,
|
||||
sopInstanceUID: null,
|
||||
};
|
||||
|
||||
let point;
|
||||
|
||||
switch (ContourGeometricType) {
|
||||
case 'POINT':
|
||||
point = ContourData;
|
||||
break;
|
||||
case 'CLOSED_PLANAR':
|
||||
case 'OPEN_PLANAR':
|
||||
// These are defined as planar, so get the of the region to get the
|
||||
// Best mapping to a plane even if its potentially off center.
|
||||
|
||||
point = [0, 0, 0];
|
||||
for (let p = 0; p < NumberOfContourPoints * 3; p += 3) {
|
||||
point[0] += ContourData[p];
|
||||
point[1] += ContourData[p + 1];
|
||||
point[2] += ContourData[p + 2];
|
||||
}
|
||||
|
||||
point[0] /= NumberOfContourPoints;
|
||||
point[1] /= NumberOfContourPoints;
|
||||
point[2] /= NumberOfContourPoints;
|
||||
}
|
||||
|
||||
imageIdSopInstanceUidPairs.forEach(pair => {
|
||||
const { imageId } = pair;
|
||||
|
||||
const imagePlaneModule = cornerstone.metaData.get(
|
||||
'imagePlaneModule',
|
||||
imageId
|
||||
);
|
||||
|
||||
const distance = distanceFromPointToPlane(point, imagePlaneModule);
|
||||
|
||||
if (distance < closest.distance) {
|
||||
closest.distance = distance;
|
||||
closest.sopInstanceUID = pair.sopInstanceUID;
|
||||
}
|
||||
});
|
||||
|
||||
return closest.sopInstanceUID;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number[3]} P - The point
|
||||
* @param {object} imagePlaneModule The cornerstone metadata object for the imagePlane
|
||||
*/
|
||||
function distanceFromPointToPlane(P, imagePlaneModule) {
|
||||
const {
|
||||
rowCosines,
|
||||
columnCosines,
|
||||
imagePositionPatient: Q,
|
||||
} = imagePlaneModule;
|
||||
|
||||
let N = [];
|
||||
vec3.cross(N, rowCosines, columnCosines);
|
||||
|
||||
const [A, B, C] = N;
|
||||
|
||||
const D = -A * Q[0] - B * Q[1] - C * Q[2];
|
||||
|
||||
return Math.abs(A * P[0] + B * P[1] + C * P[2] + D); // Denominator is sqrt(A**2 + B**2 + C**2) which is 1 as its a normal vector
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
|
||||
|
||||
import TOOL_NAMES from '../utils/toolNames';
|
||||
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
|
||||
|
||||
// Cornerstone 3rd party dev kit imports
|
||||
const draw = importInternal('drawing/draw');
|
||||
@@ -37,11 +37,17 @@ export default class RTStructDisplayTool extends BaseTool {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lineWidth, opacity } = rtstructModule.configuration;
|
||||
const {
|
||||
lineWidth,
|
||||
opacity,
|
||||
highlightOpacity,
|
||||
} = rtstructModule.configuration;
|
||||
|
||||
// We have tool data for this element - iterate over each one and draw it
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
|
||||
let crossHairCenter;
|
||||
|
||||
for (let i = 0; i < toolState.data.length; i++) {
|
||||
const data = toolState.data[i];
|
||||
|
||||
@@ -75,27 +81,51 @@ export default class RTStructDisplayTool extends BaseTool {
|
||||
colorArray[2]
|
||||
},${opacity})`;
|
||||
|
||||
let highlight = data.highlight;
|
||||
const options = { color, lineWidth };
|
||||
|
||||
if (highlight) {
|
||||
crossHairCenter = { x: 0, y: 0 };
|
||||
|
||||
points.forEach(point => {
|
||||
crossHairCenter.x += point.x;
|
||||
crossHairCenter.y += point.y;
|
||||
});
|
||||
|
||||
crossHairCenter.x /= points.length;
|
||||
crossHairCenter.y /= points.length;
|
||||
|
||||
// TODO: Disabling hightlight for now, it'd be good to bring it back
|
||||
// when we have a good way of doing this for SEG.
|
||||
|
||||
// options.fillStyle = color = `rgba(${colorArray[0]},${colorArray[1]},${
|
||||
// colorArray[2]
|
||||
// },${highlightOpacity})`;
|
||||
|
||||
// Draw highlight lines.
|
||||
|
||||
delete data.highlight; // Don't highlight on next render.
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'CLOSED_PLANAR':
|
||||
this._renderClosedPlanar(context, eventData.element, points, {
|
||||
color,
|
||||
lineWidth,
|
||||
});
|
||||
this._renderClosedPlanar(context, eventData.element, points, options);
|
||||
break;
|
||||
case 'POINT':
|
||||
this._renderPoint(context, eventData.element, points, {
|
||||
color,
|
||||
lineWidth,
|
||||
});
|
||||
this._renderPoint(context, eventData.element, points, options);
|
||||
break;
|
||||
case 'OPEN_PLANAR':
|
||||
this._renderOpenPlanar(context, eventData.element, points, {
|
||||
color,
|
||||
lineWidth,
|
||||
});
|
||||
this._renderOpenPlanar(context, eventData.element, points, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (crossHairCenter) {
|
||||
drawCanvasCrosshairs(eventData, crossHairCenter, {
|
||||
color: toolColors.getActiveColor(),
|
||||
lineWidth: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_renderClosedPlanar(context, element, points, options) {
|
||||
|
||||
@@ -13,7 +13,8 @@ function refreshViewport() {
|
||||
|
||||
const configuration = {
|
||||
lineWidth: 3,
|
||||
opacity: 1.0,
|
||||
opacity: 0.75,
|
||||
highlightOpacity: 0.5,
|
||||
};
|
||||
|
||||
const state = {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { importInternal } = cornerstoneTools;
|
||||
const draw = importInternal('drawing/draw');
|
||||
const drawLine = importInternal('drawing/drawLine');
|
||||
const getNewContext = importInternal('drawing/getNewContext');
|
||||
|
||||
export default function _drawCanvasCrosshairs(eventData, center, options) {
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
const { element } = eventData;
|
||||
|
||||
const centerCanvas = cornerstone.pixelToCanvas(element, center);
|
||||
|
||||
const { clientWidth: width, clientHeight: height } = element;
|
||||
|
||||
const offset = 10;
|
||||
|
||||
draw(context, context => {
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x + offset, y: centerCanvas.y },
|
||||
{ x: width, y: centerCanvas.y },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x - offset, y: centerCanvas.y },
|
||||
{ x: 0, y: centerCanvas.y },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x, y: centerCanvas.y + offset },
|
||||
{ x: centerCanvas.x, y: height },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x, y: centerCanvas.y - offset },
|
||||
{ x: centerCanvas.x, y: 0 },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.6...@ohif/extension-dicom-segmentation@0.4.7) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.5...@ohif/extension-dicom-segmentation@0.4.6) (2020-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.4...@ohif/extension-dicom-segmentation@0.4.5) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.3...@ohif/extension-dicom-segmentation@0.4.4) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.2...@ohif/extension-dicom-segmentation@0.4.3) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.1...@ohif/extension-dicom-segmentation@0.4.2) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.0...@ohif/extension-dicom-segmentation@0.4.1) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-segmentation",
|
||||
"version": "0.4.1",
|
||||
"version": "0.4.7",
|
||||
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -31,7 +31,7 @@
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
@@ -39,6 +39,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"gl-matrix": "^3.3.0",
|
||||
"react-select": "^3.0.8"
|
||||
"react-select": "^3.0.8",
|
||||
"react-tooltip": "4.2.10"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TableListItem, Icon } from '@ohif/ui';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
import './SegmentItem.css';
|
||||
|
||||
@@ -17,7 +18,15 @@ ColoredCircle.propTypes = {
|
||||
color: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true, onVisibilityChange }) => {
|
||||
const SegmentItem = ({
|
||||
index,
|
||||
label,
|
||||
onClick,
|
||||
itemClass,
|
||||
color,
|
||||
visible = true,
|
||||
onVisibilityChange,
|
||||
}) => {
|
||||
const [isVisible, setIsVisible] = useState(visible);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -37,7 +46,18 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
|
||||
>
|
||||
<div>
|
||||
<div className="segment-label" style={{ marginBottom: 4 }}>
|
||||
<span>{label}</span>
|
||||
<a data-tip data-for={`SegmentHover${index}`}>
|
||||
<span>{label}</span>
|
||||
</a>
|
||||
<ReactTooltip
|
||||
id={`SegmentHover${index}`}
|
||||
delayShow={250}
|
||||
place="right"
|
||||
border={true}
|
||||
type="light"
|
||||
>
|
||||
<span>{label}</span>
|
||||
</ReactTooltip>
|
||||
<Icon
|
||||
className={`eye-icon ${isVisible && '--visible'}`}
|
||||
name={isVisible ? 'eye' : 'eye-closed'}
|
||||
@@ -61,7 +81,7 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
|
||||
<span style={{ marginRight: '4px' }}>
|
||||
<Icon name="edit" width="14px" height="14px" />
|
||||
</span>
|
||||
Relabel
|
||||
Relabel
|
||||
</button>
|
||||
<button
|
||||
className="btnAction"
|
||||
@@ -70,7 +90,7 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
|
||||
<span style={{ marginRight: '4px' }}>
|
||||
<Icon name="edit" width="14px" height="14px" />
|
||||
</span>
|
||||
Description
|
||||
Description
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -90,7 +110,7 @@ SegmentItem.propTypes = {
|
||||
|
||||
SegmentItem.defaultProps = {
|
||||
itemClass: '',
|
||||
onClick: () => { }
|
||||
onClick: () => {},
|
||||
};
|
||||
|
||||
export default SegmentItem;
|
||||
@@ -5,6 +5,7 @@ import cornerstone from 'cornerstone-core';
|
||||
import moment from 'moment';
|
||||
import { utils, log } from '@ohif/core';
|
||||
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
|
||||
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
|
||||
|
||||
import setActiveLabelmap from '../../utils/setActiveLabelMap';
|
||||
import refreshViewports from '../../utils/refreshViewports';
|
||||
@@ -334,6 +335,12 @@ const SegmentationPanel = ({
|
||||
imageId
|
||||
);
|
||||
|
||||
DICOMSegTempCrosshairsTool.addCrosshair(
|
||||
element,
|
||||
imageId,
|
||||
segmentNumber
|
||||
);
|
||||
|
||||
onSegmentItemClick({
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID,
|
||||
|
||||
@@ -28,12 +28,12 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
const {
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
SeriesDescription,
|
||||
FrameOfReferenceUID,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
SeriesNumber,
|
||||
} = metadata;
|
||||
|
||||
const segDisplaySet = {
|
||||
@@ -55,6 +55,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
SeriesDescription,
|
||||
metadata,
|
||||
};
|
||||
|
||||
segDisplaySet.getSourceDisplaySet = function(studies) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import setActiveLabelmap from './utils/setActiveLabelMap';
|
||||
import { classes } from '@ohif/core';
|
||||
|
||||
const { ImageSet } = classes;
|
||||
|
||||
export default function getSourceDisplaySet(studies, segDisplaySet) {
|
||||
const referencedDisplaySet = _getReferencedDisplaySet(segDisplaySet, studies);
|
||||
@@ -19,15 +22,43 @@ const _getReferencedDisplaySet = (segDisplaySet, studies) => {
|
||||
ds => ds.displaySetInstanceUID !== segDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
const ReferencedSeriesSequence = Array.isArray(
|
||||
segDisplaySet.metadata.ReferencedSeriesSequence
|
||||
)
|
||||
? segDisplaySet.metadata.ReferencedSeriesSequence
|
||||
: [segDisplaySet.metadata.ReferencedSeriesSequence];
|
||||
const { metadata } = segDisplaySet;
|
||||
|
||||
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||
);
|
||||
let referencedSeriesInstanceUIDs;
|
||||
|
||||
if (metadata.ReferencedSeriesSequence) {
|
||||
const ReferencedSeriesSequence = _toArray(
|
||||
metadata.ReferencedSeriesSequence
|
||||
);
|
||||
|
||||
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||
);
|
||||
} else {
|
||||
const { PerFrameFunctionalGroupsSequence } = metadata;
|
||||
|
||||
let SourceImageSequence;
|
||||
|
||||
if (metadata.SourceImageSequence) {
|
||||
SourceImageSequence = metadata.SourceImageSequence;
|
||||
} else {
|
||||
const firstFunctionalGroups = _toArray(
|
||||
PerFrameFunctionalGroupsSequence
|
||||
)[0];
|
||||
const { DerivationImageSequence } = firstFunctionalGroups;
|
||||
|
||||
SourceImageSequence = DerivationImageSequence;
|
||||
}
|
||||
|
||||
const firstSourceImage = _toArray(SourceImageSequence)[0];
|
||||
|
||||
const { ReferencedSOPInstanceUID } = firstSourceImage;
|
||||
|
||||
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
|
||||
otherDisplaySets,
|
||||
ReferencedSOPInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
||||
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
||||
@@ -35,3 +66,23 @@ const _getReferencedDisplaySet = (segDisplaySet, studies) => {
|
||||
|
||||
return referencedDisplaySet;
|
||||
};
|
||||
|
||||
const _findReferencedSeriesInstanceUIDsFromSOPInstanceUID = (
|
||||
displaySets,
|
||||
SOPInstanceUID
|
||||
) => {
|
||||
const imageSets = displaySets.filter(ds => ds instanceof ImageSet);
|
||||
|
||||
for (let i = 0; i < imageSets.length; i++) {
|
||||
const { images } = imageSets[i];
|
||||
for (let j = 0; j < images.length; j++) {
|
||||
if (images[j].SOPInstanceUID === SOPInstanceUID) {
|
||||
return [images[j].getData().metadata.SeriesInstanceUID];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function _toArray(arrayOrObject) {
|
||||
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import init from './init.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||
import { version } from '../package.json';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'com.ohif.dicom-segmentation',
|
||||
version,
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import csTools from 'cornerstone-tools';
|
||||
import DICOMSegTempCrosshairsTool from './tools/DICOMSegTempCrosshairsTool';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -17,4 +18,6 @@ export default function init({ servicesManager, configuration = {} }) {
|
||||
alwaysEraseOnClick: true,
|
||||
},
|
||||
});
|
||||
|
||||
csTools.addTool(DICOMSegTempCrosshairsTool);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import cornerstoneTools, {
|
||||
importInternal,
|
||||
getToolState,
|
||||
toolColors,
|
||||
getModule,
|
||||
globalImageIdSpecificToolStateManager,
|
||||
} from 'cornerstone-tools';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
|
||||
import TOOL_NAMES from './TOOL_NAMES';
|
||||
|
||||
const { DICOM_SEG_TEMP_CROSSHAIRS_TOOL } = TOOL_NAMES;
|
||||
const { getters } = getModule('segmentation');
|
||||
|
||||
// Cornerstone 3rd party dev kit imports
|
||||
const BaseTool = importInternal('base/BaseTool');
|
||||
|
||||
/**
|
||||
* @class RTStructDisplayTool - Renders RTSTRUCT data in a read only manner (i.e. as an overlay).
|
||||
* @extends cornerstoneTools.BaseTool
|
||||
*/
|
||||
export default class DICOMSegTempCrosshairsTool extends BaseTool {
|
||||
constructor(props = {}) {
|
||||
const defaultProps = {
|
||||
mixins: ['enabledOrDisabledBinaryTool'],
|
||||
name: DICOM_SEG_TEMP_CROSSHAIRS_TOOL,
|
||||
};
|
||||
|
||||
const initialProps = Object.assign(defaultProps, props);
|
||||
|
||||
super(initialProps);
|
||||
|
||||
this._rtStructModule = cornerstoneTools.getModule('rtstruct');
|
||||
}
|
||||
|
||||
renderToolData(evt) {
|
||||
const eventData = evt.detail;
|
||||
const { element } = eventData;
|
||||
const toolState = getToolState(evt.currentTarget, this.name);
|
||||
|
||||
if (!toolState) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We have tool data for this element - iterate over each one and draw it
|
||||
|
||||
for (let i = 0; i < toolState.data.length; i++) {
|
||||
const data = toolState.data[i];
|
||||
const crossHairCenter = data.center;
|
||||
|
||||
drawCanvasCrosshairs(eventData, crossHairCenter, {
|
||||
color: toolColors.getActiveColor(),
|
||||
lineWidth: 1,
|
||||
});
|
||||
|
||||
// Remove the crosshairs, we only render them for one redraw.
|
||||
toolState.data.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DICOMSegTempCrosshairsTool.addCrosshair = (element, imageId, segmentNumber) => {
|
||||
const labelmap3D = getters.labelmap3D(element);
|
||||
const stackToolState = cornerstoneTools.getToolState(element, 'stack');
|
||||
const enabledElement = cornerstone.getEnabledElement(element);
|
||||
|
||||
const { rows, columns } = enabledElement.image;
|
||||
|
||||
if (!stackToolState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageIds = stackToolState.data[0].imageIds;
|
||||
const imageIdIndex = imageIds.findIndex(imgId => imgId === imageId);
|
||||
|
||||
const labelmap2D = labelmap3D.labelmaps2D[imageIdIndex];
|
||||
const { pixelData } = labelmap2D;
|
||||
|
||||
let xCenter = 0;
|
||||
let yCenter = 0;
|
||||
|
||||
let count = 0;
|
||||
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < columns; x++) {
|
||||
if (pixelData[y * columns + x] === segmentNumber) {
|
||||
count++;
|
||||
xCenter += x + 0.5;
|
||||
yCenter += y + 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xCenter /= count;
|
||||
yCenter /= count;
|
||||
|
||||
const globalToolState = globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
if (!globalToolState[imageId]) {
|
||||
globalToolState[imageId] = {};
|
||||
}
|
||||
|
||||
const imageIdSpecificToolState = globalToolState[imageId];
|
||||
|
||||
if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL]) {
|
||||
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL] = { data: [] };
|
||||
} else if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data) {
|
||||
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data = [];
|
||||
}
|
||||
|
||||
const toolSpecificData =
|
||||
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data;
|
||||
|
||||
toolSpecificData.push({ center: { x: xCenter, y: yCenter }, segmentNumber });
|
||||
|
||||
// Enable the tool if not enabled for the element.
|
||||
|
||||
const tool = cornerstoneTools.getToolForElement(
|
||||
element,
|
||||
DICOM_SEG_TEMP_CROSSHAIRS_TOOL
|
||||
);
|
||||
|
||||
if (tool.mode !== 'enabled') {
|
||||
// If not already active or passive, set passive so contours render.
|
||||
cornerstoneTools.setToolEnabled(DICOM_SEG_TEMP_CROSSHAIRS_TOOL);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
const TOOL_NAMES = {
|
||||
DICOM_SEG_TEMP_CROSSHAIRS_TOOL: 'DICOMSegTempCrosshairsTool',
|
||||
};
|
||||
|
||||
export default TOOL_NAMES;
|
||||
@@ -0,0 +1,56 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { importInternal } = cornerstoneTools;
|
||||
const draw = importInternal('drawing/draw');
|
||||
const drawLine = importInternal('drawing/drawLine');
|
||||
const getNewContext = importInternal('drawing/getNewContext');
|
||||
|
||||
export default function _drawCanvasCrosshairs(eventData, center, options) {
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
const { element } = eventData;
|
||||
|
||||
const centerCanvas = cornerstone.pixelToCanvas(element, center);
|
||||
|
||||
const { clientWidth: width, clientHeight: height } = element;
|
||||
|
||||
const offset = 10;
|
||||
|
||||
draw(context, context => {
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x + offset, y: centerCanvas.y },
|
||||
{ x: width, y: centerCanvas.y },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x - offset, y: centerCanvas.y },
|
||||
{ x: 0, y: centerCanvas.y },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x, y: centerCanvas.y + offset },
|
||||
{ x: centerCanvas.x, y: height },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x, y: centerCanvas.y - offset },
|
||||
{ x: centerCanvas.x, y: 0 },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
const SRC_DIR = path.join(__dirname, '../src');
|
||||
const DIST_DIR = path.join(__dirname, '../dist');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
const merge = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
|
||||
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');
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
|
||||
|
||||
return merge(commonConfig, {
|
||||
devtool: 'source-map',
|
||||
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: 'OHIFExtDicomP10Downloader',
|
||||
libraryTarget: 'umd',
|
||||
libraryExport: 'default',
|
||||
filename: pkg.main,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.1.0...@ohif/extension-dicom-tag-browser@0.1.1) (2020-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.1.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.5...@ohif/extension-dicom-tag-browser@0.1.0) (2020-12-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add show scroll option to modal ([be494a7](https://github.com/OHIF/Viewers/commit/be494a7376d15777dbe598289c4ecdb9f48a6a48))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.4...@ohif/extension-dicom-tag-browser@0.0.5) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.3...@ohif/extension-dicom-tag-browser@0.0.4) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.0.2...@ohif/extension-dicom-tag-browser@0.0.3) (2020-09-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 0.0.2 (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-tag-browser
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 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 @@
|
||||
# @ohif/extension-dicom-tag-browser
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-tag-browser",
|
||||
"version": "0.1.1",
|
||||
"description": "OHIF extension for checking DICOM headers.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "src/index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1.16.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
|
||||
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
|
||||
"build:package": "yarn run build",
|
||||
"prepublishOnly": "yarn run build",
|
||||
"start": "yarn run dev"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^2.6.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"react": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"moment": "2.24.0",
|
||||
"react-select": "^3.0.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
|
||||
const DicomBrowserSelect = ({ value, formatOptionLabel, options }) => (
|
||||
<Select
|
||||
value={value}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
styles={dicomBrowserSelectStyles}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
|
||||
const computedstyle = getComputedStyle(document.body);
|
||||
const uiGrayDarker = computedstyle.getPropertyValue('--ui-gray-darker');
|
||||
const activeColor = computedstyle.getPropertyValue('--active-color');
|
||||
const defaultColor = computedstyle.getPropertyValue('--default-color');
|
||||
const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
|
||||
|
||||
const dicomBrowserSelectStyles = {
|
||||
singleValue: (base, state) => ({
|
||||
...base,
|
||||
width: '100%',
|
||||
}),
|
||||
control: (base, state) => ({
|
||||
...base,
|
||||
cursor: 'pointer',
|
||||
background: uiGrayDarker,
|
||||
borderRadius: state.isFocused ? '5px 5px 5px 5px' : 5,
|
||||
borderColor: state.isFocused ? activeColor : defaultColor,
|
||||
minHeight: '50px',
|
||||
'&:hover': {
|
||||
borderColor: activeColor,
|
||||
},
|
||||
}),
|
||||
menu: base => ({
|
||||
...base,
|
||||
borderRadius: 5,
|
||||
background: uiGrayDarker,
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
width: '100%',
|
||||
cursor: 'pointer',
|
||||
'&:first-of-type': {
|
||||
borderTopLeftRadius: 5,
|
||||
borderTopRightRadius: 5,
|
||||
},
|
||||
'&:last-of-type': {
|
||||
borderBottomLeftRadius: 5,
|
||||
borderBottomRightRadius: 5,
|
||||
},
|
||||
background: state.isSelected ? uiGrayDark : uiGrayDarker,
|
||||
'&:hover': {
|
||||
background: uiGrayDark,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default DicomBrowserSelect;
|
||||
@@ -0,0 +1,26 @@
|
||||
.dcmseg-segmentation-item {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-item .segmentation-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-item .segmentation-meta-title {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
color: white;
|
||||
max-width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.dcmseg-segmentation-item .segmentation-meta-description {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary-color);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './DicomBrowserSelectItem.css';
|
||||
|
||||
const DicomBrowserSelectItem = ({ onClick, title, description }) => {
|
||||
return (
|
||||
<li className="dcmseg-segmentation-item" onClick={onClick}>
|
||||
<div className="segmentation-meta">
|
||||
<div className="segmentation-meta-title">{title}</div>
|
||||
<div className="segmentation-meta-description">{description}</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
DicomBrowserSelectItem.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
DicomBrowserSelectItem.defaultProps = {
|
||||
description: '',
|
||||
};
|
||||
|
||||
export default DicomBrowserSelectItem;
|
||||
@@ -0,0 +1,27 @@
|
||||
.dicom-tag-browser-table {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.dicom-tag-browser-table tr {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
color: var(--table-text-primary-color);
|
||||
border-top: 1px solid #ddd;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dicom-tag-browser-table td.dicom-tag-browser-table-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dicom-tag-browser-table th {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
text-align: center;
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.dicom-tag-browser-table th.dicom-tag-browser-table-left {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { classes, cornerstone as OHIFCornerstone } from '@ohif/core';
|
||||
import dcmjs from 'dcmjs';
|
||||
import DicomBrowserSelect from './DicomBrowserSelect';
|
||||
import moment from 'moment';
|
||||
import './DicomTagBrowser.css';
|
||||
import DicomBrowserSelectItem from './DicomBrowserSelectItem';
|
||||
|
||||
const { ImageSet } = classes;
|
||||
const { DicomMetaDictionary } = dcmjs.data;
|
||||
const { nameMap } = DicomMetaDictionary;
|
||||
|
||||
const { metadataProvider } = OHIFCornerstone;
|
||||
|
||||
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
const [
|
||||
activeDisplaySetInstanceUID,
|
||||
setActiveDisplaySetInstanceUID,
|
||||
] = useState(displaySetInstanceUID);
|
||||
const [activeInstance, setActiveInstance] = useState(0);
|
||||
const [tags, setTags] = useState([]);
|
||||
const [meta, setMeta] = useState('');
|
||||
const [instanceList, setInstanceList] = useState([]);
|
||||
const [displaySetList, setDisplaySetList] = useState([]);
|
||||
const [isImageStack, setIsImageStack] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const activeDisplaySet = displaySets.find(
|
||||
ds => ds.displaySetInstanceUID === activeDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
const newDisplaySetList = displaySets.map(displaySet => {
|
||||
const {
|
||||
displaySetInstanceUID,
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
SeriesDescription,
|
||||
Modality,
|
||||
} = displaySet;
|
||||
|
||||
/* Map to display representation */
|
||||
const dateStr = `${SeriesDate}:${SeriesTime}`.split('.')[0];
|
||||
const date = moment(dateStr, 'YYYYMMDD:HHmmss');
|
||||
const displayDate = date.format('ddd, MMM Do YYYY');
|
||||
|
||||
return {
|
||||
value: displaySetInstanceUID,
|
||||
title: `${SeriesNumber} (${Modality}): ${SeriesDescription}`,
|
||||
description: displayDate,
|
||||
onClick: () => {
|
||||
setActiveDisplaySetInstanceUID(displaySetInstanceUID);
|
||||
setActiveInstance(0);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let metadata;
|
||||
const isImageStack = activeDisplaySet instanceof ImageSet;
|
||||
|
||||
let selectedInstanceValue;
|
||||
let instanceList;
|
||||
|
||||
if (isImageStack) {
|
||||
const { images } = activeDisplaySet;
|
||||
const image = images[activeInstance];
|
||||
|
||||
instanceList = images.map((image, index) => {
|
||||
const metadata = image.getData().metadata;
|
||||
|
||||
const { InstanceNumber } = metadata;
|
||||
|
||||
return {
|
||||
value: index,
|
||||
title: `Instance Number: ${InstanceNumber}`,
|
||||
description: '',
|
||||
onClick: () => {
|
||||
setActiveInstance(index);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
metadata = image.getData().metadata;
|
||||
} else {
|
||||
metadata = activeDisplaySet.metadata;
|
||||
}
|
||||
|
||||
setTags(getSortedTags(metadata));
|
||||
setMeta(metadata);
|
||||
setInstanceList(instanceList);
|
||||
setDisplaySetList(newDisplaySetList);
|
||||
setIsImageStack(isImageStack);
|
||||
}, [activeDisplaySetInstanceUID, activeInstance]);
|
||||
|
||||
const selectedDisplaySetValue = displaySetList.find(
|
||||
ds => ds.value === activeDisplaySetInstanceUID
|
||||
);
|
||||
|
||||
let instanceSelectList = null;
|
||||
|
||||
if (isImageStack) {
|
||||
const selectedInstanceValue = instanceList[activeInstance];
|
||||
|
||||
instanceSelectList = (
|
||||
<DicomBrowserSelect
|
||||
value={selectedInstanceValue}
|
||||
formatOptionLabel={DicomBrowserSelectItem}
|
||||
options={instanceList}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DicomBrowserSelect
|
||||
value={selectedDisplaySetValue}
|
||||
formatOptionLabel={DicomBrowserSelectItem}
|
||||
options={displaySetList}
|
||||
/>
|
||||
{instanceSelectList}
|
||||
<DicomTagTable tags={tags} meta={meta}></DicomTagTable>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function DicomTagTable({ tags, meta }) {
|
||||
const rows = getFormattedRowsFromTags(tags, meta);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className="dicom-tag-browser-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className="dicom-tag-browser-table-left">Tag</th>
|
||||
<th className="dicom-tag-browser-table-left">Value Representation</th>
|
||||
<th className="dicom-tag-browser-table-left">Keyword</th>
|
||||
<th className="dicom-tag-browser-table-left">Value</th>
|
||||
</tr>
|
||||
{rows.map((row, index) => {
|
||||
const className = row.className ? row.className : null;
|
||||
|
||||
return (
|
||||
<tr className={className} key={`DICOMTagRow-${index}`}>
|
||||
<td>{row[0]}</td>
|
||||
<td className="dicom-tag-browser-table-center">{row[1]}</td>
|
||||
<td>{row[2]}</td>
|
||||
<td>{row[3]}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getFormattedRowsFromTags(tags, meta) {
|
||||
const rows = [];
|
||||
|
||||
tags.forEach(tagInfo => {
|
||||
if (tagInfo.vr === 'SQ') {
|
||||
rows.push([
|
||||
`${tagInfo.tagIndent}${tagInfo.tag}`,
|
||||
tagInfo.vr,
|
||||
tagInfo.keyword,
|
||||
'',
|
||||
]);
|
||||
|
||||
const { values } = tagInfo;
|
||||
|
||||
values.forEach((item, index) => {
|
||||
const formatedRowsFromTags = getFormattedRowsFromTags(item);
|
||||
|
||||
rows.push([
|
||||
`${item[0].tagIndent}(FFFE,E000)`,
|
||||
'',
|
||||
`Item #${index}`,
|
||||
'',
|
||||
]);
|
||||
|
||||
rows.push(...formatedRowsFromTags);
|
||||
});
|
||||
} else {
|
||||
if (tagInfo.vr === 'xs') {
|
||||
try {
|
||||
const dataset = metadataProvider.getStudyDataset(meta.StudyInstanceUID);
|
||||
const tag = dcmjs.data.Tag.fromPString(tagInfo.tag).toCleanString();
|
||||
const originalTagInfo = dataset[tag];
|
||||
tagInfo.vr = originalTagInfo.vr;
|
||||
} catch (error) {
|
||||
console.error(`Failed to parse value representation for tag '${tagInfo.keyword}'`);
|
||||
}
|
||||
}
|
||||
|
||||
rows.push([
|
||||
`${tagInfo.tagIndent}${tagInfo.tag}`,
|
||||
tagInfo.vr,
|
||||
tagInfo.keyword,
|
||||
tagInfo.value,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function getSortedTags(metadata) {
|
||||
const tagList = getRows(metadata);
|
||||
|
||||
// Sort top level tags, sequence groups are sorted when created.
|
||||
_sortTagList(tagList);
|
||||
|
||||
return tagList;
|
||||
}
|
||||
|
||||
function getRows(metadata, depth = 0) {
|
||||
// Tag, Type, Value, Keyword
|
||||
|
||||
const keywords = Object.keys(metadata);
|
||||
|
||||
let tagIndent = '';
|
||||
|
||||
for (let i = 0; i < depth; i++) {
|
||||
tagIndent += '>';
|
||||
}
|
||||
|
||||
if (depth > 0) {
|
||||
tagIndent += ' '; // If indented, add a space after the indents.
|
||||
}
|
||||
|
||||
const rows = [];
|
||||
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
let keyword = keywords[i];
|
||||
|
||||
if (keyword === '_vrMap') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tagInfo = nameMap[keyword];
|
||||
|
||||
let value = metadata[keyword];
|
||||
|
||||
if (tagInfo && tagInfo.vr === 'SQ') {
|
||||
const sequenceAsArray = toArray(value);
|
||||
|
||||
// Push line defining the sequence
|
||||
|
||||
const sequence = {
|
||||
tag: tagInfo.tag,
|
||||
tagIndent,
|
||||
vr: tagInfo.vr,
|
||||
keyword,
|
||||
values: [],
|
||||
};
|
||||
|
||||
rows.push(sequence);
|
||||
|
||||
if (value === null) {
|
||||
// Type 2 Sequence
|
||||
continue;
|
||||
}
|
||||
|
||||
sequenceAsArray.forEach(item => {
|
||||
const sequenceRows = getRows(item, depth + 1);
|
||||
|
||||
if (sequenceRows.length) {
|
||||
// Sort the sequence group.
|
||||
_sortTagList(sequenceRows);
|
||||
sequence.values.push(sequenceRows);
|
||||
}
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value = value.join('\\');
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
if (value === null) {
|
||||
value = ' ';
|
||||
} else {
|
||||
if (typeof value === 'object') {
|
||||
if (value.InlineBinary) {
|
||||
value = 'Inline Binary';
|
||||
} else if (value.BulkDataURI) {
|
||||
value = `Bulk Data URI`; //: ${value.BulkDataURI}`;
|
||||
} else if (value.Alphabetic) {
|
||||
value = value.Alphabetic;
|
||||
} else {
|
||||
console.warn(`Unrecognised Value: ${value} for ${keyword}:`);
|
||||
console.warn(value);
|
||||
value = ' ';
|
||||
}
|
||||
} else {
|
||||
console.warn(`Unrecognised Value: ${value} for ${keyword}:`);
|
||||
value = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tag / vr/ keyword/ value
|
||||
|
||||
// Remove retired tags
|
||||
keyword = keyword.replace('RETIRED_', '');
|
||||
|
||||
if (tagInfo) {
|
||||
rows.push({
|
||||
tag: tagInfo.tag,
|
||||
tagIndent,
|
||||
vr: tagInfo.vr,
|
||||
keyword,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
// Private tag
|
||||
const tag = `(${keyword.substring(0, 4)},${keyword.substring(4, 8)})`;
|
||||
|
||||
rows.push({
|
||||
tag,
|
||||
tagIndent,
|
||||
vr: '',
|
||||
keyword: 'Private Tag',
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function toArray(objectOrArray) {
|
||||
return Array.isArray(objectOrArray) ? objectOrArray : [objectOrArray];
|
||||
}
|
||||
|
||||
function _sortTagList(tagList) {
|
||||
tagList.sort((a, b) => {
|
||||
if (a.tag < b.tag) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
export default DicomTagBrowser;
|
||||
@@ -0,0 +1,53 @@
|
||||
import { utils } from '@ohif/core';
|
||||
import React from 'react';
|
||||
import DicomTagBrowser from './components/DicomTagBrowser';
|
||||
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
export default function getCommandsModule(servicesManager) {
|
||||
const actions = {
|
||||
openDICOMTagViewer({ viewports }) {
|
||||
const { activeViewportIndex, viewportSpecificData } = viewports;
|
||||
const activeViewportSpecificData =
|
||||
viewportSpecificData[activeViewportIndex];
|
||||
|
||||
const {
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
} = activeViewportSpecificData;
|
||||
|
||||
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
const displaySets = studyMetadata.getDisplaySets();
|
||||
|
||||
const { UIModalService } = servicesManager.services;
|
||||
|
||||
const WrappedDicomTagBrowser = function () {
|
||||
return (
|
||||
<DicomTagBrowser
|
||||
displaySets={displaySets}
|
||||
displaySetInstanceUID={displaySetInstanceUID}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
UIModalService.show({
|
||||
content: WrappedDicomTagBrowser,
|
||||
title: `DICOM Tag Browser`,
|
||||
fullscreen: true,
|
||||
showScrollbar: true
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
openDICOMTagViewer: {
|
||||
commandFn: actions.openDICOMTagViewer,
|
||||
storeContexts: ['servers', 'viewports'],
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
actions,
|
||||
definitions,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import getCommandsModule from './getCommandsModule';
|
||||
import toolbarModule from './toolbarModule';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extension
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'dicom-tag-browser',
|
||||
|
||||
/**
|
||||
* MODULE GETTERS
|
||||
*/
|
||||
getCommandsModule({ servicesManager }) {
|
||||
return getCommandsModule(servicesManager);
|
||||
},
|
||||
getToolbarModule() {
|
||||
return toolbarModule;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
const TOOLBAR_BUTTON_TYPES = {
|
||||
COMMAND: 'command',
|
||||
};
|
||||
|
||||
const definitions = [
|
||||
{
|
||||
id: 'TagBrowser',
|
||||
label: 'Tag Browser',
|
||||
icon: 'list',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
commandName: 'openDICOMTagViewer',
|
||||
context: 'VIEWER',
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
definitions,
|
||||
defaultContext: 'VIEWER',
|
||||
};
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.2.0...@ohif/extension-lesion-tracker@0.2.1) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-lesion-tracker
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.1.0...@ohif/extension-lesion-tracker@0.2.0) (2020-02-10)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-lesion-tracker",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "OHIF extension for Lesion Tracker",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import MeasurementComparisonTable from './components/MeasurementComparisonTable';
|
||||
import { version } from '../package.json';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'lesion-tracker',
|
||||
version,
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
@@ -26,7 +28,7 @@ export default {
|
||||
component: MeasurementComparisonTable,
|
||||
},
|
||||
],
|
||||
defaultContext: ['VIEWER']
|
||||
defaultContext: ['VIEWER'],
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -3,6 +3,156 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.1...@ohif/extension-vtk@1.9.2) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.0...@ohif/extension-vtk@1.9.1) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.9.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.8.0...@ohif/extension-vtk@1.9.0) (2020-12-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add error boundary and retry logic for network failures during dynamic imports ([#2145](https://github.com/OHIF/Viewers/issues/2145)) ([4c07904](https://github.com/OHIF/Viewers/commit/4c079044f6ae2381c6054d8d77414100152d1d19))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.16...@ohif/extension-vtk@1.8.0) (2020-11-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** Added de (German) translations ([2fff2d5](https://github.com/OHIF/Viewers/commit/2fff2d511fa1d36bdd6476da4ffa5684bf55374f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.16](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.15...@ohif/extension-vtk@1.7.16) (2020-11-19)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.15](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.14...@ohif/extension-vtk@1.7.15) (2020-11-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.14](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.13...@ohif/extension-vtk@1.7.14) (2020-10-20)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.12...@ohif/extension-vtk@1.7.13) (2020-10-15)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.11...@ohif/extension-vtk@1.7.12) (2020-10-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.10...@ohif/extension-vtk@1.7.11) (2020-10-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.9...@ohif/extension-vtk@1.7.10) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.8...@ohif/extension-vtk@1.7.9) (2020-10-06)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.7...@ohif/extension-vtk@1.7.8) (2020-09-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.6...@ohif/extension-vtk@1.7.7) (2020-09-17)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.5...@ohif/extension-vtk@1.7.6) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.4...@ohif/extension-vtk@1.7.5) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.3...@ohif/extension-vtk@1.7.4) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.2...@ohif/extension-vtk@1.7.3) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.1...@ohif/extension-vtk@1.7.2) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "1.7.2",
|
||||
"version": "1.9.2",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -35,7 +35,7 @@
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
@@ -50,11 +50,11 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"react-vtkjs-viewport": "^0.10.4"
|
||||
"react-vtkjs-viewport": "^0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.10.1",
|
||||
"@ohif/ui": "^1.5.3",
|
||||
"@ohif/core": "^2.11.1",
|
||||
"@ohif/ui": "^1.7.1",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* We use this component to leverage "Code Splitting"
|
||||
*
|
||||
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export default function asyncComponent(importComponent) {
|
||||
class AsyncComponent extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
component: null,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
// Add dynamically loaded component to state
|
||||
const { default: component } = await importComponent();
|
||||
|
||||
this.setState({
|
||||
component: component,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const C = this.state.component;
|
||||
|
||||
// Render the loaded component, or null
|
||||
return C ? <C {...this.props} /> : null;
|
||||
}
|
||||
}
|
||||
|
||||
return AsyncComponent;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import throttle from 'lodash.throttle';
|
||||
import {
|
||||
vtkInteractorStyleMPRCrosshairs,
|
||||
vtkInteractorStyleMPRWindowLevel,
|
||||
vtkInteractorStyleRotatableMPRCrosshairs,
|
||||
vtkSVGRotatableCrosshairsWidget,
|
||||
vtkInteractorStyleMPRRotate,
|
||||
vtkSVGCrosshairsWidget,
|
||||
} from 'react-vtkjs-viewport';
|
||||
import { getImageData } from 'react-vtkjs-viewport';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
@@ -11,11 +11,10 @@ import setMPRLayout from './utils/setMPRLayout.js';
|
||||
import setViewportToVTK from './utils/setViewportToVTK.js';
|
||||
import Constants from 'vtk.js/Sources/Rendering/Core/VolumeMapper/Constants.js';
|
||||
import OHIFVTKViewport from './OHIFVTKViewport';
|
||||
import vtkCoordinate from 'vtk.js/Sources/Rendering/Core/Coordinate';
|
||||
|
||||
const { BlendMode } = Constants;
|
||||
|
||||
const commandsModule = ({ commandsManager }) => {
|
||||
const commandsModule = ({ commandsManager, UINotificationService }) => {
|
||||
// TODO: Put this somewhere else
|
||||
let apis = {};
|
||||
|
||||
@@ -119,6 +118,14 @@ const commandsModule = ({ commandsManager }) => {
|
||||
getVtkApis: ({ index }) => {
|
||||
return apis[index];
|
||||
},
|
||||
resetMPRView() {
|
||||
apis.forEach(api => {
|
||||
api.resetOrientation();
|
||||
});
|
||||
|
||||
// Reset the crosshairs
|
||||
apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
|
||||
},
|
||||
axial: async ({ viewports }) => {
|
||||
const api = await _getActiveViewportVTKApi(viewports);
|
||||
|
||||
@@ -165,7 +172,7 @@ const commandsModule = ({ commandsManager }) => {
|
||||
segmentNumber,
|
||||
frameIndex,
|
||||
frame,
|
||||
done = () => { }
|
||||
done = () => {},
|
||||
}) => {
|
||||
let api = apis[viewports.activeViewportIndex];
|
||||
|
||||
@@ -180,10 +187,13 @@ const commandsModule = ({ commandsManager }) => {
|
||||
displaySetInstanceUID,
|
||||
SOPClassUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
||||
const imageDataObject = getImageData(
|
||||
stack.imageIds,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
let pixelIndex = 0;
|
||||
let x = 0;
|
||||
@@ -209,9 +219,12 @@ const commandsModule = ({ commandsManager }) => {
|
||||
y /= count;
|
||||
|
||||
const position = [x, y, frameIndex];
|
||||
const worldPos = _convertModelToWorldSpace(position, imageDataObject.vtkImageData);
|
||||
const worldPos = _convertModelToWorldSpace(
|
||||
position,
|
||||
imageDataObject.vtkImageData
|
||||
);
|
||||
|
||||
api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
|
||||
api.svgWidgets.rotatableCrosshairsWidget.moveCrosshairs(worldPos, apis);
|
||||
done();
|
||||
},
|
||||
setSegmentationConfiguration: async ({
|
||||
@@ -254,21 +267,38 @@ const commandsModule = ({ commandsManager }) => {
|
||||
await Promise.all(promises);
|
||||
},
|
||||
enableRotateTool: () => {
|
||||
apis.forEach(api => {
|
||||
apis.forEach((api, apiIndex) => {
|
||||
const istyle = vtkInteractorStyleMPRRotate.newInstance();
|
||||
|
||||
api.setInteractorStyle({ istyle });
|
||||
api.setInteractorStyle({
|
||||
istyle,
|
||||
configuration: { apis, apiIndex, uid: api.uid },
|
||||
});
|
||||
});
|
||||
},
|
||||
enableCrosshairsTool: () => {
|
||||
apis.forEach((api, apiIndex) => {
|
||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||
const istyle = vtkInteractorStyleRotatableMPRCrosshairs.newInstance();
|
||||
|
||||
api.setInteractorStyle({
|
||||
istyle,
|
||||
configuration: { apis, apiIndex },
|
||||
configuration: {
|
||||
apis,
|
||||
apiIndex,
|
||||
uid: api.uid,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const rotatableCrosshairsWidget =
|
||||
apis[0].svgWidgets.rotatableCrosshairsWidget;
|
||||
|
||||
const referenceLines = rotatableCrosshairsWidget.getReferenceLines();
|
||||
|
||||
// Initilise crosshairs if not initialised.
|
||||
if (!referenceLines) {
|
||||
rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
|
||||
}
|
||||
},
|
||||
enableLevelTool: () => {
|
||||
function updateVOI(apis, windowWidth, windowCenter) {
|
||||
@@ -291,10 +321,14 @@ const commandsModule = ({ commandsManager }) => {
|
||||
},
|
||||
};
|
||||
|
||||
apis.forEach(api => {
|
||||
apis.forEach((api, apiIndex) => {
|
||||
const istyle = vtkInteractorStyleMPRWindowLevel.newInstance();
|
||||
|
||||
api.setInteractorStyle({ istyle, callbacks });
|
||||
api.setInteractorStyle({
|
||||
istyle,
|
||||
callbacks,
|
||||
configuration: { apis, apiIndex, uid: api.uid },
|
||||
});
|
||||
});
|
||||
},
|
||||
setSlabThickness: ({ slabThickness }) => {
|
||||
@@ -391,18 +425,59 @@ const commandsModule = ({ commandsManager }) => {
|
||||
// Add widgets and set default interactorStyle of each viewport.
|
||||
apis.forEach((api, apiIndex) => {
|
||||
api.addSVGWidget(
|
||||
vtkSVGCrosshairsWidget.newInstance(),
|
||||
'crosshairsWidget'
|
||||
vtkSVGRotatableCrosshairsWidget.newInstance(),
|
||||
'rotatableCrosshairsWidget'
|
||||
);
|
||||
|
||||
const uid = api.uid;
|
||||
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
|
||||
const istyle = vtkInteractorStyleRotatableMPRCrosshairs.newInstance();
|
||||
|
||||
api.setInteractorStyle({
|
||||
istyle,
|
||||
configuration: { apis, apiIndex, uid },
|
||||
});
|
||||
|
||||
api.svgWidgets.rotatableCrosshairsWidget.setApiIndex(apiIndex);
|
||||
api.svgWidgets.rotatableCrosshairsWidget.setApis(apis);
|
||||
});
|
||||
|
||||
const firstApi = apis[0];
|
||||
|
||||
// Initialise crosshairs
|
||||
apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
|
||||
|
||||
// Check if we have full WebGL 2 support
|
||||
const openGLRenderWindow = apis[0].genericRenderWindow.getOpenGLRenderWindow();
|
||||
|
||||
if (!openGLRenderWindow.getWebgl2()) {
|
||||
// Throw a warning if we don't have WebGL 2 support,
|
||||
// And the volume is too big to fit in a 2D texture
|
||||
|
||||
const openGLContext = openGLRenderWindow.getContext();
|
||||
const maxTextureSizeInBytes = openGLContext.getParameter(
|
||||
openGLContext.MAX_TEXTURE_SIZE
|
||||
);
|
||||
|
||||
const maxBufferLengthFloat32 =
|
||||
(maxTextureSizeInBytes * maxTextureSizeInBytes) / 4;
|
||||
|
||||
const dimensions = firstApi.volumes[0]
|
||||
.getMapper()
|
||||
.getInputData()
|
||||
.getDimensions();
|
||||
|
||||
const volumeLength = dimensions[0] * dimensions[1] * dimensions[2];
|
||||
|
||||
if (volumeLength > maxBufferLengthFloat32) {
|
||||
UINotificationService.show({
|
||||
title: 'Browser does not support WebGL 2',
|
||||
message:
|
||||
'This volume is too large to fit in WebGL 1 textures and will display incorrectly. Please use a different browser to view this data',
|
||||
type: 'error',
|
||||
autoClose: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -456,6 +531,10 @@ const commandsModule = ({ commandsManager }) => {
|
||||
commandFn: actions.enableLevelTool,
|
||||
options: {},
|
||||
},
|
||||
resetMPRView: {
|
||||
commandFn: actions.resetMPRView,
|
||||
options: {},
|
||||
},
|
||||
setBlendModeToComposite: {
|
||||
commandFn: actions.setBlendModeToComposite,
|
||||
options: { blendMode: BlendMode.COMPOSITE_BLEND },
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React from 'react';
|
||||
import asyncComponent from './asyncComponent.js';
|
||||
import { asyncComponent, retryImport } from '@ohif/ui';
|
||||
|
||||
import commandsModule from './commandsModule.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import withCommandsManager from './withCommandsManager.js';
|
||||
import { version } from '../package.json';
|
||||
// This feels weird
|
||||
// import loadLocales from './loadLocales';
|
||||
|
||||
const OHIFVTKViewport = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
|
||||
retryImport(() => import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js'))
|
||||
);
|
||||
|
||||
const vtkExtension = {
|
||||
@@ -15,6 +17,7 @@ const vtkExtension = {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'vtk',
|
||||
version,
|
||||
|
||||
getViewportModule({ commandsManager, servicesManager }) {
|
||||
const ExtendedVTKViewport = props => (
|
||||
@@ -25,8 +28,9 @@ const vtkExtension = {
|
||||
getToolbarModule() {
|
||||
return toolbarModule;
|
||||
},
|
||||
getCommandsModule({ commandsManager }) {
|
||||
return commandsModule({ commandsManager });
|
||||
getCommandsModule({ commandsManager, servicesManager }) {
|
||||
const { UINotificationService } = servicesManager.services;
|
||||
return commandsModule({ commandsManager, UINotificationService });
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Rotate": "Drehen"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Buttons from './Buttons.json';
|
||||
|
||||
export default {
|
||||
de: {
|
||||
Buttons,
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import en from './en';
|
||||
import es from './es';
|
||||
import de from './de';
|
||||
|
||||
export default {
|
||||
...en,
|
||||
...es
|
||||
...es,
|
||||
...de,
|
||||
};
|
||||
@@ -25,6 +25,16 @@ const definitions = [
|
||||
commandName: 'enableLevelTool',
|
||||
commandOptions: {},
|
||||
},
|
||||
{
|
||||
id: 'Reset',
|
||||
label: 'Reset',
|
||||
icon: 'reset',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
commandName: 'resetMPRView',
|
||||
commandOptions: {},
|
||||
},
|
||||
/*
|
||||
{
|
||||
id: 'Rotate',
|
||||
label: 'Rotate',
|
||||
@@ -34,6 +44,7 @@ const definitions = [
|
||||
commandName: 'enableRotateTool',
|
||||
commandOptions: {},
|
||||
},
|
||||
*/
|
||||
/*
|
||||
{
|
||||
id: 'setBlendModeToComposite',
|
||||
@@ -131,4 +142,4 @@ const definitions = [
|
||||
export default {
|
||||
definitions,
|
||||
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
||||
}
|
||||
};
|
||||
@@ -28,6 +28,7 @@
|
||||
"test:unit": "jest --collectCoverage",
|
||||
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
|
||||
"test:e2e": "lerna run test:e2e --stream",
|
||||
"test:e2e:script-tag": "lerna run test:e2e:script-tag --stream",
|
||||
"test:e2e:ci": "lerna run test:e2e:ci --stream",
|
||||
"test:e2e:dist": "lerna run test:e2e:dist --stream",
|
||||
"test:e2e:serve": "lerna run test:e2e:serve --stream",
|
||||
|
||||
@@ -3,6 +3,79 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.11.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.0...@ohif/core@2.11.1) (2020-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.11.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.7...@ohif/core@2.11.0) (2020-12-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add show scroll option to modal ([be494a7](https://github.com/OHIF/Viewers/commit/be494a7376d15777dbe598289c4ecdb9f48a6a48))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.6...@ohif/core@2.10.7) (2020-10-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.5...@ohif/core@2.10.6) (2020-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use SeriesMetadata method instead of property in findMostRecentStructuredReport ([#2038](https://github.com/OHIF/Viewers/issues/2038)) ([91364db](https://github.com/OHIF/Viewers/commit/91364db8820efc86665db258d0044c16fb3e2c87)), closes [#1714](https://github.com/OHIF/Viewers/issues/1714)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.4...@ohif/core@2.10.5) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.3...@ohif/core@2.10.4) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.2...@ohif/core@2.10.3) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.1...@ohif/core@2.10.2) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.0...@ohif/core@2.10.1) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "2.10.1",
|
||||
"version": "2.11.1",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
@@ -39,7 +39,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"ajv": "^6.10.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"immer": "6.0.2",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
|
||||
@@ -13,7 +13,7 @@ const findMostRecentStructuredReport = studies => {
|
||||
// Skip series that may not have instances yet
|
||||
// This can happen if we have retrieved just the initial
|
||||
// details about the series via QIDO-RS, but not the full metadata
|
||||
if (!series.instances || !series.instances.length) {
|
||||
if (!series || series.getInstanceCount() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class MetadataProvider {
|
||||
writable: false,
|
||||
value: new Map(),
|
||||
});
|
||||
this.datasets = {};
|
||||
}
|
||||
|
||||
async addInstance(dicomJSONDatasetOrP10ArrayBuffer, options = {}) {
|
||||
@@ -52,6 +53,7 @@ class MetadataProvider {
|
||||
SOPInstanceUID,
|
||||
} = naturalizedDataset;
|
||||
|
||||
this._getAndCacheStudyDataset(StudyInstanceUID, dicomJSONDataset);
|
||||
const study = this._getAndCacheStudy(StudyInstanceUID);
|
||||
const series = this._getAndCacheSeriesFromStudy(study, SeriesInstanceUID);
|
||||
const instance = this._getAndCacheInstanceFromStudy(series, SOPInstanceUID);
|
||||
@@ -71,6 +73,16 @@ class MetadataProvider {
|
||||
this.imageIdToUIDs.set(imageId, uids);
|
||||
}
|
||||
|
||||
_getAndCacheStudyDataset(StudyInstanceUID, dataset) {
|
||||
if (!this.datasets[StudyInstanceUID]) {
|
||||
this.datasets[StudyInstanceUID] = dataset;
|
||||
}
|
||||
}
|
||||
|
||||
getStudyDataset(StudyInstanceUID) {
|
||||
return this.datasets[StudyInstanceUID];
|
||||
}
|
||||
|
||||
_getAndCacheStudy(StudyInstanceUID) {
|
||||
const studies = this.studies;
|
||||
|
||||
@@ -83,6 +95,7 @@ class MetadataProvider {
|
||||
|
||||
return study;
|
||||
}
|
||||
|
||||
_getAndCacheSeriesFromStudy(study, SeriesInstanceUID) {
|
||||
let series = study.series.get(SeriesInstanceUID);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ export default class ExtensionManager {
|
||||
constructor({ commandsManager, servicesManager, api, appConfig = {} }) {
|
||||
this.modules = {};
|
||||
this.registeredExtensionIds = [];
|
||||
this.registeredExtensionVesions = {};
|
||||
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
||||
//
|
||||
this._commandsManager = commandsManager;
|
||||
@@ -51,6 +52,7 @@ export default class ExtensionManager {
|
||||
}
|
||||
|
||||
let extensionId = extension.id;
|
||||
const version = extension.version;
|
||||
|
||||
if (!extensionId) {
|
||||
extensionId = Math.random()
|
||||
@@ -98,6 +100,8 @@ export default class ExtensionManager {
|
||||
|
||||
// Track extension registration
|
||||
this.registeredExtensionIds.push(extensionId);
|
||||
|
||||
this.registeredExtensionVesions[extensionId] = version;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +124,8 @@ export default class ExtensionManager {
|
||||
commandsManager: this._commandsManager,
|
||||
appConfig: this._appConfig,
|
||||
configuration,
|
||||
api: this._api
|
||||
api: this._api,
|
||||
extensionManager: this,
|
||||
});
|
||||
|
||||
if (!extensionModule) {
|
||||
|
||||
@@ -184,6 +184,8 @@ describe('ExtensionManager.js', () => {
|
||||
commandsManager,
|
||||
appConfig,
|
||||
configuration: extensionConfiguration,
|
||||
api: undefined,
|
||||
extensionManager,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -226,7 +228,7 @@ describe('ExtensionManager.js', () => {
|
||||
return {
|
||||
definitions: {
|
||||
exampleDefinition: {
|
||||
commandFn: () => { },
|
||||
commandFn: () => {},
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* @property {boolean} [closeButton=true] Should the modal body render the close button.
|
||||
* @property {string} [title=null] Should the modal render the title independently of the body content.
|
||||
* @property {string} [customClassName=null] The custom class to style the modal.
|
||||
* @property {boolean} [showScrollbar=null] Show or hide scrollbar.
|
||||
*/
|
||||
|
||||
const name = 'UIModalService';
|
||||
@@ -28,7 +29,7 @@ const serviceImplementation = {
|
||||
/**
|
||||
* Show a new UI modal;
|
||||
*
|
||||
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, onClose, closeButton, title, customClassName }
|
||||
* @param {ModalProps} props { content, contentProps, shouldCloseOnEsc, isOpen, onClose, closeButton, title, customClassName, showScrollbar }
|
||||
*/
|
||||
function _show({
|
||||
content = null,
|
||||
@@ -38,7 +39,9 @@ function _show({
|
||||
onClose = null,
|
||||
closeButton = true,
|
||||
title = null,
|
||||
fullscreen = false,
|
||||
customClassName = null,
|
||||
showScrollbar = false
|
||||
}) {
|
||||
return serviceImplementation._show({
|
||||
content,
|
||||
@@ -48,7 +51,9 @@ function _show({
|
||||
onClose,
|
||||
closeButton,
|
||||
title,
|
||||
fullscreen,
|
||||
customClassName,
|
||||
showScrollbar
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,15 @@ import { retrieveStudyMetadata } from './retrieveStudyMetadata';
|
||||
* @param {Array} studyInstanceUIDs The UIDs of the Studies to be retrieved
|
||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @param {boolean} [separateSeriesInstanceUIDFilters = false] - If true, split filtered metadata calls into multiple calls,
|
||||
* as some DICOMWeb implementations only support single filters.
|
||||
* @returns {Promise} that will be resolved with the metadata or rejected with the error
|
||||
*/
|
||||
export default function retrieveStudiesMetadata(
|
||||
server,
|
||||
studyInstanceUIDs,
|
||||
filters
|
||||
filters,
|
||||
separateSeriesInstanceUIDFilters = false
|
||||
) {
|
||||
// Create an empty array to store the Promises for each metaData retrieval call
|
||||
const promises = [];
|
||||
@@ -24,7 +27,12 @@ export default function retrieveStudiesMetadata(
|
||||
// Loop through the array of studyInstanceUIDs
|
||||
studyInstanceUIDs.forEach(function(StudyInstanceUID) {
|
||||
// Send the call and resolve or reject the related promise based on its outcome
|
||||
const promise = retrieveStudyMetadata(server, StudyInstanceUID, filters);
|
||||
const promise = retrieveStudyMetadata(
|
||||
server,
|
||||
StudyInstanceUID,
|
||||
filters,
|
||||
separateSeriesInstanceUIDFilters
|
||||
);
|
||||
|
||||
// Add the current promise to the array of promises
|
||||
promises.push(promise);
|
||||
|
||||
@@ -11,9 +11,16 @@ const StudyMetaDataPromises = new Map();
|
||||
* @param {string} StudyInstanceUID The UID of the Study to be retrieved
|
||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @param {boolean} [separateSeriesInstanceUIDFilters = false] - If true, split filtered metadata calls into multiple calls,
|
||||
* as some DICOMWeb implementations only support single filters.
|
||||
* @returns {Promise} that will be resolved with the metadata or rejected with the error
|
||||
*/
|
||||
export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
|
||||
export function retrieveStudyMetadata(
|
||||
server,
|
||||
StudyInstanceUID,
|
||||
filters,
|
||||
separateSeriesInstanceUIDFilters = false
|
||||
) {
|
||||
// @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all
|
||||
// and further requests for that metadata will always fail. On failure, we probably need to remove the
|
||||
// corresponding promise from the "StudyMetaDataPromises" map...
|
||||
@@ -33,11 +40,29 @@ export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
|
||||
}
|
||||
|
||||
// Create a promise to handle the data retrieval
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) {
|
||||
resolve(data);
|
||||
}, reject);
|
||||
});
|
||||
let promise;
|
||||
|
||||
if (
|
||||
filters &&
|
||||
filters.seriesInstanceUID &&
|
||||
separateSeriesInstanceUIDFilters
|
||||
) {
|
||||
promise = __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePromise(
|
||||
server,
|
||||
StudyInstanceUID,
|
||||
filters
|
||||
);
|
||||
} else {
|
||||
promise = RetrieveMetadata(server, StudyInstanceUID, filters);
|
||||
|
||||
/*
|
||||
promise = new Promise((resolve, reject) => {
|
||||
RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) {
|
||||
resolve(data);
|
||||
}, reject);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
// Store the promise in cache
|
||||
StudyMetaDataPromises.set(StudyInstanceUID, promise);
|
||||
@@ -45,6 +70,49 @@ export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits up seriesInstanceUID filters to multiple calls for platforms
|
||||
* @param {Object} server Object with server configuration parameters
|
||||
* @param {string} StudyInstanceUID The UID of the Study to be retrieved
|
||||
* @param {Object} filters - Object containing filters to be applied on retrieve metadata process
|
||||
*/
|
||||
function __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePromise(
|
||||
server,
|
||||
StudyInstanceUID,
|
||||
filters
|
||||
) {
|
||||
const { seriesInstanceUID } = filters;
|
||||
const seriesInstanceUIDs = seriesInstanceUID.split(',');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const promises = [];
|
||||
|
||||
seriesInstanceUIDs.forEach(uid => {
|
||||
const seriesSpecificFilters = Object.assign({}, filters, {
|
||||
seriesInstanceUID: uid,
|
||||
});
|
||||
|
||||
promises.push(
|
||||
RetrieveMetadata(server, StudyInstanceUID, seriesSpecificFilters)
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(promises).then(results => {
|
||||
const data = results[0];
|
||||
|
||||
let series = [];
|
||||
|
||||
results.forEach(result => {
|
||||
series = [...series, ...result.series];
|
||||
});
|
||||
|
||||
data.series = series;
|
||||
|
||||
resolve(data);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the cached study metadata retrieval promise to ensure that the browser will
|
||||
* re-retrieve the study metadata when it is next requested
|
||||
|
||||
@@ -21,12 +21,12 @@ describe('Queue', () => {
|
||||
const mockedTimeout = jest.fn(timeout);
|
||||
const timer = queue.bind(mockedTimeout);
|
||||
const start = Date.now();
|
||||
timer(120).then(now => {
|
||||
timer(1200).then(now => {
|
||||
const elapsed = now - start;
|
||||
expect(elapsed >= 120 && elapsed < 240).toBe(true);
|
||||
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
|
||||
});
|
||||
const end = await timer(120);
|
||||
expect(end - start > 240).toBe(true);
|
||||
const end = await timer(1200);
|
||||
expect(end - start > 2400).toBe(true);
|
||||
expect(mockedTimeout).toBeCalledTimes(2);
|
||||
});
|
||||
it('should prevent task execution when queue limit is reached', async () => {
|
||||
@@ -34,15 +34,15 @@ describe('Queue', () => {
|
||||
const mockedTimeout = jest.fn(timeout);
|
||||
const timer = queue.bind(mockedTimeout);
|
||||
const start = Date.now();
|
||||
const promise = timer(120).then(time => time - start);
|
||||
const promise = timer(1200).then(time => time - start);
|
||||
try {
|
||||
await timer(120);
|
||||
await timer(1200);
|
||||
} catch (e) {
|
||||
expect(Date.now() - start < 120).toBe(true);
|
||||
expect(Date.now() - start < 1200).toBe(true);
|
||||
expect(e.message).toBe('Queue limit reached');
|
||||
}
|
||||
const elapsed = await promise;
|
||||
expect(elapsed >= 120 && elapsed < 240).toBe(true);
|
||||
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
|
||||
expect(mockedTimeout).toBeCalledTimes(1);
|
||||
});
|
||||
it('should safely bind tasks to the queue', async () => {
|
||||
@@ -51,16 +51,16 @@ describe('Queue', () => {
|
||||
const mockedTimeout = jest.fn(timeout);
|
||||
const timer = queue.bindSafe(mockedTimeout, mockedErrorHandler);
|
||||
const start = Date.now();
|
||||
const promise = timer(120).then(time => time - start);
|
||||
await timer(120);
|
||||
expect(Date.now() - start < 120).toBe(true);
|
||||
const promise = timer(1200).then(time => time - start);
|
||||
await timer(1200);
|
||||
expect(Date.now() - start < 1200).toBe(true);
|
||||
expect(mockedErrorHandler).toBeCalledTimes(1);
|
||||
expect(mockedErrorHandler).nthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ message: 'Queue limit reached' })
|
||||
);
|
||||
const elapsed = await promise;
|
||||
expect(elapsed >= 120 && elapsed < 240).toBe(true);
|
||||
expect(elapsed >= 1200 && elapsed < 2400).toBe(true);
|
||||
expect(mockedTimeout).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,23 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.53.0](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.8...@ohif/i18n@0.53.0) (2020-11-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add syncTranslations script ([b161016](https://github.com/OHIF/Viewers/commit/b161016edb8808ff0d17ea637820837dca1ca3ce))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **i18n:** Added de (German) translations ([2fff2d5](https://github.com/OHIF/Viewers/commit/2fff2d511fa1d36bdd6476da4ffa5684bf55374f))
|
||||
* **i18n:** Added Russian language localization. ([d5b8974](https://github.com/OHIF/Viewers/commit/d5b89748c92c2cf5a88f54ae491adc31a1679705))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.52.8](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.7...@ohif/i18n@0.52.8) (2020-04-07)
|
||||
|
||||
**Note:** Version bump only for package @ohif/i18n
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/i18n",
|
||||
"version": "0.52.8",
|
||||
"version": "0.53.0",
|
||||
"description": "Internationalization library for The OHIF Viewer",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"No hotkeys found": "Nenhuma tecla de atalho está configurada para este aplicativo. As teclas de atalho podem ser configuradas no arquivo app-config.js do aplicativo."
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||
|
||||
export default {
|
||||
'ar': {
|
||||
UserPreferencesModal,
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"Browser": "Browser",
|
||||
"Build Number": "Build Number",
|
||||
"Latest Master Commits": "Letzter Master Commit",
|
||||
"More details": "Mehr Details",
|
||||
"Name": "Name",
|
||||
"OHIF Viewer - About": "OHIF Viewer",
|
||||
"OS": "OS",
|
||||
"Report an issue": "Problem melden",
|
||||
"Repository URL": "Repository URL",
|
||||
"Value": "Wert",
|
||||
"Version Information": "Versionsinformation",
|
||||
"Visit the forum": "Das Forum besuchen"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"Acquired": "Akquiriert",
|
||||
"Angle": "Winkel",
|
||||
"Axial": "Axial",
|
||||
"Bidirectional": "Bidirektional",
|
||||
"Brush": "Pinsel",
|
||||
"CINE": "CINE",
|
||||
"Cancel": "Abbruch",
|
||||
"Circle": "Kreis",
|
||||
"Clear": "Entfernen",
|
||||
"Coronal": "Coronal",
|
||||
"Crosshairs": "Kreuzlinien",
|
||||
"Download": "Herunterladen",
|
||||
"Ellipse": "Ellipse",
|
||||
"Elliptical": "Elliptisch",
|
||||
"Flip H": "Spiegeln H",
|
||||
"Flip V": "Spiegeln V",
|
||||
"Freehand": "Freihand",
|
||||
"Invert": "Invertieren",
|
||||
"Layout": "$t(Common:Layout)",
|
||||
"Length": "Länge",
|
||||
"Levels": "Levels",
|
||||
"Magnify": "Vergrössern",
|
||||
"Manual": "Manuell",
|
||||
"Measurements": "Messungen",
|
||||
"More": "$t(Common:More)",
|
||||
"Next": "$t(Common:Next)",
|
||||
"Pan": "Verschieben",
|
||||
"Play": "$t(Common:Play)",
|
||||
"Previous": "$t(Common:Previous)",
|
||||
"Probe": "Messung",
|
||||
"ROI Window": "ROI Fenster",
|
||||
"Rectangle": "Rechteck",
|
||||
"Reset": "$t(Common:Reset)",
|
||||
"Reset to Defaults": "Standardwerte",
|
||||
"Rotate Right": "Rechts drehen",
|
||||
"Sagittal": "Sagittal",
|
||||
"Save": "Speichern",
|
||||
"Stack Scroll": "Stapel",
|
||||
"Stop": "$t(Common:Stop)",
|
||||
"Themes": "Themes",
|
||||
"Zoom": "Zoom"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Next image": "$t(Common:Next) $t(Common:Image)",
|
||||
"Play / Stop": "$t(Common:Play) / $t(Common:Stop)",
|
||||
"Previous image": "$t(Common:Previous) $t(Common:Image)",
|
||||
"Skip to first image": "Zum ersten $t(Common:Image)",
|
||||
"Skip to last image": "Zum letzten $t(Common:Image)",
|
||||
"fps": "fps"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"Close": "Schliessen",
|
||||
"Image": "Bild",
|
||||
"Layout": "Layout",
|
||||
"Measurements": "Messungen",
|
||||
"More": "Mehr",
|
||||
"Next": "Nächstes",
|
||||
"Play": "Abspielen",
|
||||
"Previous": "Zurück",
|
||||
"Reset": "Zurücksetzen",
|
||||
"RowsPerPage": "Zeilen pro Seite",
|
||||
"Series": "Serie",
|
||||
"Show": "Zeigen",
|
||||
"Stop": "Stopp",
|
||||
"StudyDate": "Studien Datum"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Clear dates": "Zeiträume löschen",
|
||||
"End Date": "Bis",
|
||||
"Start Date": "Ab"
|
||||
}
|
||||
Loaded 100 of 216 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user