Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
587de94cea | ||
|
|
6cbd9839c6 | ||
|
|
8038009afd | ||
|
|
f07f21117d | ||
|
|
efde6ed20e | ||
|
|
32022f51f2 | ||
|
|
d282f72d75 | ||
|
|
29fceacee9 | ||
|
|
a7a5a359dc | ||
|
|
e362e895d4 | ||
|
|
45a07dfa19 | ||
|
|
7323e63608 | ||
|
|
0baf18dcb2 | ||
|
|
cca3d6bff2 | ||
|
|
f3cecf129b | ||
|
|
fe03fc6f55 | ||
|
|
b59187c74d | ||
|
|
5210c80369 | ||
|
|
f57725ac8b | ||
|
|
138e0fb3bf | ||
|
|
be494a7376 | ||
|
|
ab23feefbb | ||
|
|
4c079044f6 | ||
|
|
81fcfc7370 | ||
|
|
6ec6446787 | ||
|
|
4b5a43641a | ||
|
|
2fb0b01fe2 | ||
|
|
3632e5d495 | ||
|
|
dfe566e2aa | ||
|
|
6c5ad9e98d | ||
|
|
c0c3ab307d |
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" }}'
|
||||
|
||||
+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,7 +3,6 @@ const dotenv = require('dotenv');
|
||||
//
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const PACKAGE = require('../platform/viewer/package.json');
|
||||
// ~~ RULES
|
||||
const loadShadersRule = require('./rules/loadShaders.js');
|
||||
const loadWebWorkersRule = require('./rules/loadWebWorkers.js');
|
||||
@@ -77,7 +76,10 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
||||
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
|
||||
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
|
||||
'process.env.VERSION_NUMBER': JSON.stringify(PACKAGE.version || ''),
|
||||
'process.env.VERSION_NUMBER': webpack.DefinePlugin.runtimeValue(() => {
|
||||
const package = require('../platform/viewer/package.json');
|
||||
return JSON.stringify(package.version || '');
|
||||
}, ['../platform/viewer/package.json']),
|
||||
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
||||
/* i18n */
|
||||
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.10.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.6...@ohif/extension-cornerstone@2.10.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "2.9.6",
|
||||
"version": "2.10.0",
|
||||
"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.5",
|
||||
"dcmjs": "0.17.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.3.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.9...@ohif/extension-dicom-html@1.3.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-html",
|
||||
"version": "1.2.9",
|
||||
"version": "1.3.0",
|
||||
"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.5",
|
||||
"dcmjs": "0.17.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -3,6 +3,39 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.6.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.6.0...@ohif/extension-dicom-rt@0.6.1) (2020-12-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* panel issues and mpr button (IDC2122-IDC2117) ([32022f5](https://github.com/OHIF/Viewers/commit/32022f51f2d24f53f4c98188980db038a6dfe76c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.6.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.5.0...@ohif/extension-dicom-rt@0.6.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.9...@ohif/extension-dicom-rt@0.5.0) (2020-12-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update approach to better handle rtdose instead of disabling panel ([f3cecf1](https://github.com/OHIF/Viewers/commit/f3cecf129b16f8d24d239aa44fdff7f46e6ba683))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-rt",
|
||||
"version": "0.4.9",
|
||||
"version": "0.6.1",
|
||||
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
|
||||
"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.5",
|
||||
"dcmjs": "0.17.0",
|
||||
"gl-matrix": "^3.3.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
|
||||
@@ -15,7 +15,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
||||
id: 'OHIFDicomRTStructSopClassHandler',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUIDs,
|
||||
getDisplaySetFromSeries: function(
|
||||
getDisplaySetFromSeries: function (
|
||||
series,
|
||||
study,
|
||||
dicomWebClient,
|
||||
@@ -71,11 +71,11 @@ const OHIFDicomRTStructSopClassHandler = {
|
||||
}
|
||||
}
|
||||
|
||||
rtStructDisplaySet.getSourceDisplaySet = function(studies) {
|
||||
rtStructDisplaySet.getSourceDisplaySet = function (studies) {
|
||||
return getSourceDisplaySet(studies, rtStructDisplaySet);
|
||||
};
|
||||
|
||||
rtStructDisplaySet.load = function(referencedDisplaySet, studies) {
|
||||
rtStructDisplaySet.load = function (referencedDisplaySet, studies) {
|
||||
return loadRTStruct(
|
||||
rtStructDisplaySet,
|
||||
referencedDisplaySet,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
@@ -31,19 +31,20 @@ const refreshViewport = () => {
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Array} props.studies
|
||||
* @param {Array} props.viewports - viewportSpecificData
|
||||
* @param {Array} props.getActiveViewport - get active viewport data
|
||||
* @param {number} props.activeIndex - activeViewportIndex
|
||||
* @param {number} props.isOpen - isOpen
|
||||
* @returns component
|
||||
*/
|
||||
const RTPanel = ({
|
||||
studies,
|
||||
viewports,
|
||||
activeIndex,
|
||||
isOpen,
|
||||
onContourItemClick,
|
||||
activeContexts = [],
|
||||
contexts = {},
|
||||
activeViewport,
|
||||
getActiveViewport
|
||||
}) => {
|
||||
const isVTK = () => activeContexts.includes(contexts.VTK);
|
||||
const isCornerstone = () => activeContexts.includes(contexts.CORNERSTONE);
|
||||
@@ -54,11 +55,11 @@ const RTPanel = ({
|
||||
referencedDisplaysets: [],
|
||||
sets: [],
|
||||
selectedSet: null,
|
||||
isLocked: false
|
||||
};
|
||||
|
||||
const [state, setState] = useState(DEFAULT_STATE);
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const activeViewport = viewports[activeIndex];
|
||||
|
||||
/*
|
||||
* TODO: Improve the way we notify parts of the app that depends on rts to be loaded.
|
||||
@@ -78,30 +79,37 @@ const RTPanel = ({
|
||||
* without subscribing to external events.
|
||||
*/
|
||||
useEffect(() => {
|
||||
updateStructureSets();
|
||||
|
||||
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener(
|
||||
'extensiondicomrtrtloaded',
|
||||
updateStructureSets
|
||||
);
|
||||
document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
|
||||
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
||||
Modality: 'RTSTRUCT',
|
||||
});
|
||||
setState(state => ({ ...state, isLocked: !referencedDisplaysets.length }));
|
||||
}, [activeViewport]);
|
||||
|
||||
const updateStructureSets = () => {
|
||||
const viewport = getActiveViewport();
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const StructureSets = module.state.StructureSets;
|
||||
|
||||
if (StructureSets && StructureSets.length) {
|
||||
const viewportSets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
activeViewport.SeriesInstanceUID
|
||||
viewport.SeriesInstanceUID
|
||||
);
|
||||
|
||||
const studyMetadata = studyMetadataManager.get(
|
||||
activeViewport.StudyInstanceUID
|
||||
);
|
||||
const studyMetadata = studyMetadataManager.get(viewport.StudyInstanceUID);
|
||||
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
||||
referencedSeriesInstanceUID: viewport.SeriesInstanceUID,
|
||||
Modality: 'RTSTRUCT',
|
||||
});
|
||||
|
||||
@@ -118,10 +126,6 @@ const RTPanel = ({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateStructureSets();
|
||||
}, [studies, viewports, activeIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowSettings(showSettings && !isOpen);
|
||||
}, [isOpen]);
|
||||
@@ -232,78 +236,80 @@ const RTPanel = ({
|
||||
<div className="dcmrt-panel">
|
||||
<div className="dcmrt-panel-header">
|
||||
<h3>RT Structure Sets</h3>
|
||||
<Icon
|
||||
className="cog-icon"
|
||||
name="cog"
|
||||
width="25px"
|
||||
height="25px"
|
||||
onClick={() => setShowSettings(true)}
|
||||
/>
|
||||
{!state.isLocked && (
|
||||
<Icon
|
||||
className="cog-icon"
|
||||
name="cog"
|
||||
width="25px"
|
||||
height="25px"
|
||||
onClick={() => setShowSettings(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!state.referencedDisplaysets.length && (
|
||||
<LoadingIndicator expand height="70px" width="70px" />
|
||||
)}
|
||||
{state.sets &&
|
||||
state.referencedDisplaysets.map(displaySet => {
|
||||
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
|
||||
{!state.isLocked && !state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />}
|
||||
{state.sets && state.referencedDisplaysets.map(displaySet => {
|
||||
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
|
||||
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
viewports[activeIndex].SeriesInstanceUID
|
||||
);
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
activeViewport.SeriesInstanceUID
|
||||
);
|
||||
|
||||
const loadedSet = sets.find(
|
||||
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
return (
|
||||
<PanelSection
|
||||
key={SeriesInstanceUID}
|
||||
title={metadata.StructureSetLabel}
|
||||
loading={!isLoaded || !loadedSet}
|
||||
visible={isLoaded && loadedSet && loadedSet.visible}
|
||||
hideVisibleButton={!isLoaded}
|
||||
expanded={
|
||||
isLoaded &&
|
||||
loadedSet.SeriesInstanceUID ===
|
||||
state.selectedSet.SeriesInstanceUID
|
||||
const loadedSet = sets.find(
|
||||
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
|
||||
if (!loadedSet) return null;
|
||||
|
||||
return (
|
||||
<PanelSection
|
||||
key={SeriesInstanceUID}
|
||||
title={metadata.StructureSetLabel}
|
||||
loading={!isLoaded || !loadedSet}
|
||||
visible={isLoaded && loadedSet && loadedSet.visible}
|
||||
hideVisibleButton={!isLoaded}
|
||||
expanded={
|
||||
isLoaded &&
|
||||
loadedSet.SeriesInstanceUID ===
|
||||
state.selectedSet.SeriesInstanceUID
|
||||
}
|
||||
onVisibilityChange={newVisibility => {
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
|
||||
if (newVisibility) {
|
||||
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
|
||||
} else {
|
||||
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
|
||||
}
|
||||
onVisibilityChange={newVisibility => {
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
activeViewport.SeriesInstanceUID
|
||||
);
|
||||
setState(state => ({ ...state, sets }));
|
||||
refreshViewport();
|
||||
}}
|
||||
onExpandChange={async () => {
|
||||
if (!isLoaded) {
|
||||
await displaySet.load(activeViewport, studies);
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
|
||||
if (newVisibility) {
|
||||
module.setters.showStructureSet(loadedSet.SeriesInstanceUID);
|
||||
} else {
|
||||
module.setters.hideStructureSet(loadedSet.SeriesInstanceUID);
|
||||
}
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
viewports[activeIndex].SeriesInstanceUID
|
||||
activeViewport.SeriesInstanceUID
|
||||
);
|
||||
setState(state => ({ ...state, sets }));
|
||||
refreshViewport();
|
||||
}}
|
||||
onExpandChange={async () => {
|
||||
if (!isLoaded) {
|
||||
await displaySet.load(viewports[activeIndex], studies);
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||
viewports[activeIndex].SeriesInstanceUID
|
||||
);
|
||||
const selectedSet = sets.find(
|
||||
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
setState(state => ({ ...state, selectedSet, sets }));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ScrollableArea>
|
||||
<TableList headless>
|
||||
{isLoaded &&
|
||||
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
|
||||
</TableList>
|
||||
</ScrollableArea>
|
||||
</PanelSection>
|
||||
);
|
||||
})}
|
||||
const selectedSet = sets.find(
|
||||
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||
);
|
||||
setState(state => ({ ...state, selectedSet, sets }));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ScrollableArea>
|
||||
<TableList headless>
|
||||
{isLoaded &&
|
||||
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
|
||||
</TableList>
|
||||
</ScrollableArea>
|
||||
</PanelSection>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -90,12 +90,17 @@ export default async function loadRTStruct(
|
||||
const sopInstanceUID = ContourImageSequence
|
||||
? ContourImageSequence.ReferencedSOPInstanceUID
|
||||
: _getClosestSOPInstanceUID(
|
||||
ContourData,
|
||||
ContourGeometricType,
|
||||
NumberOfContourPoints,
|
||||
imageIdSopInstanceUidPairs
|
||||
);
|
||||
ContourData,
|
||||
ContourGeometricType,
|
||||
NumberOfContourPoints,
|
||||
imageIdSopInstanceUidPairs
|
||||
);
|
||||
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
|
||||
|
||||
if (!imageId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const imageIdSpecificToolData = _getOrCreateImageIdSpecificToolData(
|
||||
toolState,
|
||||
imageId,
|
||||
@@ -294,7 +299,7 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => {
|
||||
imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID
|
||||
);
|
||||
|
||||
return imageIdSopInstanceUidPairsEntry.imageId;
|
||||
return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null;
|
||||
};
|
||||
|
||||
function _getImageIdSopInstanceUidPairsForDisplaySet(
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.5.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.7...@ohif/extension-dicom-segmentation@0.5.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-segmentation",
|
||||
"version": "0.4.7",
|
||||
"version": "0.5.0",
|
||||
"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.5",
|
||||
"dcmjs": "0.17.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
+27
-5
@@ -159,12 +159,12 @@ const SegmentationPanel = ({
|
||||
const labelmapList = getLabelmapList(
|
||||
brushStackState,
|
||||
firstImageId,
|
||||
activeViewport
|
||||
activeViewport,
|
||||
);
|
||||
const segmentList = getSegmentList(
|
||||
labelmap3D,
|
||||
firstImageId,
|
||||
brushStackState
|
||||
brushStackState,
|
||||
);
|
||||
setState(state => ({
|
||||
...state,
|
||||
@@ -292,10 +292,13 @@ const SegmentationPanel = ({
|
||||
|
||||
const sameSegment = state.selectedSegment === segmentNumber;
|
||||
const setCurrentSelectedSegment = () => {
|
||||
const activeViewport = viewports[activeIndex];
|
||||
|
||||
_setActiveSegment(
|
||||
firstImageId,
|
||||
segmentNumber,
|
||||
labelmap3D.activeSegmentIndex
|
||||
labelmap3D.activeSegmentIndex,
|
||||
activeViewport
|
||||
);
|
||||
updateState('selectedSegment', sameSegment ? null : segmentNumber);
|
||||
|
||||
@@ -640,7 +643,7 @@ const _getReferencedSegDisplaysets = (StudyInstanceUID, SeriesInstanceUID) => {
|
||||
* @param {*} activeSegmentIndex
|
||||
* @returns
|
||||
*/
|
||||
const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
|
||||
const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex, activeViewport) => {
|
||||
if (segmentIndex === activeSegmentIndex) {
|
||||
log.info(`${activeSegmentIndex} is already the active segment`);
|
||||
return;
|
||||
@@ -653,8 +656,27 @@ const _setActiveSegment = (firstImageId, segmentIndex, activeSegmentIndex) => {
|
||||
brushStackState.labelmaps3D[brushStackState.activeLabelmapIndex];
|
||||
labelmap3D.activeSegmentIndex = segmentIndex;
|
||||
|
||||
refreshViewports();
|
||||
/**
|
||||
* Activates the correct label map if clicked segment
|
||||
* does not belong to the active labelmap
|
||||
*/
|
||||
const { StudyInstanceUID } = activeViewport;
|
||||
const studyMetadata = studyMetadataManager.get(StudyInstanceUID);
|
||||
const allDisplaySets = studyMetadata.getDisplaySets();
|
||||
let newLabelmapIndex = brushStackState.activeLabelmapIndex;
|
||||
allDisplaySets.forEach(displaySet => {
|
||||
if (displaySet.labelmapSegments) {
|
||||
Object.keys(displaySet.labelmapSegments).forEach(labelmapIndex => {
|
||||
if (displaySet.labelmapSegments[labelmapIndex].includes(segmentIndex)) {
|
||||
newLabelmapIndex = labelmapIndex;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
brushStackState.activeLabelmapIndex = newLabelmapIndex;
|
||||
|
||||
refreshViewports();
|
||||
return segmentIndex;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { MODULE_TYPES, utils } from '@ohif/core';
|
||||
import loadSegmentation from './loadSegmentation';
|
||||
import getSourceDisplaySet from './getSourceDisplaySet';
|
||||
import OHIF from '@ohif/core';
|
||||
import dcmjs from 'dcmjs';
|
||||
|
||||
const { DicomLoaderService } = OHIF.utils;
|
||||
const { DicomMessage, DicomMetaDictionary } = dcmjs.data;
|
||||
|
||||
// TODO: Should probably use dcmjs for this
|
||||
const SOP_CLASS_UIDS = {
|
||||
@@ -16,7 +21,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
id: 'OHIFDicomSegSopClassHandler',
|
||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||
sopClassUIDs,
|
||||
getDisplaySetFromSeries: function(
|
||||
getDisplaySetFromSeries: function (
|
||||
series,
|
||||
study,
|
||||
dicomWebClient,
|
||||
@@ -46,7 +51,6 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
StudyInstanceUID,
|
||||
FrameOfReferenceUID,
|
||||
authorizationHeaders,
|
||||
metadata,
|
||||
isDerived: true,
|
||||
referencedDisplaySetUID: null, // Assigned when loaded.
|
||||
labelmapIndex: null, // Assigned when loaded.
|
||||
@@ -58,15 +62,85 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
metadata,
|
||||
};
|
||||
|
||||
segDisplaySet.getSourceDisplaySet = function(studies) {
|
||||
segDisplaySet.getSourceDisplaySet = function (studies) {
|
||||
return getSourceDisplaySet(studies, segDisplaySet);
|
||||
};
|
||||
|
||||
segDisplaySet.load = function(referencedDisplaySet, studies) {
|
||||
return loadSegmentation(segDisplaySet, referencedDisplaySet, studies);
|
||||
segDisplaySet.load = async function (referencedDisplaySet, studies) {
|
||||
segDisplaySet.isLoaded = true;
|
||||
const { StudyInstanceUID } = referencedDisplaySet;
|
||||
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||
segDisplaySet,
|
||||
studies
|
||||
);
|
||||
const dicomData = DicomMessage.readFile(segArrayBuffer);
|
||||
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
|
||||
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
const imageIds = _getImageIdsForDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
referencedDisplaySet.SeriesInstanceUID
|
||||
);
|
||||
return new Promise((resolve, reject) => {
|
||||
let results;
|
||||
try {
|
||||
results = _parseSeg(segArrayBuffer, imageIds);
|
||||
} catch (error) {
|
||||
segDisplaySet.isLoaded = false;
|
||||
segDisplaySet.loadError = true;
|
||||
reject(error);
|
||||
}
|
||||
const { labelmapBufferArray, segMetadata, segmentsOnFrame, segmentsOnFrameArray } = results;
|
||||
|
||||
if (labelmapBufferArray.length > 1) {
|
||||
for (let i = 0; i < labelmapBufferArray.length; ++i) {
|
||||
loadSegmentation(imageIds, segDisplaySet, labelmapBufferArray[i], segMetadata, segmentsOnFrame, segmentsOnFrameArray[i]);
|
||||
}
|
||||
} else {
|
||||
loadSegmentation(imageIds, segDisplaySet, labelmapBufferArray[0], segMetadata, segmentsOnFrame, []);
|
||||
}
|
||||
segDisplaySet.labelmapIndex = 0;
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
return segDisplaySet;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function _parseSeg(arrayBuffer, imageIds) {
|
||||
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
||||
imageIds,
|
||||
arrayBuffer,
|
||||
cornerstone.metaData
|
||||
);
|
||||
}
|
||||
|
||||
function _getImageIdsForDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySets = study.displaySets.filter(displaySet => {
|
||||
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
// TODO -> We could make check the instance list and see if any match?
|
||||
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
|
||||
// ^ Will that even happen?
|
||||
}
|
||||
|
||||
const referencedDisplaySet = displaySets[0];
|
||||
|
||||
return referencedDisplaySet.images.map(image => image.getImageId());
|
||||
}
|
||||
@@ -1,92 +1,62 @@
|
||||
import OHIF from '@ohif/core';
|
||||
import dcmjs from 'dcmjs';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { DicomLoaderService } = OHIF.utils;
|
||||
|
||||
export default async function loadSegmentation(
|
||||
imageIds,
|
||||
segDisplaySet,
|
||||
referencedDisplaySet,
|
||||
studies
|
||||
labelmapBuffer,
|
||||
segMetadata,
|
||||
segmentsOnFrame,
|
||||
labelmapSegments
|
||||
) {
|
||||
const { StudyInstanceUID } = referencedDisplaySet;
|
||||
const { setters } = cornerstoneTools.getModule('segmentation');
|
||||
|
||||
// Set here is loading is asynchronous.
|
||||
// If this function throws its set back to false.
|
||||
segDisplaySet.isLoaded = true;
|
||||
// TODO: Could define a color LUT based on colors in the SEG.
|
||||
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
|
||||
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
|
||||
|
||||
const segArrayBuffer = await DicomLoaderService.findDicomDataPromise(
|
||||
segDisplaySet,
|
||||
studies
|
||||
setters.labelmap3DByFirstImageId(
|
||||
imageIds[0],
|
||||
labelmapBuffer,
|
||||
labelmapIndex,
|
||||
segMetadata,
|
||||
imageIds.length,
|
||||
segmentsOnFrame,
|
||||
colorLUTIndex
|
||||
);
|
||||
|
||||
const dicomData = dcmjs.data.DicomMessage.readFile(segArrayBuffer);
|
||||
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||
dicomData.dict
|
||||
);
|
||||
if (!segDisplaySet.labelmapSegments) {
|
||||
segDisplaySet.labelmapSegments = {};
|
||||
}
|
||||
|
||||
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
|
||||
/**
|
||||
* Cache each labelmap segments.
|
||||
* This data is used to determine the active label map when a given segment is activated/clicked.
|
||||
*/
|
||||
segDisplaySet.labelmapSegments[labelmapIndex] = labelmapSegments.length ? Array.from(new Set(labelmapSegments.filter(a => !!a).reduce((a, b) => a.concat(b)))) : [];
|
||||
segDisplaySet.labelmapIndex = labelmapIndex;
|
||||
|
||||
const imageIds = _getImageIdsForDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
referencedDisplaySet.SeriesInstanceUID
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let results;
|
||||
|
||||
try {
|
||||
results = _parseSeg(segArrayBuffer, imageIds);
|
||||
} catch (error) {
|
||||
segDisplaySet.isLoaded = false;
|
||||
segDisplaySet.loadError = true;
|
||||
reject(error);
|
||||
}
|
||||
|
||||
const { labelmapBuffer, segMetadata, segmentsOnFrame } = results;
|
||||
const { setters } = cornerstoneTools.getModule('segmentation');
|
||||
|
||||
// TODO: Could define a color LUT based on colors in the SEG.
|
||||
const labelmapIndex = _getNextLabelmapIndex(imageIds[0]);
|
||||
const colorLUTIndex = _makeColorLUTAndGetIndex(segMetadata);
|
||||
|
||||
setters.labelmap3DByFirstImageId(
|
||||
imageIds[0],
|
||||
labelmapBuffer,
|
||||
labelmapIndex,
|
||||
segMetadata,
|
||||
imageIds.length,
|
||||
segmentsOnFrame,
|
||||
colorLUTIndex
|
||||
);
|
||||
|
||||
segDisplaySet.labelmapIndex = labelmapIndex;
|
||||
|
||||
/*
|
||||
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
|
||||
*
|
||||
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
|
||||
* or other components that could rely on loaded segmentations that
|
||||
* the segments were loaded so that e.g. when the user opens the panel
|
||||
* before the segments are fully loaded, the panel can subscribe to this custom event
|
||||
* and update itself with the new segments.
|
||||
*
|
||||
* This limitation is due to the fact that the cs segmentation module is an object (which will be
|
||||
* updated after the segments are loaded) that React its not aware of its changes
|
||||
* because the module object its not passed in to the panel component as prop but accessed externally.
|
||||
*
|
||||
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
|
||||
* allows us to easily watch the module or the segmentations loading process in any other component
|
||||
* without subscribing to external events.
|
||||
*/
|
||||
console.log('Segmentation loaded.');
|
||||
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
|
||||
document.dispatchEvent(event);
|
||||
|
||||
resolve(labelmapIndex);
|
||||
});
|
||||
/*
|
||||
* TODO: Improve the way we notify parts of the app that depends on segs to be loaded.
|
||||
*
|
||||
* Currently we are using a non-ideal implementation through a custom event to notify the segmentation panel
|
||||
* or other components that could rely on loaded segmentations that
|
||||
* the segments were loaded so that e.g. when the user opens the panel
|
||||
* before the segments are fully loaded, the panel can subscribe to this custom event
|
||||
* and update itself with the new segments.
|
||||
*
|
||||
* This limitation is due to the fact that the cs segmentation module is an object (which will be
|
||||
* updated after the segments are loaded) that React its not aware of its changes
|
||||
* because the module object its not passed in to the panel component as prop but accessed externally.
|
||||
*
|
||||
* Improving this event approach to something reactive that can be tracked inside the react lifecycle,
|
||||
* allows us to easily watch the module or the segmentations loading process in any other component
|
||||
* without subscribing to external events.
|
||||
*/
|
||||
console.log('Segmentation loaded.');
|
||||
const event = new CustomEvent('extensiondicomsegmentationsegloaded');
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function _getNextLabelmapIndex(firstImageId) {
|
||||
@@ -172,38 +142,3 @@ function _getNextColorLUTIndex() {
|
||||
|
||||
return colorLUTIndex;
|
||||
}
|
||||
|
||||
function _parseSeg(arrayBuffer, imageIds) {
|
||||
return dcmjs.adapters.Cornerstone.Segmentation.generateToolState(
|
||||
imageIds,
|
||||
arrayBuffer,
|
||||
cornerstone.metaData
|
||||
);
|
||||
}
|
||||
|
||||
function _getImageIdsForDisplaySet(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
SeriesInstanceUID
|
||||
) {
|
||||
const study = studies.find(
|
||||
study => study.StudyInstanceUID === StudyInstanceUID
|
||||
);
|
||||
|
||||
const displaySets = study.displaySets.filter(displaySet => {
|
||||
return displaySet.SeriesInstanceUID === SeriesInstanceUID;
|
||||
});
|
||||
|
||||
if (displaySets.length > 1) {
|
||||
console.warn(
|
||||
'More than one display set with the same SeriesInstanceUID. This is not supported yet...'
|
||||
);
|
||||
// TODO -> We could make check the instance list and see if any match?
|
||||
// Do we split the segmentation into two cornerstoneTools segmentations if there are images in both series?
|
||||
// ^ Will that even happen?
|
||||
}
|
||||
|
||||
const referencedDisplaySet = displaySets[0];
|
||||
|
||||
return referencedDisplaySet.images.map(image => image.getImageId());
|
||||
}
|
||||
@@ -3,6 +3,47 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-tag-browser@0.1.1...@ohif/extension-dicom-tag-browser@0.2.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-tag-browser",
|
||||
"version": "0.0.4",
|
||||
"version": "0.2.0",
|
||||
"description": "OHIF extension for checking DICOM headers.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^2.6.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"dcmjs": "0.17.0",
|
||||
"react": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { classes } from '@ohif/core';
|
||||
import { classes, cornerstone as OHIFCornerstone } from '@ohif/core';
|
||||
import dcmjs from 'dcmjs';
|
||||
import DicomBrowserSelect from './DicomBrowserSelect';
|
||||
import moment from 'moment';
|
||||
@@ -10,6 +10,8 @@ const { ImageSet } = classes;
|
||||
const { DicomMetaDictionary } = dcmjs.data;
|
||||
const { nameMap } = DicomMetaDictionary;
|
||||
|
||||
const { metadataProvider } = OHIFCornerstone;
|
||||
|
||||
const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
const [
|
||||
activeDisplaySetInstanceUID,
|
||||
@@ -17,6 +19,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
] = 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);
|
||||
@@ -83,6 +86,7 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
}
|
||||
|
||||
setTags(getSortedTags(metadata));
|
||||
setMeta(metadata);
|
||||
setInstanceList(instanceList);
|
||||
setDisplaySetList(newDisplaySetList);
|
||||
setIsImageStack(isImageStack);
|
||||
@@ -114,41 +118,43 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => {
|
||||
options={displaySetList}
|
||||
/>
|
||||
{instanceSelectList}
|
||||
<DicomTagTable tags={tags}></DicomTagTable>
|
||||
<DicomTagTable tags={tags} meta={meta}></DicomTagTable>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function DicomTagTable({ tags }) {
|
||||
const rows = getFormattedRowsFromTags(tags);
|
||||
function DicomTagTable({ tags, meta }) {
|
||||
const rows = getFormattedRowsFromTags(tags, meta);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className="dicom-tag-browser-table">
|
||||
<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 => {
|
||||
const className = row.className ? row.className : null;
|
||||
<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}>
|
||||
<td>{row[0]}</td>
|
||||
<td className="dicom-tag-browser-table-center">{row[1]}</td>
|
||||
<td>{row[2]}</td>
|
||||
<td>{row[3]}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
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) {
|
||||
function getFormattedRowsFromTags(tags, meta) {
|
||||
const rows = [];
|
||||
|
||||
tags.forEach(tagInfo => {
|
||||
@@ -175,6 +181,17 @@ function getFormattedRowsFromTags(tags) {
|
||||
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,
|
||||
@@ -247,10 +264,11 @@ function getRows(metadata, depth = 0) {
|
||||
sequenceAsArray.forEach(item => {
|
||||
const sequenceRows = getRows(item, depth + 1);
|
||||
|
||||
// Sort the sequence group.
|
||||
_sortTagList(sequenceRows);
|
||||
|
||||
sequence.values.push(sequenceRows);
|
||||
if (sequenceRows.length) {
|
||||
// Sort the sequence group.
|
||||
_sortTagList(sequenceRows);
|
||||
sequence.values.push(sequenceRows);
|
||||
}
|
||||
});
|
||||
|
||||
continue;
|
||||
@@ -276,13 +294,12 @@ function getRows(metadata, depth = 0) {
|
||||
} else if (value.Alphabetic) {
|
||||
value = value.Alphabetic;
|
||||
} else {
|
||||
console.error('Unrecognised Value for element:');
|
||||
console.error(value);
|
||||
console.warn(`Unrecognised Value: ${value} for ${keyword}:`);
|
||||
console.warn(value);
|
||||
value = ' ';
|
||||
}
|
||||
} else {
|
||||
console.error('Unrecognised Value for element:');
|
||||
console.error(value);
|
||||
console.warn(`Unrecognised Value: ${value} for ${keyword}:`);
|
||||
value = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function getCommandsModule(servicesManager) {
|
||||
|
||||
const { UIModalService } = servicesManager.services;
|
||||
|
||||
const WrappedDicomTagBrowser = function() {
|
||||
const WrappedDicomTagBrowser = function () {
|
||||
return (
|
||||
<DicomTagBrowser
|
||||
displaySets={displaySets}
|
||||
@@ -34,6 +34,7 @@ export default function getCommandsModule(servicesManager) {
|
||||
content: WrappedDicomTagBrowser,
|
||||
title: `DICOM Tag Browser`,
|
||||
fullscreen: true,
|
||||
showScrollbar: true
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,6 +3,82 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.1...@ohif/extension-vtk@1.10.2) (2021-01-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.10.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.10.0...@ohif/extension-vtk@1.10.1) (2020-12-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* panel issues and mpr button (IDC2122-IDC2117) ([32022f5](https://github.com/OHIF/Viewers/commit/32022f51f2d24f53f4c98188980db038a6dfe76c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.10.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.4...@ohif/extension-vtk@1.10.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.3...@ohif/extension-vtk@1.9.4) (2020-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reset VOI on mpr reset ([#2115](https://github.com/OHIF/Viewers/issues/2115)) ([0baf18d](https://github.com/OHIF/Viewers/commit/0baf18dcb2985378016236b735329346a851e14f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.9.2...@ohif/extension-vtk@1.9.3) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "1.8.0",
|
||||
"version": "1.10.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.5",
|
||||
"dcmjs": "0.17.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
@@ -53,8 +53,8 @@
|
||||
"react-vtkjs-viewport": "^0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.10.7",
|
||||
"@ohif/ui": "^1.5.7",
|
||||
"@ohif/core": "^2.12.1",
|
||||
"@ohif/ui": "^1.8.1",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
|
||||
@@ -138,6 +138,8 @@ class OHIFVTKViewport extends Component {
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
) => {
|
||||
const { UINotificationService } = this.props.servicesManager.services;
|
||||
|
||||
const stack = OHIFVTKViewport.getCornerstoneStack(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
@@ -159,6 +161,18 @@ class OHIFVTKViewport extends Component {
|
||||
const { activeLabelmapIndex } = brushStackState;
|
||||
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
|
||||
|
||||
if (
|
||||
brushStackState.labelmaps3D.length > 1 &&
|
||||
this.props.viewportIndex === 0
|
||||
) {
|
||||
UINotificationService.show({
|
||||
title: 'Overlapping Segmentation Found',
|
||||
message:
|
||||
'Overlapping segmentations cannot be displayed when in MPR mode',
|
||||
type: 'info',
|
||||
});
|
||||
}
|
||||
|
||||
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
|
||||
isHidden => {
|
||||
return { visible: !isHidden };
|
||||
@@ -296,52 +310,79 @@ class OHIFVTKViewport extends Component {
|
||||
seriesDescription: displaySet.seriesDescription,
|
||||
};
|
||||
|
||||
const {
|
||||
imageDataObject,
|
||||
labelmapDataObject,
|
||||
labelmapColorLUT,
|
||||
} = this.getViewportData(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
try {
|
||||
const {
|
||||
imageDataObject,
|
||||
labelmapDataObject,
|
||||
labelmapColorLUT,
|
||||
} = this.getViewportData(
|
||||
studies,
|
||||
StudyInstanceUID,
|
||||
displaySetInstanceUID,
|
||||
SOPInstanceUID,
|
||||
frameIndex
|
||||
);
|
||||
|
||||
this.imageDataObject = imageDataObject;
|
||||
this.imageDataObject = imageDataObject;
|
||||
|
||||
/* TODO: Not currently used until we have drawing tools in vtkjs.
|
||||
if (!labelmap) {
|
||||
labelmap = createLabelMapImageData(data);
|
||||
} */
|
||||
/* TODO: Not currently used until we have drawing tools in vtkjs.
|
||||
if (!labelmap) {
|
||||
labelmap = createLabelMapImageData(data);
|
||||
} */
|
||||
|
||||
const volumeActor = this.getOrCreateVolume(
|
||||
imageDataObject,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
const volumeActor = this.getOrCreateVolume(
|
||||
imageDataObject,
|
||||
displaySetInstanceUID
|
||||
);
|
||||
|
||||
this.setState(
|
||||
{
|
||||
percentComplete: 0,
|
||||
dataDetails,
|
||||
},
|
||||
() => {
|
||||
this.loadProgressively(imageDataObject);
|
||||
this.setState(
|
||||
{
|
||||
percentComplete: 0,
|
||||
dataDetails,
|
||||
},
|
||||
() => {
|
||||
this.loadProgressively(imageDataObject);
|
||||
|
||||
// TODO: There must be a better way to do this.
|
||||
// We do this so that if all the data is available the react-vtkjs-viewport
|
||||
// Will render _something_ before the volumes are set and the volume
|
||||
// Construction that happens in react-vtkjs-viewport locks up the CPU.
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
volumes: [volumeActor],
|
||||
paintFilterLabelMapImageData: labelmapDataObject,
|
||||
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
|
||||
labelmapColorLUT,
|
||||
});
|
||||
}, 200);
|
||||
// TODO: There must be a better way to do this.
|
||||
// We do this so that if all the data is available the react-vtkjs-viewport
|
||||
// Will render _something_ before the volumes are set and the volume
|
||||
// Construction that happens in react-vtkjs-viewport locks up the CPU.
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
volumes: [volumeActor],
|
||||
paintFilterLabelMapImageData: labelmapDataObject,
|
||||
paintFilterBackgroundImageData: imageDataObject.vtkImageData,
|
||||
labelmapColorLUT,
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
const errorTitle = 'Failed to load 2D MPR';
|
||||
console.error(errorTitle, error);
|
||||
const { UINotificationService } = this.props.servicesManager.services;
|
||||
if (this.props.viewportIndex === 0) {
|
||||
const message = error.message.includes('buffer')
|
||||
? 'Dataset is too big to display in MPR'
|
||||
: error.message;
|
||||
console.error(errorTitle, error);
|
||||
UINotificationService.show({
|
||||
title: errorTitle,
|
||||
message,
|
||||
type: 'error',
|
||||
autoClose: false,
|
||||
action: {
|
||||
label: 'Exit 2D MPR',
|
||||
onClick: ({ close }) => {
|
||||
// context: 'ACTIVE_VIEWPORT::VTK',
|
||||
close();
|
||||
this.props.commandsManager.runCommand('setCornerstoneLayout');
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
this.setState({ isLoaded: true });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ const { BlendMode } = Constants;
|
||||
const commandsModule = ({ commandsManager, UINotificationService }) => {
|
||||
// TODO: Put this somewhere else
|
||||
let apis = {};
|
||||
let defaultVOI;
|
||||
|
||||
async function _getActiveViewportVTKApi(viewports) {
|
||||
const {
|
||||
@@ -119,9 +120,11 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
|
||||
return apis[index];
|
||||
},
|
||||
resetMPRView() {
|
||||
apis.forEach(api => {
|
||||
api.resetOrientation();
|
||||
});
|
||||
// Reset orientation
|
||||
apis.forEach(api => api.resetOrientation());
|
||||
|
||||
// Reset VOI
|
||||
if (defaultVOI) setVOI(defaultVOI);
|
||||
|
||||
// Reset the crosshairs
|
||||
apis[0].svgWidgets.rotatableCrosshairsWidget.resetCrosshairs(apis, 0);
|
||||
@@ -172,7 +175,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
|
||||
segmentNumber,
|
||||
frameIndex,
|
||||
frame,
|
||||
done = () => {},
|
||||
done = () => { },
|
||||
}) => {
|
||||
let api = apis[viewports.activeViewportIndex];
|
||||
|
||||
@@ -387,6 +390,7 @@ const commandsModule = ({ commandsManager, UINotificationService }) => {
|
||||
|
||||
// Get current VOI if cornerstone viewport.
|
||||
const cornerstoneVOI = getVOIFromCornerstoneViewport();
|
||||
defaultVOI = cornerstoneVOI;
|
||||
|
||||
const viewportProps = [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -8,7 +9,9 @@ import { version } from '../package.json';
|
||||
// import loadLocales from './loadLocales';
|
||||
|
||||
const OHIFVTKViewport = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
|
||||
retryImport(() =>
|
||||
import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
|
||||
)
|
||||
);
|
||||
|
||||
const vtkExtension = {
|
||||
@@ -20,7 +23,11 @@ const vtkExtension = {
|
||||
|
||||
getViewportModule({ commandsManager, servicesManager }) {
|
||||
const ExtendedVTKViewport = props => (
|
||||
<OHIFVTKViewport {...props} servicesManager={servicesManager} />
|
||||
<OHIFVTKViewport
|
||||
{...props}
|
||||
servicesManager={servicesManager}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
);
|
||||
return withCommandsManager(ExtendedVTKViewport, commandsManager);
|
||||
},
|
||||
|
||||
@@ -31,7 +31,63 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
|
||||
|
||||
if (!displaySet) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
// 2D MPR is not currently available for 4D datasets.
|
||||
|
||||
// Assuming that slices at different time have the same position, here we just check if
|
||||
// there are multiple slices for the same ImagePositionPatient and disable MPR.
|
||||
|
||||
// A better heuristic would be checking 4D tags, e.g. the presence of multiple TemporalPositionIdentifier values.
|
||||
// However, some studies (e.g. https://github.com/OHIF/Viewers/issues/2113) do not have such tags.
|
||||
|
||||
for (let ii = 0; ii < displaySet.numImageFrames; ++ii) {
|
||||
const image = displaySet.images[ii];
|
||||
if (!image) continue;
|
||||
|
||||
const imageIdControl = image.getImageId()
|
||||
const instanceMetadataControl = cornerstone.metaData.get('instance', imageIdControl)
|
||||
|
||||
if (!instanceMetadataControl ||
|
||||
instanceMetadataControl === undefined ||
|
||||
!instanceMetadataControl.ImagePositionPatient ||
|
||||
instanceMetadataControl.ImagePositionPatient === undefined) {
|
||||
// if ImagePositionPatient is missing, skip the 4D datasets check.
|
||||
// do not return false, because it could be a 3D dataset.
|
||||
continue;
|
||||
}
|
||||
|
||||
let xImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[0];
|
||||
let yImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[1];
|
||||
let zImagePositionPatientControl = instanceMetadataControl.ImagePositionPatient[2];
|
||||
|
||||
for (let jj = ii + 1; jj < displaySet.numImageFrames; ++jj) {
|
||||
const image = displaySet.images[jj];
|
||||
if (!image) continue;
|
||||
|
||||
const imageId = image.getImageId()
|
||||
const instanceMetadata = cornerstone.metaData.get('instance', imageId)
|
||||
|
||||
if (!instanceMetadata ||
|
||||
instanceMetadata === undefined ||
|
||||
!instanceMetadata.ImagePositionPatient ||
|
||||
instanceMetadata.ImagePositionPatient === undefined) {
|
||||
// if ImagePositionPatient is missing, skip the 4D datasets check.
|
||||
// do not return false, because it could be a 3D dataset.
|
||||
continue;
|
||||
}
|
||||
|
||||
let xImagePositionPatient = instanceMetadata.ImagePositionPatient[0];
|
||||
let yImagePositionPatient = instanceMetadata.ImagePositionPatient[1];
|
||||
let zImagePositionPatient = instanceMetadata.ImagePositionPatient[2];
|
||||
|
||||
if (xImagePositionPatientControl === xImagePositionPatient &&
|
||||
yImagePositionPatientControl === yImagePositionPatient &&
|
||||
zImagePositionPatientControl === zImagePositionPatient) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return displaySet.isReconstructable;
|
||||
};
|
||||
|
||||
@@ -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,47 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.12.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.12.0...@ohif/core@2.12.1) (2021-01-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.12.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.11.1...@ohif/core@2.12.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "2.10.7",
|
||||
"version": "2.12.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.5",
|
||||
"dcmjs": "0.17.0",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"immer": "6.0.2",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -40,6 +41,7 @@ function _show({
|
||||
title = null,
|
||||
fullscreen = false,
|
||||
customClassName = null,
|
||||
showScrollbar = false
|
||||
}) {
|
||||
return serviceImplementation._show({
|
||||
content,
|
||||
@@ -51,6 +53,7 @@ function _show({
|
||||
title,
|
||||
fullscreen,
|
||||
customClassName,
|
||||
showScrollbar
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* @property {string} [position="bottomRight"] -"topLeft" | "topCenter | "topRight" | "bottomLeft" | "bottomCenter" | "bottomRight"
|
||||
* @property {string} [type="info"] - "info" | "error" | "warning" | "success"
|
||||
* @property {boolean} [autoClose=true]
|
||||
* @property {object} [action=null]
|
||||
*/
|
||||
|
||||
const name = 'UINotificationService';
|
||||
@@ -34,7 +35,7 @@ const serviceImplementation = {
|
||||
* Create and show a new UI notification; returns the
|
||||
* ID of the created notification.
|
||||
*
|
||||
* @param {Notification} notification { title, message, duration, position, type, autoClose}
|
||||
* @param {Notification} notification { title, message, duration, position, type, autoClose, action}
|
||||
* @returns {number} id
|
||||
*/
|
||||
function _show({
|
||||
@@ -44,6 +45,7 @@ function _show({
|
||||
position = 'bottomRight',
|
||||
type = 'info',
|
||||
autoClose = true,
|
||||
action = null,
|
||||
}) {
|
||||
return serviceImplementation._show({
|
||||
title,
|
||||
@@ -52,6 +54,7 @@ function _show({
|
||||
position,
|
||||
type,
|
||||
autoClose,
|
||||
action,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,58 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.8.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.8.0...@ohif/ui@1.8.1) (2021-01-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/ui
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.8.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.1...@ohif/ui@1.8.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.7.0...@ohif/ui@1.7.1) (2020-12-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fallback to original dataset VR for XS ([f57725a](https://github.com/OHIF/Viewers/commit/f57725ac8bfd59ea46ab334a1823882afadefba1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.7.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.6.0...@ohif/ui@1.7.0) (2020-12-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add show scroll option to modal ([be494a7](https://github.com/OHIF/Viewers/commit/be494a7376d15777dbe598289c4ecdb9f48a6a48))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [1.6.0](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.7...@ohif/ui@1.6.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.5.7](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.5.6...@ohif/ui@1.5.7) (2020-11-02)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/ui",
|
||||
"version": "1.5.7",
|
||||
"version": "1.8.1",
|
||||
"description": "A set of React components for Medical Imaging Viewers",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -15,8 +15,8 @@ const ErrorFallback = ({ error, componentStack, resetErrorBoundary }) => {
|
||||
|
||||
const OHIFErrorBoundary = ({
|
||||
context = 'OHIF',
|
||||
onReset = () => {},
|
||||
onError = () => {},
|
||||
onReset = () => { },
|
||||
onError = () => { },
|
||||
fallbackComponent,
|
||||
children,
|
||||
}) => {
|
||||
@@ -45,7 +45,7 @@ OHIFErrorBoundary.propTypes = {
|
||||
onReset: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
children: PropTypes.node.isRequired,
|
||||
fallbackComponent: PropTypes.element,
|
||||
fallbackComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func, PropTypes.element]),
|
||||
};
|
||||
|
||||
export default OHIFErrorBoundary;
|
||||
@@ -0,0 +1,24 @@
|
||||
.ErrorPage {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.ErrorPage .error-container {
|
||||
margin: 10px;
|
||||
width: 50%;
|
||||
height: 25%;
|
||||
overflow: scroll;
|
||||
border-radius: 15px;
|
||||
border-color: var(--active-color);
|
||||
border: 1px solid;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.ErrorPage .retry-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon } from '@ohif/ui';
|
||||
|
||||
import './ErrorPage.css';
|
||||
|
||||
const ErrorPage = ({ error, title, description, onRetry }) => {
|
||||
return (
|
||||
<div className="ErrorPage">
|
||||
{title && <h3>{title}</h3>}
|
||||
<p>{description}</p>
|
||||
<Icon
|
||||
className="retry-icon"
|
||||
name="rotate-right"
|
||||
width="25px"
|
||||
height="25px"
|
||||
onClick={onRetry}
|
||||
/>
|
||||
{error && (
|
||||
<div className="error-container">
|
||||
<pre>{error.message}</pre>
|
||||
<pre>{error.stack}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorPage.propTypes = {
|
||||
error: PropTypes.object,
|
||||
title: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
onRetry: PropTypes.func
|
||||
};
|
||||
|
||||
ErrorPage.defaultProps = {
|
||||
description: 'Oh snap, something went wrong, please try reloading',
|
||||
onRetry: () => window.location.reload()
|
||||
};
|
||||
|
||||
export default ErrorPage;
|
||||
@@ -0,0 +1,2 @@
|
||||
import ErrorPage from './ErrorPage';
|
||||
export default ErrorPage;
|
||||
@@ -16,6 +16,7 @@ import { SelectTree } from './selectTree';
|
||||
import { SimpleDialog } from './simpleDialog';
|
||||
import { OHIFModal } from './ohifModal';
|
||||
import { ContextMenu } from './contextMenu';
|
||||
import ErrorPage from './errorPage';
|
||||
import {
|
||||
PageToolbar,
|
||||
StudyList,
|
||||
@@ -58,4 +59,5 @@ export {
|
||||
Tooltip,
|
||||
AboutContent,
|
||||
OHIFModal,
|
||||
ErrorPage
|
||||
};
|
||||
@@ -27,13 +27,13 @@
|
||||
&.OHIFModal-fullscreen
|
||||
width: 95%
|
||||
|
||||
|
||||
|
||||
&__content
|
||||
padding: 20px
|
||||
max-height: 90vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
&:not(.visibleScrollbar) &__content
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
&::-webkit-scrollbar
|
||||
|
||||
@@ -156,19 +156,50 @@
|
||||
transition: all 300ms ease;
|
||||
}
|
||||
|
||||
.sb-success {
|
||||
.sb-item .sb-action,
|
||||
.sb-error .sb-action,
|
||||
.sb-warning .sb-action,
|
||||
.sb-info .sb-action,
|
||||
.sb-success .sb-action {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.42857143;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
-ms-touch-action: manipulation;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-image: none;
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sb-success,
|
||||
.sb-success .sb-action {
|
||||
background-color: var(--snackbar-success);
|
||||
}
|
||||
|
||||
.sb-error {
|
||||
.sb-error,
|
||||
.sb-error .sb-action {
|
||||
background-color: var(--snackbar-error);
|
||||
}
|
||||
|
||||
.sb-warning {
|
||||
.sb-warning,
|
||||
.sb-warning .sb-action {
|
||||
background-color: var(--snackbar-warning);
|
||||
}
|
||||
|
||||
.sb-info {
|
||||
.sb-info,
|
||||
.sb-info .sb-action {
|
||||
background-color: var(--snackbar-info);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
const SnackbarItem = ({ options, onClose }) => {
|
||||
const handleClose = () => {
|
||||
onClose(options.id);
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
options.action.onClick({ ...options, close: handleClose });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (options.autoClose) {
|
||||
setTimeout(() => {
|
||||
@@ -24,6 +28,11 @@ const SnackbarItem = ({ options, onClose }) => {
|
||||
</span>
|
||||
{options.title && <div className="sb-title">{options.title}</div>}
|
||||
{options.message && <div className="sb-message">{options.message}</div>}
|
||||
{options.action && (
|
||||
<button className="sb-action" onClick={handleClick}>
|
||||
{options.action.label}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
isOpen: true,
|
||||
onClose: null,
|
||||
closeButton: true,
|
||||
showScrollbar: false,
|
||||
title: null,
|
||||
customClassName: '',
|
||||
fullscreen: false,
|
||||
@@ -81,13 +82,14 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
shouldCloseOnEsc,
|
||||
fullscreen,
|
||||
closeButton,
|
||||
showScrollbar
|
||||
} = options;
|
||||
|
||||
return (
|
||||
<Provider value={{ show, hide }}>
|
||||
{ModalContent && (
|
||||
<Modal
|
||||
className={classNames(customClassName, ModalContent.className)}
|
||||
className={classNames(customClassName, ModalContent.className, { 'visibleScrollbar': showScrollbar })}
|
||||
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||
isOpen={isOpen}
|
||||
title={title}
|
||||
|
||||
@@ -22,6 +22,7 @@ const SnackbarProvider = ({ children, service }) => {
|
||||
autoClose: true,
|
||||
position: 'bottomRight',
|
||||
type: SnackbarTypes.INFO,
|
||||
action: null,
|
||||
};
|
||||
|
||||
const [count, setCount] = useState(1);
|
||||
@@ -37,6 +38,10 @@ const SnackbarProvider = ({ children, service }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (options.type === 'error') {
|
||||
console.error(options.error);
|
||||
}
|
||||
|
||||
const newItem = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...options,
|
||||
|
||||
@@ -29,7 +29,8 @@ import {
|
||||
Tooltip,
|
||||
AboutContent,
|
||||
OHIFModal,
|
||||
ErrorBoundary
|
||||
ErrorBoundary,
|
||||
ErrorPage
|
||||
} from './components';
|
||||
import { useDebounce, useMedia } from './hooks';
|
||||
|
||||
@@ -53,6 +54,7 @@ import { ScrollableArea } from './ScrollableArea/ScrollableArea.js';
|
||||
import Toolbar from './viewer/Toolbar.js';
|
||||
import ToolbarButton from './viewer/ToolbarButton.js';
|
||||
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
|
||||
import { asyncComponent, retryImport } from './utils/asyncComponent';
|
||||
import {
|
||||
SnackbarProvider,
|
||||
useSnackbarContext,
|
||||
@@ -112,7 +114,6 @@ export {
|
||||
ToolbarSection,
|
||||
Tooltip,
|
||||
AboutContent,
|
||||
ViewerbaseDragDropContext,
|
||||
SnackbarProvider,
|
||||
useSnackbarContext,
|
||||
withSnackbar,
|
||||
@@ -125,7 +126,12 @@ export {
|
||||
withDialog,
|
||||
useDialog,
|
||||
ErrorBoundary,
|
||||
ErrorPage,
|
||||
// Hooks
|
||||
useDebounce,
|
||||
useMedia,
|
||||
// Utils
|
||||
ViewerbaseDragDropContext,
|
||||
asyncComponent,
|
||||
retryImport
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ErrorPage } from '@ohif/ui';
|
||||
|
||||
export const retryImport = (fn, retriesLeft = 5, interval = 1000) =>
|
||||
new Promise((resolve, reject) => {
|
||||
fn().then(resolve).catch((error) => {
|
||||
setTimeout(() => {
|
||||
if (retriesLeft === 1) {
|
||||
/* reject('maximum retries exceeded'); */
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Passing on "reject" is the important part */
|
||||
retry(fn, retriesLeft - 1, interval).then(resolve, reject);
|
||||
}, interval);
|
||||
});
|
||||
});
|
||||
|
||||
const onError = (error, setState) => setState({ component: ErrorPage });
|
||||
|
||||
/**
|
||||
* We use this function to lazy load the import of a component to leverage 'Code Splitting'
|
||||
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
|
||||
*/
|
||||
const asyncComponent = (importComponent, options = { onError }) => props => {
|
||||
const [state, setState] = useState({ component: null });
|
||||
|
||||
const isFunction = item => typeof item === 'function';
|
||||
const isChunkError = error => error.toString().indexOf('ChunkLoadError') > -1;
|
||||
|
||||
useEffect(() => {
|
||||
const addDynamicallyLoadedComponentToState = async () => {
|
||||
try {
|
||||
const { default: component } = await importComponent();
|
||||
setState({ component });
|
||||
if (options.onLoaded && isFunction(options.onLoaded)) {
|
||||
options.onLoaded(component);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AsyncComponent] Failed to import chunk:', error);
|
||||
|
||||
if (options.onError && isFunction(options.onError)) {
|
||||
options.onError(error, setState);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isChunkError(error)) {
|
||||
console.error('[AsyncComponent] Reloading due to chunk error');
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addDynamicallyLoadedComponentToState();
|
||||
}, []);
|
||||
|
||||
const Component = state.component;
|
||||
return Component ? <Component {...props} /> : null;
|
||||
};
|
||||
|
||||
export default asyncComponent;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as asyncComponent } from './asyncComponent';
|
||||
export { retryImport } from './asyncComponent';
|
||||
@@ -3,6 +3,103 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [4.8.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.1...@ohif/viewer@4.8.2) (2021-01-21)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.8.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.8.0...@ohif/viewer@4.8.1) (2020-12-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.8.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.2...@ohif/viewer@4.8.0) (2020-12-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* visualize overlapping segments in cornerstone ([#2185](https://github.com/OHIF/Viewers/issues/2185)) ([29fceac](https://github.com/OHIF/Viewers/commit/29fceacee97d51f1952a0f6b574c66596d32c201))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.7.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.1...@ohif/viewer@4.7.2) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.7.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.7.0...@ohif/viewer@4.7.1) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.7.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.3...@ohif/viewer@4.7.0) (2020-12-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Update approach to better handle rtdose instead of disabling panel ([f3cecf1](https://github.com/OHIF/Viewers/commit/f3cecf129b16f8d24d239aa44fdff7f46e6ba683))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.6.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.2...@ohif/viewer@4.6.3) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.6.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.1...@ohif/viewer@4.6.2) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.6.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.6.0...@ohif/viewer@4.6.1) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.6.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.29...@ohif/viewer@4.6.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))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.5.29](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.28...@ohif/viewer@4.5.29) (2020-12-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.5.28](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.27...@ohif/viewer@4.5.28) (2020-11-30)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
@@ -6,5 +6,10 @@
|
||||
"requestTimeout": 10000,
|
||||
"responseTimeout": 10000,
|
||||
"projectId": "4oe38f",
|
||||
"video": false
|
||||
"video": false,
|
||||
"reporter": "junit",
|
||||
"reporterOptions": {
|
||||
"mochaFile": "cypress/results/test-output.xml",
|
||||
"toConsole": true
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ describe('OHIF Cornerstone Hotkeys', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -3,12 +3,18 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.initCornerstoneToolsAliases();
|
||||
cy.initCommonElementsAliases();
|
||||
|
||||
cy.get('[data-cy="thumbnail-list"]:nth-child(1)').click();
|
||||
|
||||
const expectedText = 'Ser: 1';
|
||||
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
||||
|
||||
cy.resetViewport();
|
||||
});
|
||||
|
||||
@@ -49,7 +55,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
it('checks if Stack Scroll tool will navigate across all series in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@stackScrollBtn')
|
||||
.click()
|
||||
.then($stackScrollBtn => {
|
||||
@@ -85,7 +91,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
it('checks if Levels tool will change the contrast and brightness of an image in the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@levelsBtn')
|
||||
.click()
|
||||
.then($levelsBtn => {
|
||||
@@ -106,7 +112,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
it('checks if Pan tool will move the image inside the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@panBtn')
|
||||
.click()
|
||||
.then($panBtn => {
|
||||
@@ -120,7 +126,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
it('checks if Length annotation can be added on viewport and on measurements panel', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@lengthBtn')
|
||||
.click()
|
||||
.then($lengthbtn => {
|
||||
@@ -147,7 +153,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
it('checks if Angle annotation can be added on viewport and on measurements panel', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@angleBtn')
|
||||
.click()
|
||||
.then($angleBtn => {
|
||||
@@ -192,7 +198,8 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
|
||||
//Click on button
|
||||
cy.get('@cineBtn').click();
|
||||
//Vefiry if cine control overlay is being displayed
|
||||
|
||||
// Verify if cine control overlay is being displayed
|
||||
cy.get('.cine-controls')
|
||||
.as('cineControls')
|
||||
.should('be.visible');
|
||||
@@ -253,13 +260,15 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
cy.get('@cineBtn')
|
||||
.click()
|
||||
.then(() => {
|
||||
//Vefiry if cine control overlay is hidden
|
||||
cy.get('@cineControls').should('not.be.visible');
|
||||
// Verify that cine control overlay is hidden
|
||||
cy.get('@cineControls').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('checks if More button will prompt a modal with secondary tools', () => {
|
||||
//Click on More button
|
||||
cy.get('@moreBtn').click();
|
||||
|
||||
//Verify if overlay is displayed
|
||||
cy.get('.tooltip-toolbar-overlay')
|
||||
.as('toolbarOverlay')
|
||||
@@ -288,9 +297,10 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
//Verify if overlay is hidden
|
||||
cy.get('@toolbarOverlay').should('not.be.visible');
|
||||
cy.get('@toolbarOverlay').should('not.exist');
|
||||
});
|
||||
|
||||
|
||||
it('checks if Layout tool will multiply the number of viewports displayed', () => {
|
||||
//Click on Layout button and verify if overlay is displayed
|
||||
cy.get('@layoutBtn')
|
||||
@@ -415,7 +425,7 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
// TODO: We need a seperate test server for this to work.
|
||||
// As anyone can save measurements on a different slice.
|
||||
|
||||
cy.get('.measurementItem'); //.should('not.exist');
|
||||
//cy.get('.measurementItem'); //.should('not.exist');
|
||||
|
||||
//Close More button overlay
|
||||
cy.get('@moreBtn').click();
|
||||
@@ -439,6 +449,9 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'F');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'R');
|
||||
});
|
||||
|
||||
//Click on More button to close it
|
||||
cy.get('@moreBtn').click();
|
||||
});
|
||||
|
||||
it('check if Flip H tool will flip the image horizontally in the viewport', () => {
|
||||
@@ -451,6 +464,10 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
cy.get('[data-cy="flip h"]').click();
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'H');
|
||||
|
||||
//Click on More button to close it
|
||||
cy.get('@moreBtn').click();
|
||||
cy.get('.tooltip-toolbar-overlay').should('not.exist');
|
||||
});
|
||||
|
||||
it('check if Flip V tool will flip the image vertically in the viewport', () => {
|
||||
@@ -463,5 +480,9 @@ describe('OHIF Cornerstone Toolbar', () => {
|
||||
cy.get('[data-cy="flip v"]').click();
|
||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
|
||||
cy.get('@viewportInfoMidTop').should('contains.text', 'F');
|
||||
|
||||
//Click on More button to close it
|
||||
cy.get('@moreBtn').click();
|
||||
cy.get('.tooltip-toolbar-overlay').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ describe('OHIF Download Snapshot File', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -22,51 +22,54 @@ describe('OHIF Study List', function() {
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(3);
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('Juno');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches MRN with exact string', function() {
|
||||
cy.get('@MRN').type('ProstateX-0000');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(2);
|
||||
expect($list).to.contain('ProstateX-0000');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Accession with exact string', function() {
|
||||
cy.get('@AccessionNumber').type('0000481914');
|
||||
cy.get('@MRN').type('0000003');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('0000481914');
|
||||
expect($list).to.contain('0000003');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Accession with exact string', function() {
|
||||
cy.get('@AccessionNumber').type('0000155811');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('0000155811');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Modality with camel case', function() {
|
||||
cy.get('@modalities').type('Mr');
|
||||
cy.get('@modalities').type('Ct');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
// TODO: Why are we facing some inconsistency with this result? ¯\_(ツ)_/¯
|
||||
expect($list.length).to.be.eq(9);
|
||||
expect($list).to.contain('MR');
|
||||
expect($list.length).to.be.greaterThan(1);
|
||||
expect($list).to.contain('CT');
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
TODO: Currently broken in dicomweb-server
|
||||
|
||||
it('searches Description with exact string', function() {
|
||||
cy.get('@StudyDescription').type('CHEST');
|
||||
cy.get('@StudyDescription').type('PETCT');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(2);
|
||||
expect($list).to.contain('CHEST');
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('PETCT');
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it('changes Rows per page and checks the study count', function() {
|
||||
//Show Rows per page options
|
||||
@@ -128,52 +131,60 @@ describe('OHIF Study List', function() {
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(3);
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('Juno');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches MRN with with exact string', function() {
|
||||
cy.get('@patientNameOrMRN').type('ProstateX-0000');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(6);
|
||||
expect($list).to.contain('ProstateX-0000');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Modality with exact string', function() {
|
||||
cy.get('@accessionModalityDescription').type('MR');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
// TODO: Why are we facing some inconsistency with this result? ¯\_(ツ)_/¯
|
||||
expect($list.length).to.be.eq(9);
|
||||
expect($list).to.contain('MR');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Accession with exact string', function() {
|
||||
cy.get('@accessionModalityDescription').type('0000481914');
|
||||
cy.get('@patientNameOrMRN').type('Juno');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('0000481914');
|
||||
expect($list).to.contain('Juno');
|
||||
});
|
||||
});
|
||||
|
||||
it('searches Description with exact string', function() {
|
||||
cy.get('@accessionModalityDescription').type('CHEST');
|
||||
it('searches Modality with exact string', function() {
|
||||
cy.get('@accessionModalityDescription').type('CT');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(2);
|
||||
expect($list).to.contain('CHEST');
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('CT');
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
/*
|
||||
TODO: Currently broken in dicomweb-server
|
||||
it('searches Accession with exact string', function() {
|
||||
cy.get('@accessionModalityDescription').type('0000155811');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('0000155811');
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO: Currently broken in dicomweb-server
|
||||
it('searches Description with exact string', function() {
|
||||
|
||||
cy.get('@accessionModalityDescription').type('PETCT');
|
||||
//Wait result list to be displayed
|
||||
cy.waitStudyList();
|
||||
cy.get('@searchResult').should($list => {
|
||||
expect($list.length).to.be.eq(1);
|
||||
expect($list).to.contain('PETCT');
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
it('changes Rows per page and checks the study count', function() {
|
||||
//Show Rows per page options
|
||||
const pageRows = [25, 50, 100];
|
||||
|
||||
@@ -3,12 +3,12 @@ describe('OHIF Study Viewer Page', function() {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
cy.initCommonElementsAliases();
|
||||
cy.resetViewport();
|
||||
cy.resetViewport().wait(50);
|
||||
});
|
||||
|
||||
it('checks if series thumbnails are being displayed', function() {
|
||||
@@ -50,10 +50,10 @@ describe('OHIF Study Viewer Page', function() {
|
||||
.click();
|
||||
|
||||
// Click "Relabel"
|
||||
cy.get('.btnAction')
|
||||
cy.get('.btnAction', { timeout: 10000 })
|
||||
.first()
|
||||
.contains('Relabel')
|
||||
.click();
|
||||
.click().should('be.visible');
|
||||
|
||||
// Search for "Bone"
|
||||
cy.get('.searchInput').type('Bone');
|
||||
@@ -68,11 +68,20 @@ describe('OHIF Study Viewer Page', function() {
|
||||
|
||||
// Verify if 'Bone' label was added
|
||||
cy.get('.measurementLocation').should('contain.text', 'Bone');
|
||||
|
||||
// Remove the measurement we just added
|
||||
cy.get('.btnAction')
|
||||
.last()
|
||||
.contains('Delete')
|
||||
.click()
|
||||
|
||||
// Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||
});
|
||||
|
||||
/*
|
||||
TODO: Not sure why this is failing
|
||||
it('checks if Description can be added to measurement item under Measurements panel', () => {
|
||||
cy.addLengthMeasurement(); //Adding measurement in the viewport
|
||||
cy.get('@measurementsBtn').click();
|
||||
@@ -94,7 +103,19 @@ describe('OHIF Study Viewer Page', function() {
|
||||
|
||||
//Verify if descriptionText was added
|
||||
cy.get('.measurementLocation').should('contain.text', descriptionText);
|
||||
|
||||
// Remove the measurement we just added
|
||||
cy.get('.btnAction')
|
||||
.last()
|
||||
.contains('Delete')
|
||||
.click()
|
||||
|
||||
// Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
cy.get('@measurementsPanel').should('not.be.enabled');
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
it('checks if measurement item can be deleted through the context menu on the viewport', function() {
|
||||
cy.addLengthMeasurement([100, 100], [200, 100]); //Adding measurement in the viewport
|
||||
@@ -108,6 +129,7 @@ describe('OHIF Study Viewer Page', function() {
|
||||
.trigger('mouseup', x1, y1, {
|
||||
which: 3,
|
||||
})
|
||||
.wait(300)
|
||||
.then(() => {
|
||||
//Contextmenu is visible
|
||||
cy.get('.ToolContextMenu').should('be.visible');
|
||||
@@ -122,12 +144,9 @@ describe('OHIF Study Viewer Page', function() {
|
||||
cy.get('@measurementsBtn').click();
|
||||
|
||||
//Verify measurements was removed from panel
|
||||
cy.get('.measurementItem');
|
||||
|
||||
// TODO: We need a seperate test server for this to work.
|
||||
// As anyone can save measurements on a different slice.
|
||||
// .should('not.exist')
|
||||
// .log('Annotation removed with success');
|
||||
cy.get('.measurementItem')
|
||||
.should('not.exist')
|
||||
.log('Annotation successfully removed');
|
||||
|
||||
//Close panel
|
||||
cy.get('@measurementsBtn').click();
|
||||
@@ -228,7 +247,7 @@ describe('OHIF Study Viewer Page', function() {
|
||||
});
|
||||
|
||||
const expectedText =
|
||||
'Ser: 5Img: 1 12/12512 x 512Loc: -15.40 mm Thick: 4.00 mm'; //'Img: 13 13/13';
|
||||
'Ser: 2Img: 13 13/13512 x 512Loc: 18.40 mm Thick: 3.00 mm'; //'Img: 13 13/13';
|
||||
cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText);
|
||||
});
|
||||
|
||||
@@ -246,7 +265,8 @@ describe('OHIF Study Viewer Page', function() {
|
||||
.trigger('mousemove', 'center', { which: 3 })
|
||||
.trigger('mouseup');
|
||||
|
||||
const expectedText = 'Zoom: 301%';
|
||||
|
||||
const expectedText = 'Zoom: 442%';
|
||||
cy.get('@viewportInfoBottomRight').should('contains.text', expectedText);
|
||||
});
|
||||
|
||||
@@ -301,6 +321,6 @@ describe('OHIF Study Viewer Page', function() {
|
||||
|
||||
//close modal
|
||||
cy.get('[data-cy="close-button"]').click();
|
||||
cy.get('@aboutOverlay').should('not.be.enabled');
|
||||
cy.get('@aboutOverlay').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -184,7 +184,7 @@ describe('OHIF User Preferences', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
cy.initCommonElementsAliases();
|
||||
|
||||
// Check if application is in Spanish
|
||||
@@ -217,7 +217,7 @@ describe('OHIF User Preferences', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -583,7 +583,7 @@ describe('OHIF User Preferences', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Temporarily disabling as we transition to containerized PACS for E2E tests
|
||||
|
||||
describe('OHIF HTML Extension', () => {
|
||||
before(() => {
|
||||
cy.openStudyInViewer(
|
||||
'1.2.826.0.13854362241694438965858641723883466450351448'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
// TODO -> Commented these out until we get a seperate temporary PACS running on the CI.
|
||||
@@ -79,3 +82,4 @@ describe('OHIF HTML Extension', () => {
|
||||
// cy.screenshot('PDF Extension - Should load PDF file');
|
||||
// });
|
||||
// });
|
||||
*/
|
||||
@@ -1,3 +1,6 @@
|
||||
/*
|
||||
Temporarily disabling as we transition to containerized PACS for E2E tests
|
||||
|
||||
describe('OHIF Microscopy Extension', () => {
|
||||
before(() => {
|
||||
cy.openStudyModality('SM');
|
||||
@@ -27,3 +30,4 @@ describe('OHIF Microscopy Extension', () => {
|
||||
cy.screenshot('Microscopy Extension - Should display loaded canvas');
|
||||
});
|
||||
});
|
||||
*/
|
||||
@@ -3,10 +3,14 @@ describe('OHIF VTK Extension', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1'
|
||||
);
|
||||
cy.expectMinimumThumbnails(7);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
|
||||
// TODO: Added 1s wait because we are loading initial series list
|
||||
// from QIDO-RS, which is breaking some cypress checks
|
||||
|
||||
|
||||
//Waiting for the desired thumbnail content to be displayed
|
||||
cy.get('[data-cy="thumbnail-list"]').should($list => {
|
||||
cy.get('[data-cy="thumbnail-list"]').wait(1000).should($list => {
|
||||
expect($list).to.contain('CT WB 5.0 B35f');
|
||||
});
|
||||
|
||||
@@ -15,7 +19,7 @@ describe('OHIF VTK Extension', () => {
|
||||
// has data from a drag-n-drop
|
||||
// Drag and drop third thumbnail into first viewport
|
||||
cy.get('[data-cy="thumbnail-list"]')
|
||||
.contains('CT WB 5.0 B35f')
|
||||
.eq(2)
|
||||
.drag('.viewport-drop-target');
|
||||
|
||||
//Select 2D MPR button
|
||||
|
||||
@@ -3,7 +3,7 @@ describe('OHIF Save Measurements', function() {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ describe('Visual Regression - OHIF Cornerstone Hotkeys', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ describe('Visual Regression - OHIF Cornerstone Toolbar', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -13,7 +13,7 @@ describe('Visual Regression - OHIF Cornerstone Toolbar', () => {
|
||||
});
|
||||
|
||||
it('checks if Pan tool will move the image inside the viewport', () => {
|
||||
//Click on button and vefiry if icon is active on toolbar
|
||||
//Click on button and verify if icon is active on toolbar
|
||||
cy.get('@panBtn')
|
||||
.click()
|
||||
.then($panBtn => {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ describe('Visual Regression - OHIF Download Snapshot File', () => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
+4
@@ -1,3 +1,6 @@
|
||||
/*
|
||||
Temporarily disabling as we transition to containerized PACS for E2E tests
|
||||
|
||||
describe('Visual Regression - OHIF Microscopy Extension', () => {
|
||||
before(() => {
|
||||
cy.openStudyModality('SM');
|
||||
@@ -20,3 +23,4 @@ describe('Visual Regression - OHIF Microscopy Extension', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
*/
|
||||
+4
@@ -1,3 +1,6 @@
|
||||
/*
|
||||
Temporarily disabling as we transition to containerized PACS for E2E tests
|
||||
|
||||
describe('Visual Regression - OHIF PDF Extension', () => {
|
||||
before(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
@@ -20,3 +23,4 @@ describe('Visual Regression - OHIF PDF Extension', () => {
|
||||
cy.percyCanvasSnapshot('PDF Extension - Should load PDF file');
|
||||
});
|
||||
});
|
||||
*/
|
||||
+4
@@ -1,3 +1,6 @@
|
||||
/*
|
||||
Temporarily disabling as we transition to containerized PACS for E2E tests
|
||||
|
||||
describe('Visual Regression - OHIF VTK Extension', () => {
|
||||
before(() => {
|
||||
cy.checkStudyRouteInViewer(
|
||||
@@ -91,3 +94,4 @@ describe('Visual Regression - OHIF VTK Extension', () => {
|
||||
cy.percyCanvasSnapshot('VTK Rotate tool - Should rotate image');
|
||||
});
|
||||
});
|
||||
*/
|
||||
@@ -13,7 +13,7 @@ describe('Visual Regression - OHIF Routes', function() {
|
||||
);
|
||||
|
||||
cy.server();
|
||||
cy.route('GET', '**/ TESTStudy; /**').as('getTESTStudy');
|
||||
cy.route('GET', '*TESTStudy; /**').as('getTESTStudy');
|
||||
|
||||
cy.wait('@getTESTStudy.all');
|
||||
cy.get('@getTESTStudy').should($route => {
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ describe('Visual Regression - OHIF Study Viewer Page', function() {
|
||||
cy.checkStudyRouteInViewer(
|
||||
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
|
||||
);
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -31,6 +31,6 @@ describe('Visual Regression - OHIF Study Viewer Page', function() {
|
||||
|
||||
//close modal
|
||||
cy.get('[data-cy="close-button"]').click();
|
||||
cy.get('@aboutOverlay').should('not.be.enabled');
|
||||
cy.get('@aboutOverlay').should('not.exist');
|
||||
});
|
||||
});
|
||||
+13
-3
@@ -9,6 +9,11 @@ describe('Visual Regression - OHIF User Preferences', () => {
|
||||
cy.openPreferences();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Close User Preferences modal
|
||||
cy.closePreferences();
|
||||
});
|
||||
|
||||
it('checks displayed information on User Preferences modal', function() {
|
||||
// Go go hotkeys tab
|
||||
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
|
||||
@@ -52,7 +57,7 @@ describe('Visual Regression - OHIF User Preferences', () => {
|
||||
context('Study Viewer Page', function() {
|
||||
before(() => {
|
||||
cy.openStudyInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
|
||||
cy.expectMinimumThumbnails(5);
|
||||
cy.expectMinimumThumbnails(3);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -64,6 +69,11 @@ describe('Visual Regression - OHIF User Preferences', () => {
|
||||
cy.openPreferences();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Close User Preferences modal
|
||||
cy.closePreferences();
|
||||
});
|
||||
|
||||
it('checks displayed information on User Preferences modal', function() {
|
||||
// Go go hotkeys tab
|
||||
cy.selectPreferencesTab('@userPreferencesHotkeysTab');
|
||||
@@ -150,13 +160,13 @@ describe('Visual Regression - OHIF User Preferences', () => {
|
||||
|
||||
// Set new hotkey for 'Next Image Viewport' function
|
||||
cy.setNewHotkeyShortcutOnUserPreferencesModal(
|
||||
'Next Image Viewport',
|
||||
'Next Viewport',
|
||||
'{shift}{rightarrow}'
|
||||
);
|
||||
|
||||
// Set new hotkey for 'Previous Image Viewport' function
|
||||
cy.setNewHotkeyShortcutOnUserPreferencesModal(
|
||||
'Previous Image Viewport',
|
||||
'Previous Viewport',
|
||||
'{shift}{leftarrow}'
|
||||
);
|
||||
|
||||
|
||||
@@ -14,20 +14,22 @@ let percyHealthCheck = require('@percy/cypress/task');
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
on('before:browser:launch', (browser = {}, args) => {
|
||||
on('before:browser:launch', (browser = {}, launchOptions) => {
|
||||
if (browser.name === 'chrome') {
|
||||
// `args` is an araay of all the arguments
|
||||
// that will be passed to Chrome when it launchers
|
||||
args.push('--start-fullscreen');
|
||||
launchOptions.args.push('--start-fullscreen');
|
||||
|
||||
// whatever you return here becomes the new args
|
||||
return args;
|
||||
return launchOptions;
|
||||
}
|
||||
|
||||
if (browser.name === 'chromium') {
|
||||
const newArgs = args.filter(arg => arg !== '--disable-gpu');
|
||||
newArgs.push('--ignore-gpu-blacklist');
|
||||
return newArgs;
|
||||
launchOptions.args = newArgs;
|
||||
|
||||
return launchOptions;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -149,10 +149,11 @@ Cypress.Commands.add('addLine', (viewport, firstClick, secondClick) => {
|
||||
const [x1, y1] = firstClick;
|
||||
const [x2, y2] = secondClick;
|
||||
|
||||
// TODO: Added a wait which appears necessary in Cornerstone Tools >4?
|
||||
cy.wrap($viewport)
|
||||
.click(x1, y1, { force: true })
|
||||
.click(x1, y1).wait(100)
|
||||
.trigger('mousemove', { clientX: x2, clientY: y2 })
|
||||
.click(x2, y2, { force: true });
|
||||
.click(x2, y2).wait(100);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -242,6 +243,8 @@ Cypress.Commands.add('resetViewport', () => {
|
||||
cy.get('[data-cy="reset"]')
|
||||
.as('resetBtn')
|
||||
.click();
|
||||
|
||||
cy.get('.tooltip-toolbar-overlay').should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('imageZoomIn', () => {
|
||||
@@ -466,6 +469,24 @@ Cypress.Commands.add('openPreferences', () => {
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('closePreferences', () => {
|
||||
cy.log('Close User Preferences Modal');
|
||||
|
||||
cy.get('body').then(body => {
|
||||
// Close notification if displayed
|
||||
if (body.find('.sb-closeIcon').length > 0) {
|
||||
cy.get('.sb-closeIcon')
|
||||
.first()
|
||||
.click({ force: true });
|
||||
}
|
||||
|
||||
// Close User Preferences Modal (if displayed)
|
||||
if (body.find('.OHIFModal__header').length > 0) {
|
||||
cy.get('[data-cy="close-button"]').click({ force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('selectPreferencesTab', tabAlias => {
|
||||
cy.initPreferencesModalAliases();
|
||||
cy.get(tabAlias)
|
||||
@@ -527,8 +548,7 @@ Cypress.Commands.add(
|
||||
.parent()
|
||||
.find('input') // closest input to that label
|
||||
.type(shortcut, { force: true }); // Set new shortcut for that function
|
||||
})
|
||||
.blur();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "4.5.28",
|
||||
"version": "4.8.2",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -30,6 +30,7 @@
|
||||
"start": "yarn run dev",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'",
|
||||
"test:e2e:script-tag": "cypress run --config video=false --browser chrome --spec 'cypress/integration/common/**/*,cypress/integration/script-tag/**/*'",
|
||||
"test:e2e:local": "cypress run --config video=false --browser chrome --spec 'cypress/integration/common/**/*,cypress/integration/pwa/**/*'",
|
||||
"test:e2e:dist": "start-server-and-test test:e2e:serve http://localhost:3000 test:e2e:ci",
|
||||
"test:e2e:serve": "serve -n -l 3000 -s dist",
|
||||
@@ -48,18 +49,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^2.10.7",
|
||||
"@ohif/extension-cornerstone": "^2.9.6",
|
||||
"@ohif/core": "^2.12.1",
|
||||
"@ohif/extension-cornerstone": "^2.10.0",
|
||||
"@ohif/extension-debugging": "^0.1.6",
|
||||
"@ohif/extension-dicom-html": "^1.2.9",
|
||||
"@ohif/extension-dicom-html": "^1.3.0",
|
||||
"@ohif/extension-dicom-microscopy": "^0.51.3",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.6",
|
||||
"@ohif/extension-dicom-rt": "^0.4.9",
|
||||
"@ohif/extension-dicom-segmentation": "^0.4.7",
|
||||
"@ohif/extension-dicom-rt": "^0.6.1",
|
||||
"@ohif/extension-dicom-segmentation": "^0.5.0",
|
||||
"@ohif/extension-lesion-tracker": "^0.2.1",
|
||||
"@ohif/extension-vtk": "^1.8.0",
|
||||
"@ohif/extension-vtk": "^1.10.2",
|
||||
"@ohif/i18n": "^0.53.0",
|
||||
"@ohif/ui": "^1.5.7",
|
||||
"@ohif/ui": "^1.8.1",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^3.2.1",
|
||||
@@ -67,7 +68,7 @@
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.16.5",
|
||||
"dcmjs": "0.17.0",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"dicomweb-client": "^0.4.4",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -92,7 +93,7 @@
|
||||
"vtk.js": "^11.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^3.8.0",
|
||||
"cypress": "^6.0.0",
|
||||
"gh-pages": "2.0.1",
|
||||
"identity-obj-proxy": "3.0.x",
|
||||
"lodash": "4.17.15",
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
extensions: [],
|
||||
showStudyList: true,
|
||||
servers: {
|
||||
dicomWeb: [
|
||||
{
|
||||
name: 'dicomweb_server',
|
||||
wadoUriRoot: 'http://localhost:5985',
|
||||
qidoRoot: 'http://localhost:5985',
|
||||
wadoRoot: 'http://localhost:5985',
|
||||
qidoSupportsIncludeField: false,
|
||||
imageRendering: 'wadouri',
|
||||
thumbnailRendering: 'wadouri',
|
||||
enableStudyLazyLoad: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Extensions should be able to suggest default values for these?
|
||||
// Or we can require that these be explicitly set
|
||||
hotkeys: [
|
||||
// ~ Global
|
||||
{
|
||||
commandName: 'incrementActiveViewport',
|
||||
label: 'Next Viewport',
|
||||
keys: ['right'],
|
||||
},
|
||||
{
|
||||
commandName: 'decrementActiveViewport',
|
||||
label: 'Previous Viewport',
|
||||
keys: ['left'],
|
||||
},
|
||||
// Supported Keys: https://craig.is/killing/mice
|
||||
// ~ Cornerstone Extension
|
||||
{ commandName: 'rotateViewportCW', label: 'Rotate Right', keys: ['r'] },
|
||||
{ commandName: 'rotateViewportCCW', label: 'Rotate Left', keys: ['l'] },
|
||||
{ commandName: 'invertViewport', label: 'Invert', keys: ['i'] },
|
||||
{
|
||||
commandName: 'flipViewportVertical',
|
||||
label: 'Flip Horizontally',
|
||||
keys: ['h'],
|
||||
},
|
||||
{
|
||||
commandName: 'flipViewportHorizontal',
|
||||
label: 'Flip Vertically',
|
||||
keys: ['v'],
|
||||
},
|
||||
{ commandName: 'scaleUpViewport', label: 'Zoom In', keys: ['+'] },
|
||||
{ commandName: 'scaleDownViewport', label: 'Zoom Out', keys: ['-'] },
|
||||
{ commandName: 'fitViewportToWindow', label: 'Zoom to Fit', keys: ['='] },
|
||||
{ commandName: 'resetViewport', label: 'Reset', keys: ['space'] },
|
||||
// clearAnnotations
|
||||
{ commandName: 'nextImage', label: 'Next Image', keys: ['down'] },
|
||||
{ commandName: 'previousImage', label: 'Previous Image', keys: ['up'] },
|
||||
// firstImage
|
||||
// lastImage
|
||||
{
|
||||
commandName: 'previousViewportDisplaySet',
|
||||
label: 'Previous Series',
|
||||
keys: ['pagedown'],
|
||||
},
|
||||
{
|
||||
commandName: 'nextViewportDisplaySet',
|
||||
label: 'Next Series',
|
||||
keys: ['pageup'],
|
||||
},
|
||||
// ~ Cornerstone Tools
|
||||
{ commandName: 'setZoomTool', label: 'Zoom', keys: ['z'] },
|
||||
// ~ Window level presets
|
||||
{
|
||||
commandName: 'windowLevelPreset1',
|
||||
label: 'W/L Preset 1',
|
||||
keys: ['1'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset2',
|
||||
label: 'W/L Preset 2',
|
||||
keys: ['2'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset3',
|
||||
label: 'W/L Preset 3',
|
||||
keys: ['3'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset4',
|
||||
label: 'W/L Preset 4',
|
||||
keys: ['4'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset5',
|
||||
label: 'W/L Preset 5',
|
||||
keys: ['5'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset6',
|
||||
label: 'W/L Preset 6',
|
||||
keys: ['6'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset7',
|
||||
label: 'W/L Preset 7',
|
||||
keys: ['7'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset8',
|
||||
label: 'W/L Preset 8',
|
||||
keys: ['8'],
|
||||
},
|
||||
{
|
||||
commandName: 'windowLevelPreset9',
|
||||
label: 'W/L Preset 9',
|
||||
keys: ['9'],
|
||||
},
|
||||
],
|
||||
cornerstoneExtensionConfig: {},
|
||||
// Following property limits number of simultaneous series metadata requests.
|
||||
// For http/1.x-only servers, set this to 5 or less to improve
|
||||
// on first meaningful display in viewer
|
||||
// If the server is particularly slow to respond to series metadata
|
||||
// requests as it extracts the metadata from raw files everytime,
|
||||
// try setting this to even lower value
|
||||
// Leave it undefined for no limit, sutiable for HTTP/2 enabled servers
|
||||
// maxConcurrentMetadataRequests: 5,
|
||||
};
|
||||
@@ -5,9 +5,8 @@ import { Route, Switch } from 'react-router-dom';
|
||||
import { NProgress } from '@tanem/react-nprogress';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import { connect } from 'react-redux';
|
||||
import { ViewerbaseDragDropContext, ErrorBoundary } from '@ohif/ui';
|
||||
import { ViewerbaseDragDropContext, ErrorBoundary, asyncComponent, retryImport } from '@ohif/ui';
|
||||
import { SignoutCallbackComponent } from 'redux-oidc';
|
||||
import asyncComponent from './components/AsyncComponent.js';
|
||||
import * as RoutesUtil from './routes/routesUtil';
|
||||
|
||||
import NotFound from './routes/NotFound.js';
|
||||
@@ -18,7 +17,7 @@ import './theme-tide.css';
|
||||
// Contexts
|
||||
import AppContext from './context/AppContext';
|
||||
const CallbackPage = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "CallbackPage" */ './routes/CallbackPage.js')
|
||||
retryImport(() => import(/* webpackChunkName: "CallbackPage" */ './routes/CallbackPage.js'))
|
||||
);
|
||||
|
||||
class OHIFStandaloneViewer extends Component {
|
||||
@@ -203,10 +202,10 @@ class OHIFStandaloneViewer extends Component {
|
||||
{match === null ? (
|
||||
<></>
|
||||
) : (
|
||||
<ErrorBoundary context={match.url}>
|
||||
<Component match={match} location={this.props.location} />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
<ErrorBoundary context={match.url}>
|
||||
<Component match={match} location={this.props.location} />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
</CSSTransition>
|
||||
)}
|
||||
</Route>
|
||||
|
||||
@@ -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,41 +1,36 @@
|
||||
import './SidePanel.css';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
class SidePanel extends Component {
|
||||
static propTypes = {
|
||||
from: PropTypes.string.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node,
|
||||
width: PropTypes.string,
|
||||
};
|
||||
const SidePanel = ({ from, isOpen, children, width }) => {
|
||||
const fromSideClass = from === 'right' ? 'from-right' : 'from-left';
|
||||
|
||||
render() {
|
||||
const fromSideClass =
|
||||
this.props.from === 'right' ? 'from-right' : 'from-left';
|
||||
const styles = width
|
||||
? {
|
||||
maxWidth: width,
|
||||
marginRight: isOpen ? '0' : Number.parseInt(width) * -1,
|
||||
}
|
||||
: {};
|
||||
|
||||
const styles = this.props.width
|
||||
? {
|
||||
maxWidth: this.props.width,
|
||||
marginRight: this.props.isOpen
|
||||
? '0'
|
||||
: Number.parseInt(this.props.width) * -1,
|
||||
}
|
||||
: {};
|
||||
return (
|
||||
<section
|
||||
style={styles}
|
||||
className={classNames('sidepanel', fromSideClass, {
|
||||
'is-open': isOpen,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
style={styles}
|
||||
className={classNames('sidepanel', fromSideClass, {
|
||||
'is-open': this.props.isOpen,
|
||||
})}
|
||||
>
|
||||
{this.props.children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
SidePanel.propTypes = {
|
||||
from: PropTypes.string.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node,
|
||||
width: PropTypes.string,
|
||||
};
|
||||
|
||||
export default SidePanel;
|
||||
@@ -12,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
|
||||
|
||||
const { loadAndCacheDerivedDisplaySets } = utils;
|
||||
|
||||
const ViewportGrid = function(props) {
|
||||
const ViewportGrid = function (props) {
|
||||
const {
|
||||
activeViewportIndex,
|
||||
availablePlugins,
|
||||
@@ -48,6 +48,7 @@ const ViewportGrid = function(props) {
|
||||
title: 'Error loading derived display set:',
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
error,
|
||||
autoClose: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,6 +83,8 @@ class Viewer extends Component {
|
||||
disassociate: this.disassociateStudy,
|
||||
},
|
||||
});
|
||||
|
||||
this._getActiveViewport = this._getActiveViewport.bind(this);
|
||||
}
|
||||
|
||||
state = {
|
||||
@@ -214,6 +216,10 @@ class Viewer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
_getActiveViewport() {
|
||||
return this.props.viewports[this.props.activeViewportIndex];
|
||||
}
|
||||
|
||||
render() {
|
||||
let VisiblePanelLeft, VisiblePanelRight;
|
||||
const panelExtensions = extensionManager.modules[MODULE_TYPES.PANEL];
|
||||
@@ -314,11 +320,11 @@ class Viewer extends Component {
|
||||
activeIndex={this.props.activeViewportIndex}
|
||||
/>
|
||||
) : (
|
||||
<ConnectedStudyBrowser
|
||||
studies={this.state.thumbnails}
|
||||
studyMetadata={this.props.studies}
|
||||
/>
|
||||
)}
|
||||
<ConnectedStudyBrowser
|
||||
studies={this.state.thumbnails}
|
||||
studyMetadata={this.props.studies}
|
||||
/>
|
||||
)}
|
||||
</SidePanel>
|
||||
</ErrorBoundaryDialog>
|
||||
|
||||
@@ -341,6 +347,8 @@ class Viewer extends Component {
|
||||
viewports={this.props.viewports}
|
||||
studies={this.props.studies}
|
||||
activeIndex={this.props.activeViewportIndex}
|
||||
activeViewport={this.props.viewports[this.props.activeViewportIndex]}
|
||||
getActiveViewport={this._getActiveViewport}
|
||||
/>
|
||||
)}
|
||||
</SidePanel>
|
||||
@@ -363,7 +371,7 @@ export default withDialog(Viewer);
|
||||
* @param {Study[]} studies
|
||||
* @param {DisplaySet[]} studies[].displaySets
|
||||
*/
|
||||
const _mapStudiesToThumbnails = function(studies) {
|
||||
const _mapStudiesToThumbnails = function (studies) {
|
||||
return studies.map(study => {
|
||||
const { StudyInstanceUID } = study;
|
||||
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import asyncComponent from '../components/AsyncComponent.js';
|
||||
|
||||
import { asyncComponent, retryImport } from '@ohif/ui';
|
||||
import OHIF from '@ohif/core';
|
||||
|
||||
const { urlUtil: UrlUtil } = OHIF.utils;
|
||||
|
||||
// Dynamic Import Routes (CodeSplitting)
|
||||
const IHEInvokeImageDisplay = asyncComponent(() =>
|
||||
import(
|
||||
/* webpackChunkName: "IHEInvokeImageDisplay" */ './IHEInvokeImageDisplay.js'
|
||||
retryImport(() =>
|
||||
import(/* webpackChunkName: "IHEInvokeImageDisplay" */ './IHEInvokeImageDisplay.js')
|
||||
)
|
||||
);
|
||||
const ViewerRouting = asyncComponent(() =>
|
||||
import(/* webpackChunkName: "ViewerRouting" */ './ViewerRouting.js')
|
||||
retryImport(() => import(/* webpackChunkName: "ViewerRouting" */ './ViewerRouting.js'))
|
||||
);
|
||||
|
||||
const StudyListRouting = asyncComponent(() =>
|
||||
import(
|
||||
retryImport(() => import(
|
||||
/* webpackChunkName: "StudyListRouting" */ '../studylist/StudyListRouting.js'
|
||||
)
|
||||
))
|
||||
);
|
||||
const StandaloneRouting = asyncComponent(() =>
|
||||
import(
|
||||
retryImport(() => import(
|
||||
/* webpackChunkName: "ConnectedStandaloneRouting" */ '../connectedComponents/ConnectedStandaloneRouting.js'
|
||||
)
|
||||
))
|
||||
);
|
||||
const ViewerLocalFileData = asyncComponent(() =>
|
||||
import(
|
||||
retryImport(() => import(
|
||||
/* webpackChunkName: "ViewerLocalFileData" */ '../connectedComponents/ViewerLocalFileData.js'
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
const reload = () => window.location.reload();
|
||||
|
||||
Reference in new issue
Block a user