Compare commits

...
Author SHA1 Message Date
ohif-bot 225da81135 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.13
 - @ohif/core@2.3.7
 - @ohif/viewer@3.4.13
2020-01-30 18:33:22 +00:00
Rodrigo AntinarelliandDanny Brown 686d12da5c fix: Set VTK viewport as active by interaction (#1139)
* fix: Set VTK viewport as active by interaction

* feat: listen for vtkscrollevent in wrapper component

* fix: definitions can skip empty storeContexts key

* hoc to set/pass in commandsManager

* Bump minimum react-vtkjs-viewport version to leverage new event

* Simplify to use onScroll event instead of passing down commandsManager to base component

* fix: make sure we include @JamesAPetts bug fix

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-01-30 13:26:06 -05:00
ohif-bot c22756051b chore(release): publish [skip ci]
- @ohif/viewer@3.4.12
2020-01-30 03:46:42 +00:00
8fdfcc0c7d chore: Remove unused dependency on favicons-webpack-plugin. (#1351)
This dependency depends on the `favicons` library which depends on
`sharp` which depends on a native library.

This complicates the build process as the library is built from source
because there's no binary available for my macOS Catalina
installation. As this is a common developer setup I expect others to
run into this issue as well, which is why I opted to fix this issue.

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
2020-01-29 22:43:15 -05:00
ohif-bot 44ab65c981 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.12
 - @ohif/i18n@0.52.5
 - @ohif/ui@1.1.9
 - @ohif/viewer@3.4.11
2020-01-30 03:42:31 +00:00
b9574b6efc fix: download tool fixes & improvements (#1235)
* fix: download tool fixes & improvements

* fix filename error

* Create variable to track erros

* Fix small console error

* Fix all conflicts and merge changes from latest master with this PR's improvements

* Small improvement on select and textinput labels

* Add new icon for unlink

* Refactor on download image modal

* Adding loading screen

* Fix translation issue

* Fixing aspect Ratio and E2E tests

* Allow empty value without setting to 0

* Remove eslint comments

* Fixing typos

Co-authored-by: Gustavo André Lelis <galelis@gmail.com>
Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-01-29 22:39:44 -05:00
ohif-bot 96a8e26786 chore(release): publish [skip ci]
- @ohif/viewer@3.4.10
2020-01-28 21:32:03 +00:00
Mirna SilvaandDanny Brown 80f5b23622 test: 1316 - E2E tests for /local (#1379)
* 1316 - E2E tests for /local

* fix test title

* update yarn.lock

* Increased timeout for thumbnails

* Removing PDF file test from script-tag server

Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
2020-01-28 16:29:17 -05:00
ohif-bot 2b4950e954 chore(release): publish [skip ci]
- @ohif/viewer@3.4.9
2020-01-28 19:18:20 +00:00
dependabot[bot] e16f14089d chore(deps-dev): bump lodash from 4.17.11 to 4.17.15 in /platform/viewer (#1403)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.15.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.15)

Signed-off-by: dependabot[bot] <support@github.com>
2020-01-28 14:15:59 -05:00
Danny Brown c3807a89ed chore: exclude extension discussion 2020-01-28 12:46:05 -05:00
Danny Brown 049ef4a666 chore: scare away stale bot 2020-01-28 12:43:07 -05:00
ohif-bot d5fa880a29 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.11
 - @ohif/core@2.3.6
 - @ohif/viewer@3.4.8
2020-01-28 14:03:08 +00:00
Erik Ziegler 1b9a6db73f Handle Palette Color images where LUT has been returned as InlineBinary (#1350) (#1401) 2020-01-28 15:01:13 +01:00
ohif-bot ed2692908e chore(release): publish [skip ci]
- @ohif/viewer@3.4.7
2020-01-28 12:54:21 +00:00
Joël Spaltenstein 0c6c5a5d51 OIDC Initiating Login from a Third Party
https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
2020-01-28 13:50:54 +01:00
ohif-bot b449f8eb0e chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.2.2
 - @ohif/extension-vtk@1.2.10
 - @ohif/core@2.3.5
 - @ohif/viewer@3.4.6
2020-01-28 12:41:49 +00:00
Erik Ziegler cf5160fb13 Add DICOM Group 6xxx Overlay Plane rendering (#1402)
* Add DICOM Group 6xxx Overlay Plane rendering

* Bump to cornerstone-tools 4.12.0 to support DICOM 6xxx Overlays
2020-01-28 13:40:03 +01:00
ohif-bot 4c1c91c006 chore(release): publish [skip ci]
- @ohif/viewer@3.4.5
2020-01-27 16:10:06 +00:00
Mirna Silva 9ed39c149b chore: Removing dependency ffmpeg-installer (#1397)
Fixes FOSSA warning/failure
2020-01-27 11:06:51 -05:00
ohif-bot b3f0fe029d chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.9
 - @ohif/core@2.3.4
 - @ohif/viewer@3.4.4
2020-01-27 09:14:56 +00:00
Erik Ziegler a1b78afdb9 Add palette color cache object back to core (fix #1350) (#1395) 2020-01-27 10:12:07 +01:00
Mirna Silva 3d3cfb1b41 Merge pull request #1393 from mirnasilva/fix_NewPatientSaveMeasurement
Fix on CI - Choosing another Patient Study
2020-01-24 16:17:10 -03:00
Mirna Silva b812fe03d2 Fix on CI - Changing to another Patient Study 2020-01-24 15:56:25 -03:00
ohif-bot 84f087bca4 chore(release): publish [skip ci]
- @ohif/extension-vtk@1.2.8
 - @ohif/core@2.3.3
 - @ohif/viewer@3.4.3
2020-01-24 10:33:21 +00:00
Igor Octaviano a7e612eb88 refactor: 💡 Clean up JSON launch with DICOMWeb server (#1388)
* refactor: 💡 Clean up JSON launch with DICOMWeb server

Clean up JSON launch with DICOMWeb server

✅ Closes: #1387

* Remove test file

* parse seriesinstanceuids in query

* Add default studies

* Remove test file

* Cleanup retrievedata changes

* Set active server
2020-01-24 10:31:22 +00:00
Mirna Silva 9e3afda2f1 Merge pull request #1384 from mirnasilva/percy-ci-fix
Visual tests: Fixed ci config for visual tests (percy)
2020-01-20 11:13:24 -03:00
Mirna Silva 4d7a91a584 Merge branch 'master' into percy-ci-fix 2020-01-17 20:48:51 -03:00
Mirna Silva 1dc320edcd Fixed ci config for visual tests 2020-01-17 20:44:45 -03:00
52 changed files with 1168 additions and 1182 deletions

No files matched your search

+1 -2
View File
@@ -459,8 +459,7 @@ workflows:
working_directory: platform/viewer working_directory: platform/viewer
build: npx cross-env QUICK_BUILD=true yarn run build build: npx cross-env QUICK_BUILD=true yarn run build
# start server --> verify running --> percy + chrome + cypress # start server --> verify running --> percy + chrome + cypress
start: yarn run test:e2e:dist command: yarn run test:e2e:dist
spec: 'cypress/integration/visual-regression/**/*'
cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}'
no-workspace: true # Don't persist workspace no-workspace: true # Don't persist workspace
post-steps: post-steps:
+2 -1
View File
@@ -12,10 +12,11 @@ exemptLabels:
- 'Task: CI/Tooling :robot:' - 'Task: CI/Tooling :robot:'
- 'Task: Docs 📖' - 'Task: Docs 📖'
- 'Task: Docs :book:' - 'Task: Docs :book:'
- 'Task: Refactor 🛠' - 'Task: Refactor :hammer_and_wrench:'
- 'Task: Tests :microscope:' - 'Task: Tests :microscope:'
- 'PR: Awaiting Review 👀' - 'PR: Awaiting Review 👀'
- 'Triage :white_flag:' - 'Triage :white_flag:'
- 'Extension: Discussion'
- 'Announcement 🎉' - 'Announcement 🎉'
# Label to use when marking an issue as stale # Label to use when marking an issue as stale
staleLabel: 'Stale :baguette_bread:' staleLabel: 'Stale :baguette_bread:'
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.1...@ohif/extension-cornerstone@2.2.2) (2020-01-28)
**Note:** Version bump only for package @ohif/extension-cornerstone
## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.0...@ohif/extension-cornerstone@2.2.1) (2019-12-20) ## [2.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.2.0...@ohif/extension-cornerstone@2.2.1) (2019-12-20)
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-cornerstone", "name": "@ohif/extension-cornerstone",
"version": "2.2.1", "version": "2.2.2",
"description": "OHIF extension for Cornerstone", "description": "OHIF extension for Cornerstone",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -32,7 +32,7 @@
"@ohif/ui": "^0.50.0", "@ohif/ui": "^0.50.0",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.9.0", "cornerstone-tools": "^4.12.0",
"cornerstone-wado-image-loader": "^3.0.0", "cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.8.2", "dcmjs": "^0.8.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
+2
View File
@@ -98,6 +98,7 @@ export default function init({ servicesManager, configuration }) {
csTools.MagnifyTool, csTools.MagnifyTool,
csTools.StackScrollTool, csTools.StackScrollTool,
csTools.StackScrollMouseWheelTool, csTools.StackScrollMouseWheelTool,
csTools.OverlayTool,
], ],
}; };
@@ -179,4 +180,5 @@ export default function init({ servicesManager, configuration }) {
csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required csTools.setToolActive('StackScrollMouseWheel', {}); // TODO: Empty options should not be required
csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options csTools.setToolActive('PanMultiTouch', { pointers: 2 }); // TODO: Better error if no options
csTools.setToolActive('ZoomTouchPinch', {}); csTools.setToolActive('ZoomTouchPinch', {});
csTools.setToolEnabled('Overlay', {});
} }
+51
View File
@@ -3,6 +3,57 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.13](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.12...@ohif/extension-vtk@1.2.13) (2020-01-30)
### Bug Fixes
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
## [1.2.12](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.11...@ohif/extension-vtk@1.2.12) (2020-01-30)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.11](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.10...@ohif/extension-vtk@1.2.11) (2020-01-28)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.10](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.9...@ohif/extension-vtk@1.2.10) (2020-01-28)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.9](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.8...@ohif/extension-vtk@1.2.9) (2020-01-27)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.7...@ohif/extension-vtk@1.2.8) (2020-01-24)
**Note:** Version bump only for package @ohif/extension-vtk
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.6...@ohif/extension-vtk@1.2.7) (2020-01-08) ## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.2.6...@ohif/extension-vtk@1.2.7) (2020-01-08)
**Note:** Version bump only for package @ohif/extension-vtk **Note:** Version bump only for package @ohif/extension-vtk
+5 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/extension-vtk", "name": "@ohif/extension-vtk",
"version": "1.2.7", "version": "1.2.13",
"description": "OHIF extension for VTK.js", "description": "OHIF extension for VTK.js",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -49,12 +49,12 @@
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"react-vtkjs-viewport": "^0.3.9" "react-vtkjs-viewport": "^0.8.3"
}, },
"devDependencies": { "devDependencies": {
"@ohif/core": "^2.3.2", "@ohif/core": "^2.3.7",
"@ohif/ui": "^1.1.8", "@ohif/ui": "^1.1.9",
"cornerstone-tools": "^4.9.0", "cornerstone-tools": "^4.12.0",
"cornerstone-wado-image-loader": "^3.0.0", "cornerstone-wado-image-loader": "^3.0.0",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
"gh-pages": "^2.0.1", "gh-pages": "^2.0.1",
+3 -2
View File
@@ -1,6 +1,6 @@
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import { View2D } from 'react-vtkjs-viewport';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import VTKViewport from './VTKViewport';
const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions; const { setViewportActive, setViewportSpecificData } = OHIF.redux.actions;
@@ -18,6 +18,7 @@ const mapStateToProps = (state, ownProps) => {
const pluginDetails = viewportLayout.vtk || {}; const pluginDetails = viewportLayout.vtk || {};
return { return {
activeViewportIndex: state.viewports.activeViewportIndex,
layout: state.viewports.layout, layout: state.viewports.layout,
isActive, isActive,
...pluginDetails, ...pluginDetails,
@@ -75,6 +76,6 @@ const ConnectedVTKViewport = connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps, mapDispatchToProps,
mergeProps mergeProps
)(View2D); )(VTKViewport);
export default ConnectedVTKViewport; export default ConnectedVTKViewport;
+6
View File
@@ -77,6 +77,11 @@ class OHIFVTKViewport extends Component {
}), }),
viewportIndex: PropTypes.number, viewportIndex: PropTypes.number,
children: PropTypes.node, children: PropTypes.node,
onScroll: PropTypes.func,
};
static defaultProps = {
onScroll: () => {},
}; };
static id = 'OHIFVTKViewport'; static id = 'OHIFVTKViewport';
@@ -386,6 +391,7 @@ class OHIFVTKViewport extends Component {
this.state.paintFilterBackgroundImageData this.state.paintFilterBackgroundImageData
} }
viewportIndex={this.props.viewportIndex} viewportIndex={this.props.viewportIndex}
onScroll={this.props.onScroll}
/> />
)} )}
</div> </div>
+3
View File
@@ -0,0 +1,3 @@
.vtk-viewport-handler svg {
pointer-events: none;
}
+58
View File
@@ -0,0 +1,58 @@
import React, { useEffect, useCallback } from 'react';
import { View2D } from 'react-vtkjs-viewport';
import PropTypes from 'prop-types';
import './VTKViewport.css';
const VTKViewport = props => {
const style = { width: '100%', height: '100%', position: 'relative' };
const setViewportActiveHandler = useCallback(() => {
const { setViewportActive, viewportIndex, activeViewportIndex } = props;
if (viewportIndex !== activeViewportIndex) {
// set in Connected
setViewportActive();
}
});
useEffect(() => {
const handleScrollEvent = evt => {
const vtkViewportApiReference = props.onScroll(props.viewportIndex) || {};
const viewportUid = vtkViewportApiReference.uid;
const viewportWasScrolled = viewportUid === evt.detail.uid;
if (viewportWasScrolled) {
setViewportActiveHandler();
}
};
window.addEventListener('vtkscrollevent', handleScrollEvent);
return () =>
window.removeEventListener('vtkscrollevent', handleScrollEvent);
}, [props, props.onScroll, props.viewportIndex, setViewportActiveHandler]);
return (
<div
className="vtk-viewport-handler"
style={style}
onClick={setViewportActiveHandler}
>
<View2D {...props} />
</div>
);
};
VTKViewport.propTypes = {
setViewportActive: PropTypes.func.isRequired,
viewportIndex: PropTypes.number.isRequired,
activeViewportIndex: PropTypes.number.isRequired,
/* Receives viewportIndex */
onScroll: PropTypes.func,
};
VTKViewport.defaultProps = {
onScroll: () => {},
};
export default VTKViewport;
+9 -11
View File
@@ -103,6 +103,9 @@ const commandsModule = ({ commandsManager }) => {
} }
const actions = { const actions = {
getVtkApis: ({ index }) => {
return apis[index];
},
axial: async ({ viewports }) => { axial: async ({ viewports }) => {
const api = await _getActiveViewportVTKApi(viewports); const api = await _getActiveViewportVTKApi(viewports);
@@ -266,11 +269,12 @@ const commandsModule = ({ commandsManager }) => {
'crosshairsWidget' 'crosshairsWidget'
); );
const uid = api.uid;
const istyle = vtkInteractorStyleMPRCrosshairs.newInstance(); const istyle = vtkInteractorStyleMPRCrosshairs.newInstance();
api.setInteractorStyle({ api.setInteractorStyle({
istyle, istyle,
configuration: { apis, apiIndex }, configuration: { apis, apiIndex, uid },
}); });
}); });
}, },
@@ -296,55 +300,45 @@ const commandsModule = ({ commandsManager }) => {
}, },
enableRotateTool: { enableRotateTool: {
commandFn: actions.enableRotateTool, commandFn: actions.enableRotateTool,
storeContexts: [],
options: {}, options: {},
}, },
enableCrosshairsTool: { enableCrosshairsTool: {
commandFn: actions.enableCrosshairsTool, commandFn: actions.enableCrosshairsTool,
storeContexts: [],
options: {}, options: {},
}, },
enableLevelTool: { enableLevelTool: {
commandFn: actions.enableLevelTool, commandFn: actions.enableLevelTool,
storeContexts: [],
options: {}, options: {},
}, },
setBlendModeToComposite: { setBlendModeToComposite: {
commandFn: actions.setBlendModeToComposite, commandFn: actions.setBlendModeToComposite,
storeContexts: [],
options: { blendMode: BlendMode.COMPOSITE_BLEND }, options: { blendMode: BlendMode.COMPOSITE_BLEND },
}, },
setBlendModeToMaximumIntensity: { setBlendModeToMaximumIntensity: {
commandFn: actions.setBlendModeToMaximumIntensity, commandFn: actions.setBlendModeToMaximumIntensity,
storeContexts: [],
options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND }, options: { blendMode: BlendMode.MAXIMUM_INTENSITY_BLEND },
}, },
setBlendModeToMinimumIntensity: { setBlendModeToMinimumIntensity: {
commandFn: actions.setBlendMode, commandFn: actions.setBlendMode,
storeContexts: [],
options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND }, options: { blendMode: BlendMode.MINIMUM_INTENSITY_BLEND },
}, },
setBlendModeToAverageIntensity: { setBlendModeToAverageIntensity: {
commandFn: actions.setBlendMode, commandFn: actions.setBlendMode,
storeContexts: [],
options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND }, options: { blendMode: BlendMode.AVERAGE_INTENSITY_BLEND },
}, },
setSlabThickness: { setSlabThickness: {
// TODO: How do we pass in a function argument? // TODO: How do we pass in a function argument?
commandFn: actions.setSlabThickness, commandFn: actions.setSlabThickness,
storeContexts: [],
options: {}, options: {},
}, },
increaseSlabThickness: { increaseSlabThickness: {
commandFn: actions.changeSlabThickness, commandFn: actions.changeSlabThickness,
storeContexts: [],
options: { options: {
change: 3, change: 3,
}, },
}, },
decreaseSlabThickness: { decreaseSlabThickness: {
commandFn: actions.changeSlabThickness, commandFn: actions.changeSlabThickness,
storeContexts: [],
options: { options: {
change: -3, change: -3,
}, },
@@ -355,6 +349,10 @@ const commandsModule = ({ commandsManager }) => {
options: {}, options: {},
context: 'VIEWER', context: 'VIEWER',
}, },
getVtkApiForViewportIndex: {
commandFn: actions.getVtkApis,
context: 'VIEWER',
},
}; };
return { return {
+3 -2
View File
@@ -1,6 +1,7 @@
import asyncComponent from './asyncComponent.js'; import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js'; import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js'; import toolbarModule from './toolbarModule.js';
import withCommandsManager from './withCommandsManager.js';
// This feels weird // This feels weird
// import loadLocales from './loadLocales'; // import loadLocales from './loadLocales';
@@ -14,8 +15,8 @@ const vtkExtension = {
*/ */
id: 'vtk', id: 'vtk',
getViewportModule() { getViewportModule({ commandsManager }) {
return OHIFVTKViewport; return withCommandsManager(OHIFVTKViewport, commandsManager);
}, },
getToolbarModule() { getToolbarModule() {
return toolbarModule; return toolbarModule;
+18
View File
@@ -0,0 +1,18 @@
import React from 'react';
export default function withCommandsManager(Component, commandsManager = {}) {
return class WithCommandsManager extends React.Component {
render() {
return (
<Component
{...this.props}
onScroll={viewportIndex =>
commandsManager.runCommand('getVtkApiForViewportIndex', {
index: viewportIndex,
})
}
/>
);
}
};
}
+1 -1
View File
@@ -59,6 +59,7 @@
"copy-webpack-plugin": "^5.0.3", "copy-webpack-plugin": "^5.0.3",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"css-loader": "^3.2.0", "css-loader": "^3.2.0",
"cypress-file-upload": "^3.5.3",
"dotenv": "^8.1.0", "dotenv": "^8.1.0",
"eslint": "5.16.0", "eslint": "5.16.0",
"eslint-config-prettier": "^6.4.0", "eslint-config-prettier": "^6.4.0",
@@ -72,7 +73,6 @@
"eslint-plugin-react": "7.x", "eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "1.5.0", "eslint-plugin-react-hooks": "1.5.0",
"extract-css-chunks-webpack-plugin": "^4.5.4", "extract-css-chunks-webpack-plugin": "^4.5.4",
"favicons-webpack-plugin": "^1.0.2",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"husky": "^3.0.0", "husky": "^3.0.0",
"jest": "^24.8.0", "jest": "^24.8.0",
+43
View File
@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.3.7](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.6...@ohif/core@2.3.7) (2020-01-30)
### Bug Fixes
* Set VTK viewport as active by interaction ([#1139](https://github.com/OHIF/Viewers/issues/1139)) ([686d12d](https://github.com/OHIF/Viewers/commit/686d12da5c9d3d435b1e326c2a5caee36e2ed27c))
## [2.3.6](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.5...@ohif/core@2.3.6) (2020-01-28)
**Note:** Version bump only for package @ohif/core
## [2.3.5](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.4...@ohif/core@2.3.5) (2020-01-28)
**Note:** Version bump only for package @ohif/core
## [2.3.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.3...@ohif/core@2.3.4) (2020-01-27)
**Note:** Version bump only for package @ohif/core
## [2.3.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.2...@ohif/core@2.3.3) (2020-01-24)
**Note:** Version bump only for package @ohif/core
## [2.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.1...@ohif/core@2.3.2) (2020-01-06) ## [2.3.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.3.1...@ohif/core@2.3.2) (2020-01-06)
**Note:** Version bump only for package @ohif/core **Note:** Version bump only for package @ohif/core
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/core", "name": "@ohif/core",
"version": "2.3.2", "version": "2.3.7",
"description": "Generic business logic for web-based medical imaging applications", "description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team", "author": "OHIF Core Team",
"license": "MIT", "license": "MIT",
@@ -32,7 +32,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-tools": "^4.9.0", "cornerstone-tools": "^4.12.0",
"cornerstone-wado-image-loader": "^3.0.0", "cornerstone-wado-image-loader": "^3.0.0",
"dicom-parser": "^1.8.3" "dicom-parser": "^1.8.3"
}, },
+1 -1
View File
@@ -161,7 +161,7 @@ export class CommandsManager {
return; return;
} }
const { commandFn, storeContexts } = definition; const { commandFn, storeContexts = [] } = definition;
const definitionOptions = definition.options; const definitionOptions = definition.options;
let commandParams = {}; let commandParams = {};
@@ -11,6 +11,16 @@ const servers = (state = defaultState, action) => {
servers.forEach(s => (s.active = true)); servers.forEach(s => (s.active = true));
return { ...state, servers }; return { ...state, servers };
case 'ACTIVATE_SERVER': {
const newServer = { ...action.server, active: true };
const newServers = state.servers;
newServers.forEach(s => (s.active = false));
return {
...state,
servers: uniqBy([...newServers, newServer], 'wadoRoot'),
};
}
case 'SET_SERVERS': case 'SET_SERVERS':
return { ...state, servers: action.servers }; return { ...state, servers: action.servers };
@@ -1,3 +1,4 @@
import { api } from 'dicomweb-client';
import DICOMWeb from '../../../DICOMWeb'; import DICOMWeb from '../../../DICOMWeb';
const WADOProxy = { const WADOProxy = {
@@ -21,6 +22,43 @@ function parseFloatArray(obj) {
return result; return result;
} }
/**
* Simple cache schema for retrieved color palettes.
*/
const paletteColorCache = {
count: 0,
maxAge: 24 * 60 * 60 * 1000, // 24h cache?
entries: {},
isValidUID: function(paletteUID) {
return typeof paletteUID === 'string' && paletteUID.length > 0;
},
get: function(paletteUID) {
let entry = null;
if (this.entries.hasOwnProperty(paletteUID)) {
entry = this.entries[paletteUID];
// check how the entry is...
if (Date.now() - entry.time > this.maxAge) {
// entry is too old... remove entry.
delete this.entries[paletteUID];
this.count--;
entry = null;
}
}
return entry;
},
add: function(entry) {
if (this.isValidUID(entry.uid)) {
let paletteUID = entry.uid;
if (this.entries.hasOwnProperty(paletteUID) !== true) {
this.count++; // increment cache entry count...
}
entry.time = Date.now();
this.entries[paletteUID] = entry;
// @TODO: Add logic to get rid of old entries and reduce memory usage...
}
},
};
/** /**
* Create a plain JS object that describes a study (a study descriptor object) * Create a plain JS object that describes a study (a study descriptor object)
* @param {Object} server Object with server configuration parameters * @param {Object} server Object with server configuration parameters
@@ -299,40 +337,41 @@ async function makeSOPInstance(server, study, instance) {
return sopInstance; return sopInstance;
} }
/**
* Convert String to ArrayBuffer
*
* @param {String} str Input String
* @return {ArrayBuffer} Output converted ArrayBuffer
*/
function str2ab(str) {
const strLen = str.length;
const bytes = new Uint8Array(strLen);
for (let i = 0; i < strLen; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes.buffer;
}
function getPaletteColor(server, instance, tag, lutDescriptor) { function getPaletteColor(server, instance, tag, lutDescriptor) {
const numLutEntries = lutDescriptor[0]; const numLutEntries = lutDescriptor[0];
const bits = lutDescriptor[2]; const bits = lutDescriptor[2];
let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server);
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
// incorrect bulk data URIs
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
uri = uri.replace('http', 'https');
}
const config = {
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
headers: DICOMWeb.getAuthorizationHeader(server),
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
BulkDataURI: uri,
};
const readUInt16 = (byteArray, position) => { const readUInt16 = (byteArray, position) => {
return byteArray[position] + byteArray[position + 1] * 256; return byteArray[position] + byteArray[position + 1] * 256;
}; };
const arrayBufferToPaletteColorLUT = result => { const arrayBufferToPaletteColorLUT = arraybuffer => {
const arraybuffer = result[0];
const byteArray = new Uint8Array(arraybuffer); const byteArray = new Uint8Array(arraybuffer);
const lut = []; const lut = [];
for (let i = 0; i < numLutEntries; i++) { if (bits === 16) {
if (bits === 16) { for (let i = 0; i < numLutEntries; i++) {
lut[i] = readUInt16(byteArray, i * 2); lut[i] = readUInt16(byteArray, i * 2);
} else { }
} else {
for (let i = 0; i < numLutEntries; i++) {
lut[i] = byteArray[i]; lut[i] = byteArray[i];
} }
} }
@@ -340,7 +379,38 @@ function getPaletteColor(server, instance, tag, lutDescriptor) {
return lut; return lut;
}; };
return dicomWeb.retrieveBulkData(options).then(arrayBufferToPaletteColorLUT); if (instance[tag].BulkDataURI) {
let uri = WADOProxy.convertURL(instance[tag].BulkDataURI, server);
// TODO: Workaround for dcm4chee behind SSL-terminating proxy returning
// incorrect bulk data URIs
if (server.wadoRoot.indexOf('https') === 0 && !uri.includes('https')) {
uri = uri.replace('http', 'https');
}
const config = {
url: server.wadoRoot, //BulkDataURI is absolute, so this isn't used
headers: DICOMWeb.getAuthorizationHeader(server),
};
const dicomWeb = new api.DICOMwebClient(config);
const options = {
BulkDataURI: uri,
};
return dicomWeb
.retrieveBulkData(options)
.then(result => result[0])
.then(arrayBufferToPaletteColorLUT);
} else if (instance[tag].InlineBinary) {
const inlineBinaryData = atob(instance[tag].InlineBinary);
const arraybuf = str2ab(inlineBinaryData);
return arrayBufferToPaletteColorLUT(arraybuf);
}
throw new Error(
'Palette Color LUT was not provided as InlineBinary or BulkDataURI'
);
} }
/** /**
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.52.5](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.4...@ohif/i18n@0.52.5) (2020-01-30)
### Bug Fixes
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.3...@ohif/i18n@0.52.4) (2019-12-16) ## [0.52.4](https://github.com/OHIF/Viewers/compare/@ohif/i18n@0.52.3...@ohif/i18n@0.52.4) (2019-12-16)
**Note:** Version bump only for package @ohif/i18n **Note:** Version bump only for package @ohif/i18n
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/i18n", "name": "@ohif/i18n",
"version": "0.52.4", "version": "0.52.5",
"description": "Internationalization library for The OHIF Viewer", "description": "Internationalization library for The OHIF Viewer",
"author": "OHIF", "author": "OHIF",
"license": "MIT", "license": "MIT",
@@ -0,0 +1,14 @@
{
"emptyFilenameError": "The file name cannot be empty.",
"fileType": "File Type",
"filename": "File Name",
"formTitle": "Please specify the dimensions, filename, and desired type for the output image.",
"imageHeight": "Image height (px)",
"imagePreview": "Image Preview",
"imageWidth": "Image width (px)",
"keepAspectRatio": "Keep aspect ratio",
"loadingPreview": "Loading Image Preview...",
"minHeightError": "The minimum valid height is 100px.",
"minWidthError": "The minimum valid width is 100px.",
"showAnnotations": "Show Annotations"
}
+2
View File
@@ -7,6 +7,7 @@ import Header from './Header.json';
import MeasurementTable from './MeasurementTable.json'; import MeasurementTable from './MeasurementTable.json';
import StudyList from './StudyList.json'; import StudyList from './StudyList.json';
import UserPreferencesModal from './UserPreferencesModal.json'; import UserPreferencesModal from './UserPreferencesModal.json';
import ViewportDownloadForm from './ViewportDownloadForm.json';
export default { export default {
'en-US': { 'en-US': {
@@ -19,5 +20,6 @@ export default {
MeasurementTable, MeasurementTable,
StudyList, StudyList,
UserPreferencesModal, UserPreferencesModal,
ViewportDownloadForm,
}, },
}; };
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.9](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.8...@ohif/ui@1.1.9) (2020-01-30)
### Bug Fixes
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
## [1.1.8](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.7...@ohif/ui@1.1.8) (2020-01-08) ## [1.1.8](https://github.com/OHIF/Viewers/compare/@ohif/ui@1.1.7...@ohif/ui@1.1.8) (2020-01-08)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/ui", "name": "@ohif/ui",
"version": "1.1.8", "version": "1.1.9",
"description": "A set of React components for Medical Imaging Viewers", "description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -1,9 +1,16 @@
import React, { useEffect, useState, createRef } from 'react'; import React, {
useRef,
useCallback,
useEffect,
useState,
createRef,
} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import './ViewportDownloadForm.styl'; import './ViewportDownloadForm.styl';
import { TextInput, Select } from '@ohif/ui'; import { TextInput, Select, Icon } from '@ohif/ui';
import classnames from 'classnames';
const FILE_TYPE_OPTIONS = [ const FILE_TYPE_OPTIONS = [
{ {
@@ -17,6 +24,7 @@ const FILE_TYPE_OPTIONS = [
]; ];
const DEFAULT_FILENAME = 'image'; const DEFAULT_FILENAME = 'image';
const REFRESH_VIEWPORT_TIMEOUT = 1000;
const ViewportDownloadForm = ({ const ViewportDownloadForm = ({
activeViewport, activeViewport,
@@ -32,7 +40,7 @@ const ViewportDownloadForm = ({
maximumSize, maximumSize,
canvasClass, canvasClass,
}) => { }) => {
const [t] = useTranslation('Buttons'); const [t] = useTranslation('ViewportDownloadForm');
const [filename, setFilename] = useState(DEFAULT_FILENAME); const [filename, setFilename] = useState(DEFAULT_FILENAME);
const [fileType, setFileType] = useState('jpg'); const [fileType, setFileType] = useState('jpg');
@@ -44,6 +52,12 @@ const ViewportDownloadForm = ({
const [showAnnotations, setShowAnnotations] = useState(true); const [showAnnotations, setShowAnnotations] = useState(true);
const [keepAspect, setKeepAspect] = useState(true);
const [aspectMultiplier, setAspectMultiplier] = useState({
width: 1,
height: 1,
});
const [viewportElement, setViewportElement] = useState(); const [viewportElement, setViewportElement] = useState();
const [viewportElementDimensions, setViewportElementDimensions] = useState({ const [viewportElementDimensions, setViewportElementDimensions] = useState({
width: defaultSize, width: defaultSize,
@@ -62,41 +76,129 @@ const ViewportDownloadForm = ({
height: defaultSize, height: defaultSize,
}); });
// Cornerstone's `enable/disable` const [error, setError] = useState({
useEffect(() => { width: false,
enableViewport(viewportElement); height: false,
filename: false,
});
return () => { const hasError = Object.values(error).includes(true);
disableViewport(viewportElement);
};
}, [disableViewport, enableViewport, viewportElement]);
useEffect(() => { const refreshViewport = useRef(null);
const { width, height } = viewportElementDimensions;
const validSize = value => (value >= minimumSize ? value : minimumSize);
const loadAndUpdateViewports = async () => {
await loadImage(activeViewport, viewportElement, width, height);
toggleAnnotations(showAnnotations, viewportElement);
const { const downloadImage = () => {
dataUrl, downloadBlob(
width: viewportElementWidth, filename || DEFAULT_FILENAME,
height: viewportElementHeight, fileType,
} = await updateViewportPreview( viewportElement,
viewportElement, downloadCanvas.ref.current
downloadCanvas.ref.current, );
fileType };
/**
* @param {object} event - Input change event
* @param {string} dimension - "height" | "width"
*/
const onDimensionsChange = (event, dimension) => {
const oppositeDimension = dimension === 'height' ? 'width' : 'height';
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
const isEmpty = sanitizedTargetValue === '';
const newDimensions = { ...dimensions };
const updatedDimension = isEmpty
? ''
: Math.min(sanitizedTargetValue, maximumSize);
if (updatedDimension === dimensions[dimension]) {
return;
}
newDimensions[dimension] = updatedDimension;
if (keepAspect && newDimensions[oppositeDimension] !== '') {
newDimensions[oppositeDimension] = Math.round(
newDimensions[dimension] * aspectMultiplier[oppositeDimension]
); );
}
setViewportPreview(state => ({ // In current code, keepAspect is always `true`
// And we always start w/ a square width/height
setDimensions(newDimensions);
// Only update if value is non-empty
if (!isEmpty) {
setViewportElementDimensions(newDimensions);
setDownloadCanvas(state => ({
...state, ...state,
src: dataUrl, ...newDimensions,
width: validSize(viewportElementWidth),
height: validSize(viewportElementHeight),
})); }));
}
};
const error_messages = {
width: t('minWidthError'),
height: t('minHeightError'),
filename: t('emptyFilenameError'),
};
const renderErrorHandler = errorType => {
if (!error[errorType]) {
return null;
}
return <div className="input-error">{error_messages[errorType]}</div>;
};
const onKeepAspectToggle = () => {
const { width, height } = dimensions;
const aspectMultiplier = { ...aspectMultiplier };
if (!keepAspect) {
const base = Math.min(width, height);
aspectMultiplier.width = width / base;
aspectMultiplier.height = height / base;
setAspectMultiplier(aspectMultiplier);
}
setKeepAspect(!keepAspect);
};
const validSize = value => (value >= minimumSize ? value : minimumSize);
const loadAndUpdateViewports = useCallback(async () => {
const { width: scaledWidth, height: scaledHeight } = await loadImage(
activeViewport,
viewportElement,
dimensions.width,
dimensions.height
);
toggleAnnotations(showAnnotations, viewportElement);
const scaledDimensions = {
height: validSize(scaledHeight),
width: validSize(scaledWidth),
}; };
loadAndUpdateViewports(); setViewportElementDimensions(scaledDimensions);
setDownloadCanvas(state => ({
...state,
...scaledDimensions,
}));
const {
dataUrl,
width: viewportElementWidth,
height: viewportElementHeight,
} = await updateViewportPreview(
viewportElement,
downloadCanvas.ref.current,
fileType
);
setViewportPreview(state => ({
...state,
src: dataUrl,
width: validSize(viewportElementWidth),
height: validSize(viewportElementHeight),
}));
}, [ }, [
activeViewport, activeViewport,
viewportElement, viewportElement,
@@ -111,76 +213,96 @@ const ViewportDownloadForm = ({
viewportElementDimensions, viewportElementDimensions,
]); ]);
/** useEffect(() => {
* @param {object} event - Input change event enableViewport(viewportElement);
* @param {string} dimension - "height" | "width"
*/
const onDimensionsChange = (event, dimension) => {
const sanitizedTargetValue = event.target.value.replace(/\D/, '');
const isEmpty = sanitizedTargetValue === '';
const updatedDimension = isEmpty
? ''
: Math.min(sanitizedTargetValue, maximumSize);
if (updatedDimension === dimensions.width) { return () => {
return; disableViewport(viewportElement);
};
}, [disableViewport, enableViewport, viewportElement]);
useEffect(() => {
if (refreshViewport.current !== null) {
clearTimeout(refreshViewport.current);
} }
// In current code, keepAspect is always `true` refreshViewport.current = setTimeout(() => {
// And we always start w/ a square width/height refreshViewport.current = null;
setDimensions({ loadAndUpdateViewports();
width: updatedDimension, }, REFRESH_VIEWPORT_TIMEOUT);
height: updatedDimension, }, [
}); activeViewport,
viewportElement,
showAnnotations,
dimensions,
loadImage,
toggleAnnotations,
updateViewportPreview,
fileType,
downloadCanvas.ref,
minimumSize,
maximumSize,
]);
// Only update if value is non-empty useEffect(() => {
if (!isEmpty) { const { width, height } = dimensions;
setViewportElementDimensions({ const hasError = {
height: updatedDimension, width: width < minimumSize,
width: updatedDimension, height: height < minimumSize,
}); filename: !filename,
setDownloadCanvas(state => ({ };
...state,
height: updatedDimension,
width: updatedDimension,
}));
}
};
const downloadImage = () => { setError({ ...hasError });
downloadBlob( }, [dimensions, filename, minimumSize]);
filename || DEFAULT_FILENAME,
fileType,
viewportElement,
downloadCanvas.ref.current
);
};
return ( return (
<div className="ViewportDownloadForm"> <div className="ViewportDownloadForm">
<div className="title"> <div className="title">{t('formTitle')}</div>
{t(
'Please specify the dimensions, filename, and desired type for the output image.'
)}
</div>
<div className="file-info-container" data-cy="file-info-container"> <div className="file-info-container" data-cy="file-info-container">
<div className="col"> <div className="dimension-wrapper">
<div className="width"> <div className="dimensions">
<TextInput <div className="width">
data-cy="image-width" <TextInput
value={dimensions.width} type="number"
label={t('Image width (px)')} min={minimumSize}
onChange={evt => onDimensionsChange(evt, 'height')} max={maximumSize}
/> value={dimensions.width}
label={t('imageWidth')}
onChange={evt => onDimensionsChange(evt, 'width')}
data-cy="image-width"
/>
{renderErrorHandler('width')}
</div>
<div className="height">
<TextInput
type="number"
min={minimumSize}
max={maximumSize}
value={dimensions.height}
label={t('imageHeight')}
onChange={evt => onDimensionsChange(evt, 'height')}
data-cy="image-height"
/>
{renderErrorHandler('height')}
</div>
</div> </div>
<div className="height"> <div className="keep-aspect-wrapper">
<TextInput <button
data-cy="image-height" id="keep-aspect"
value={dimensions.height} className={classnames(
label={t('Image height (px)')} 'form-button btn',
onChange={evt => onDimensionsChange(evt, 'width')} keepAspect ? 'active' : ''
/> )}
data-cy="keep-aspect"
alt={t('keepAspectRatio')}
onClick={onKeepAspectToggle}
>
<Icon
name={keepAspect ? 'link' : 'unlink'}
alt={keepAspect ? 'Dismiss Aspect' : 'Keep Aspect'}
/>
</button>
</div> </div>
</div> </div>
@@ -191,9 +313,10 @@ const ViewportDownloadForm = ({
data-cy="file-name" data-cy="file-name"
value={filename} value={filename}
onChange={event => setFilename(event.target.value)} onChange={event => setFilename(event.target.value)}
label={t('File name')} label={t('filename')}
id="file-name" id="file-name"
/> />
{renderErrorHandler('filename')}
</div> </div>
<div className="file-type"> <div className="file-type">
<Select <Select
@@ -201,7 +324,7 @@ const ViewportDownloadForm = ({
data-cy="file-type" data-cy="file-type"
onChange={event => setFileType(event.target.value)} onChange={event => setFileType(event.target.value)}
options={FILE_TYPE_OPTIONS} options={FILE_TYPE_OPTIONS}
label={t('File type')} label={t('fileType')}
/> />
</div> </div>
</div> </div>
@@ -217,7 +340,7 @@ const ViewportDownloadForm = ({
checked={showAnnotations} checked={showAnnotations}
onChange={event => setShowAnnotations(event.target.checked)} onChange={event => setShowAnnotations(event.target.checked)}
/> />
{t('Show Annotations')} {t('showAnnotations')}
</label> </label>
</div> </div>
</div> </div>
@@ -245,15 +368,23 @@ const ViewportDownloadForm = ({
></canvas> ></canvas>
</div> </div>
<div className="preview" data-cy="image-preview"> {viewportPreview.src ? (
<h4> {t('Image Preview')}</h4> <div className="preview" data-cy="image-preview">
<img <div className="preview-header"> {t('imagePreview')}</div>
className="viewport-preview" <img
src={viewportPreview.src} className="viewport-preview"
alt="Viewport Preview" src={viewportPreview.src}
data-cy="viewport-preview-img" alt={t('imagePreview')}
/> data-cy="image-preview"
</div> data-cy="viewport-preview-img"
/>
</div>
) : (
<div className="loading-image">
<Icon name="circle-notch" className="icon-spin" />
{t('loadingPreview')}
</div>
)}
<div className="actions"> <div className="actions">
<div className="action-cancel"> <div className="action-cancel">
@@ -263,16 +394,17 @@ const ViewportDownloadForm = ({
className="btn btn-danger" className="btn btn-danger"
onClick={onClose} onClick={onClose}
> >
{t('Cancel')} {t('Buttons:Cancel')}
</button> </button>
</div> </div>
<div className="action-save"> <div className="action-save">
<button <button
disabled={hasError}
onClick={downloadImage} onClick={downloadImage}
className="btn btn-primary" className="btn btn-primary"
data-cy="download-btn" data-cy="download-btn"
> >
{t('Download')} {t('Buttons:Download')}
</button> </button>
</div> </div>
</div> </div>
@@ -10,6 +10,9 @@
input, select input, select
max-height: 30px; max-height: 30px;
#keep-aspect svg
margin-top: 3px;
.title .title
margin: 0; margin: 0;
font-weight: bold; font-weight: bold;
@@ -17,85 +20,117 @@
.file-info-container .file-info-container
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-around;
margin: 20px 0; margin: 20px 0;
border-radius: 5px; border-radius: 5px;
padding: 20px 10px 0; padding: 20px 10px;
background-color: #16202b; background-color: var(--ui-gray-dark);
.form-control.input-ohif
padding: 6px 12px;
@media screen and (max-width: 1023px) @media screen and (max-width: 1023px)
flex-direction: column; flex-direction: column;
align-items: flex-start;
.col .width,
flex-grow: 1; .height,
.file-name,
.file-type
height: 56px;
.input-ohif .input-ohif
margin-left: 15px; margin: 0 5px;
.file-name,
.file-type
.select-ohif, .input-ohif
width: 170px;
.input-ohif-label, .select-ohif-label
width: 90px;
display: inline-block;
@media screen and (max-width: 1023px) @media screen and (max-width: 1023px)
margin-left: 0; width: 120px;
margin-top: 5px;
width: 100%;
.file-type .dimension-wrapper
.select-ohif display: flex;
margin-left: 17px; flex-direction: row;
.dimensions
display: flex;
flex-direction: column;
.input-ohif-label
width: 120px;
display: inline-block;
.input-ohif
@media screen and (max-width: 1023px) @media screen and (max-width: 1023px)
margin-left: 0; width: 170px;
width: 100%;
.show-annotations .keep-aspect-wrapper
font-weight: bold; display: flex;
line-height: 30px; justify-content: center;
input align-items: center;
margin-right: 7px; padding: 0 10px;
vertical-align: middle; height: 86px;
label
display: flex; .show-annotations
justify-content: center; font-weight: bold;
align-items: center; line-height: 30px;
input
margin-right: 7px;
vertical-align: middle;
label
display: flex;
justify-content: center;
align-items: center;
.loading-image
height: 580px;
display: flex;
justify-content: center;
align-items: center;
color: var(--active-color);
font-size: 20px;
.icon-spin
margin-right: 15px;
.preview .preview
display: flex; display: flex;
flex-direction column; flex-direction: column;
height: fit-content; background-color: var(--ui-gray-dark);
background-color: #16202b;
width: fit-content;
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
align-self: center; justify-content: flex-start;
margin-bottom: 20px; align-items: center;
@media screen and (max-width: 1023px) height: 580px;
width: 100%;
justify-content: center;
align-items: center;
.viewport-preview .viewport-preview
max-height: 512px; max-height: 512px;
max-width: 512px; max-width: 512px;
h4 .preview-header
width: 100%; width: 100%;
text-align center; text-align center;
font-size: 1.3em; font-size: 1.3em;
margin: 0 0 10px; margin: 0 0 10px;
.preview-container
width: auto;
height: 100%;
max-height: 400px;
object-fit contain;
.actions .actions
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
.action-cancel margin-top: 20px;
margin: 0 20px;
.actions-save .btn
margin: 0 0 0 10px; margin: 0 10px;
.input-error
font-size: 12px;
color: red;
text-align: center;
margin: 3px 0;
.modal-dialog .modal-dialog
height: 100%; height: 100%;
@@ -10,7 +10,10 @@ export class TableListItem extends Component {
children: PropTypes.node, children: PropTypes.node,
itemClass: PropTypes.string, itemClass: PropTypes.string,
itemIndex: PropTypes.number, itemIndex: PropTypes.number,
itemKey: PropTypes.oneOfType(['number', 'string']), itemKey: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
onItemClick: PropTypes.func.isRequired, onItemClick: PropTypes.func.isRequired,
}; };
+4
View File
@@ -1,6 +1,10 @@
.icon-pulse .icon-pulse
fa-spin 1s infinite steps(8) fa-spin 1s infinite steps(8)
.icon-spin {
animation: spin 2s linear infinite;
}
@keyframes fa-spin{ @keyframes fa-spin{
0%{ transform:rotate(0deg) } 0%{ transform:rotate(0deg) }
to{ transform:rotate(1turn) } to{ transform:rotate(1turn) }
+2
View File
@@ -80,6 +80,7 @@ import thLarge from './icons/th-large.svg';
import thList from './icons/th-list.svg'; import thList from './icons/th-list.svg';
import times from './icons/times.svg'; import times from './icons/times.svg';
import trash from './icons/trash.svg'; import trash from './icons/trash.svg';
import unlink from './icons/unlink.svg';
import user from './icons/user.svg'; import user from './icons/user.svg';
import youtube from './icons/youtube.svg'; import youtube from './icons/youtube.svg';
@@ -158,6 +159,7 @@ const ICONS = {
rotate, rotate,
'rotate-right': rotateRight, 'rotate-right': rotateRight,
trash, trash,
unlink,
'exclamation-circle': exclamationCircle, 'exclamation-circle': exclamationCircle,
link, link,
'exclamation-triangle': exclamationTriangle, 'exclamation-triangle': exclamationTriangle,
@@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
aria-labelledby="unlink"
viewBox="0 0 512 512"
width="1em"
height="1em"
fill="currentColor"
>
<title id="title">Unlink</title>
<path d="M304.083 388.936c4.686 4.686 4.686 12.284 0 16.971l-65.057 65.056c-54.709 54.711-143.27 54.721-197.989 0-54.713-54.713-54.719-143.27 0-197.989l65.056-65.057c4.686-4.686 12.284-4.686 16.971 0l22.627 22.627c4.686 4.686 4.686 12.284 0 16.971L81.386 311.82c-34.341 34.341-33.451 88.269.597 120.866 32.577 31.187 84.788 31.337 117.445-1.32l65.057-65.056c4.686-4.686 12.284-4.686 16.971 0l22.627 22.626zm-56.568-243.245l64.304-64.304c34.346-34.346 88.286-33.453 120.882.612 31.18 32.586 31.309 84.785-1.335 117.43l-65.056 65.057c-4.686 4.686-4.686 12.284 0 16.971l22.627 22.627c4.686 4.686 12.284 4.686 16.971 0l65.056-65.057c54.711-54.709 54.721-143.271 0-197.99-54.71-54.711-143.27-54.72-197.989 0l-65.057 65.057c-4.686 4.686-4.686 12.284 0 16.971l22.627 22.627c4.685 4.685 12.283 4.685 16.97-.001zm238.343 362.794l22.627-22.627c4.686-4.686 4.686-12.284 0-16.971L43.112 3.515c-4.686-4.686-12.284-4.686-16.971 0L3.515 26.142c-4.686 4.686-4.686 12.284 0 16.971l465.373 465.373c4.686 4.686 12.284 4.686 16.97-.001z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+12 -12
View File
@@ -29,18 +29,18 @@ class Select extends Component {
render() { render() {
return ( return (
<div className="select-ohif-container"> <div className="select-ohif-container">
<label className="select-ohif-label" htmlFor={this.id}> {this.props.label && (
{this.props.label} <label className="select-ohif-label" htmlFor={this.id}>{this.props.label}</label>
<select className="form-control select-ohif" {...this.props}> )}
{this.props.options.map(({ key, value }) => { <select className="form-control select-ohif" {...this.props}>
return ( {this.props.options.map(({ key, value }) => {
<option key={key} value={value}> return (
{key} <option key={key} value={value}>
</option> {key}
); </option>
})} );
</select> })}
</label> </select>
</div> </div>
); );
} }
+10 -10
View File
@@ -14,7 +14,7 @@ class TextInput extends React.Component {
PropTypes.number PropTypes.number
]), ]),
id: PropTypes.string, id: PropTypes.string,
label:PropTypes.string, label: PropTypes.string,
type: PropTypes.string, type: PropTypes.string,
}; };
@@ -28,15 +28,15 @@ class TextInput extends React.Component {
render() { render() {
return ( return (
<div className="input-ohif-container"> <div className="input-ohif-container">
<label className="input-ohif-label" htmlFor={this.props.id}> {this.props.label && (
{this.props.label} <label className="input-ohif-label" htmlFor={this.props.id}>{this.props.label}</label>
<input )}
type={this.props.type} <input
id={this.props.id} type={this.props.type}
className="form-control input-ohif" id={this.props.id}
{...this.props} className="form-control input-ohif"
/> {...this.props}
</label> />
</div> </div>
); );
} }
-1
View File
@@ -10,7 +10,6 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin'); const ExtractCssChunksPlugin = require('extract-css-chunks-webpack-plugin');
// const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const { InjectManifest } = require('workbox-webpack-plugin'); const { InjectManifest } = require('workbox-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin'); const TerserJSPlugin = require('terser-webpack-plugin');
+91
View File
@@ -3,6 +3,97 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.4.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.12...@ohif/viewer@3.4.13) (2020-01-30)
**Note:** Version bump only for package @ohif/viewer
## [3.4.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.11...@ohif/viewer@3.4.12) (2020-01-30)
**Note:** Version bump only for package @ohif/viewer
## [3.4.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.10...@ohif/viewer@3.4.11) (2020-01-30)
### Bug Fixes
* download tool fixes & improvements ([#1235](https://github.com/OHIF/Viewers/issues/1235)) ([b9574b6](https://github.com/OHIF/Viewers/commit/b9574b6efcfeb85cde35b5cae63282f8e1b35be6))
## [3.4.10](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.9...@ohif/viewer@3.4.10) (2020-01-28)
**Note:** Version bump only for package @ohif/viewer
## [3.4.9](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.8...@ohif/viewer@3.4.9) (2020-01-28)
**Note:** Version bump only for package @ohif/viewer
## [3.4.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.7...@ohif/viewer@3.4.8) (2020-01-28)
**Note:** Version bump only for package @ohif/viewer
## [3.4.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.6...@ohif/viewer@3.4.7) (2020-01-28)
**Note:** Version bump only for package @ohif/viewer
## [3.4.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.5...@ohif/viewer@3.4.6) (2020-01-28)
**Note:** Version bump only for package @ohif/viewer
## [3.4.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.4...@ohif/viewer@3.4.5) (2020-01-27)
**Note:** Version bump only for package @ohif/viewer
## [3.4.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.3...@ohif/viewer@3.4.4) (2020-01-27)
**Note:** Version bump only for package @ohif/viewer
## [3.4.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.2...@ohif/viewer@3.4.3) (2020-01-24)
**Note:** Version bump only for package @ohif/viewer
## [3.4.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.1...@ohif/viewer@3.4.2) (2020-01-17) ## [3.4.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@3.4.1...@ohif/viewer@3.4.2) (2020-01-17)
**Note:** Version bump only for package @ohif/viewer **Note:** Version bump only for package @ohif/viewer
Binary file not shown.
@@ -16,14 +16,14 @@ describe('OHIF Download Snapshot File', () => {
.click(); .click();
}); });
it('checks displayed information for Tablet experience', function() { it('checks displayed information for Tablet experience', function () {
// Set Tablet resolution // Set Tablet resolution
cy.viewport(1000, 660); cy.viewport(1000, 660);
// Visual comparison // Visual comparison
cy.screenshot('Download Image Modal - Tablet experience'); cy.screenshot('Download Image Modal - Tablet experience');
}); });
it('checks displayed information for Desktop experience', function() { it('checks displayed information for Desktop experience', function () {
// Set Desktop resolution // Set Desktop resolution
cy.viewport(1750, 720); cy.viewport(1750, 720);
// Visual comparison // Visual comparison
@@ -52,7 +52,7 @@ describe('OHIF Download Snapshot File', () => {
.should('be.visible'); .should('be.visible');
}); });
it('cancel changes on download modal', function() { it('cancel changes on download modal', function () {
//Change Image Width, Filename and File Type //Change Image Width, Filename and File Type
cy.get('[data-cy="image-width"]') cy.get('[data-cy="image-width"]')
.clear() .clear()
@@ -91,7 +91,7 @@ describe('OHIF Download Snapshot File', () => {
// //Check error message // //Check error message
// }); // });
it('checks if "Show Annotations" checkbox will display annotations', function() { it('checks if "Show Annotations" checkbox will display annotations', function () {
// Close modal that is initially opened // Close modal that is initially opened
cy.get('[data-cy="close-button"]').click(); cy.get('[data-cy="close-button"]').click();
@@ -0,0 +1,31 @@
describe('OHIF Load Local File', () => {
beforeEach(() => {
cy.visit('/local');
});
it('checks if user can navigate to /local', () => {
cy.get('.drag-drop-contents').should(
'contain',
'Drag and Drop DICOM files here to load them in the Viewer'
);
});
it('loads an invalid file and verify if viewer is empty', () => {
const fileName = 'example.json';
cy.fixture(fileName).then(fileContent => {
cy.get('input[type=file]')
.first()
.upload({
fileContent,
fileName,
mimeType: 'application/json',
});
});
//Verify if there is no thumbnail visible
cy.expectMinimumThumbnails(0);
//Verify if DOC thumnails is displayed and load it on viewport
cy.get('[data-cy="thumbnail-list"]').should('not.exist');
});
});
@@ -43,7 +43,7 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(17); expect($list.length).to.be.eq(16);
expect($list).to.contain('MR'); expect($list).to.contain('MR');
}); });
}); });
@@ -132,7 +132,7 @@ describe('OHIF Study List', function() {
//Wait result list to be displayed //Wait result list to be displayed
cy.waitStudyList(); cy.waitStudyList();
cy.get('@searchResult').should($list => { cy.get('@searchResult').should($list => {
expect($list.length).to.be.eq(17); expect($list.length).to.be.eq(16);
expect($list).to.contain('MR'); expect($list).to.contain('MR');
}); });
}); });
@@ -0,0 +1,29 @@
describe('OHIF Load Local PDF File', () => {
beforeEach(() => {
cy.visit('/local');
});
it('loads a PDF DICOM file and visualize it', () => {
const fileName = 'PDFDICOMfile.pdf';
cy.fixture(fileName).then(fileContent => {
cy.get('input[type=file]')
.first()
.upload({
fileContent,
fileName,
mimeType: 'application/pdf',
});
});
//Verify if 1 thumbnail is visible
cy.expectMinimumThumbnails(1);
//Verify if DOC thumnails is displayed and load it on viewport
cy.get('[data-cy="thumbnail-list"]')
.contains('DOC')
.click({ force: true });
//Verify if PDF document is displayed on the viewport
cy.get('#pdf-canvas-container').should('be.visible');
});
});
@@ -1,16 +1,11 @@
describe('OHIF Save Measurements', function() { describe('OHIF Save Measurements', function() {
before(() => { before(() => {
cy.openStudy('Fall 1'); cy.openStudy('MISTER^MR');
cy.expectMinimumThumbnails(2); cy.waitDicomImage();
cy.expectMinimumThumbnails(5);
}); });
beforeEach(() => { beforeEach(() => {
// Drags Study thumbnail into viewport
cy.get('[data-cy="thumbnail-list"]')
.contains('Rheuma')
.scrollIntoView()
.drag('.viewport-drop-target');
// Wait image to load on viewport // Wait image to load on viewport
cy.wait(2000); cy.wait(2000);
@@ -1,4 +1,5 @@
import '@percy/cypress'; import '@percy/cypress';
import 'cypress-file-upload';
import { DragSimulator } from '../helpers/DragSimulator.js'; import { DragSimulator } from '../helpers/DragSimulator.js';
import { import {
initCornerstoneToolsAliases, initCornerstoneToolsAliases,
+8 -9
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ohif/viewer", "name": "@ohif/viewer",
"version": "3.4.2", "version": "3.4.13",
"description": "OHIF Viewer", "description": "OHIF Viewer",
"author": "OHIF Contributors", "author": "OHIF Contributors",
"license": "MIT", "license": "MIT",
@@ -45,21 +45,20 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"@ffmpeg-installer/ffmpeg": "1.0.20", "@ohif/core": "^2.3.7",
"@ohif/core": "^2.3.2", "@ohif/extension-cornerstone": "^2.2.2",
"@ohif/extension-cornerstone": "^2.2.1",
"@ohif/extension-dicom-html": "^1.1.0", "@ohif/extension-dicom-html": "^1.1.0",
"@ohif/extension-dicom-microscopy": "^0.50.6", "@ohif/extension-dicom-microscopy": "^0.50.6",
"@ohif/extension-dicom-pdf": "^1.0.1", "@ohif/extension-dicom-pdf": "^1.0.1",
"@ohif/extension-vtk": "^1.2.7", "@ohif/extension-vtk": "^1.2.13",
"@ohif/i18n": "^0.52.4", "@ohif/i18n": "^0.52.5",
"@ohif/ui": "^1.1.8", "@ohif/ui": "^1.1.9",
"@tanem/react-nprogress": "^1.1.25", "@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^3.2.1", "core-js": "^3.2.1",
"cornerstone-core": "^2.2.8", "cornerstone-core": "^2.2.8",
"cornerstone-math": "^0.1.8", "cornerstone-math": "^0.1.8",
"cornerstone-tools": "^4.9.0", "cornerstone-tools": "^4.12.0",
"cornerstone-wado-image-loader": "^3.0.0", "cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.8.2", "dcmjs": "^0.8.2",
"dicom-parser": "^1.8.3", "dicom-parser": "^1.8.3",
@@ -89,7 +88,7 @@
"cypress": "^3.8.0", "cypress": "^3.8.0",
"gh-pages": "2.0.1", "gh-pages": "2.0.1",
"identity-obj-proxy": "3.0.x", "identity-obj-proxy": "3.0.x",
"lodash": "4.17.11", "lodash": "4.17.15",
"lodash.clonedeep": "4.5.0" "lodash.clonedeep": "4.5.0"
} }
} }
@@ -83,6 +83,44 @@ class OHIFStandaloneViewer extends Component {
path="/callback" path="/callback"
render={() => <CallbackPage userManager={userManager} />} render={() => <CallbackPage userManager={userManager} />}
/> />
<Route
path="/login"
component={() => {
const queryParams = new URLSearchParams(
this.props.location.search
);
const iss = queryParams.get('iss');
const loginHint = queryParams.get('login_hint');
const targetLinkUri = queryParams.get('target_link_uri');
const oidcAuthority =
appConfig.oidc !== null && appConfig.oidc[0].authority;
if (iss !== oidcAuthority) {
console.error(
'iss of /login does not match the oidc authority'
);
return null;
}
userManager.removeUser().then(() => {
if (targetLinkUri !== null) {
sessionStorage.setItem(
'ohif-redirect-to',
new URL(targetLinkUri).pathname
);
} else {
sessionStorage.setItem('ohif-redirect-to', '/');
}
if (loginHint !== null) {
userManager.signinRedirect({ login_hint: loginHint });
} else {
userManager.signinRedirect();
}
});
return null;
}}
/>
<Route <Route
component={() => { component={() => {
userManager.getUser().then(user => { userManager.getUser().then(user => {
@@ -0,0 +1,21 @@
import { connect } from 'react-redux';
import StandaloneRouting from '../routes/StandaloneRouting';
const mapDispatchToProps = dispatch => {
return {
activateServer: server => {
const action = {
type: 'ACTIVATE_SERVER',
server,
};
dispatch(action);
},
};
};
const ConnectedStandaloneRouting = connect(
null,
mapDispatchToProps
)(StandaloneRouting);
export default ConnectedStandaloneRouting;
@@ -2,23 +2,21 @@ import { connect } from 'react-redux';
import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js'; import ViewerRetrieveStudyData from './ViewerRetrieveStudyData.js';
import OHIF from "@ohif/core"; import OHIF from "@ohif/core";
const { const { clearViewportSpecificData } = OHIF.redux.actions;
clearViewportSpecificData
} = OHIF.redux.actions;
const isActive = a => a.active === true; const isActive = a => a.active === true;
const mapStateToProps = state => { const mapStateToProps = (state, ownProps) => {
const activeServer = state.servers.servers.find(isActive); const activeServer = state.servers.servers.find(isActive);
return { return {
server: activeServer, server: ownProps.server || activeServer,
}; };
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
clearViewportSpecificData: () => { clearViewportSpecificData: () => {
dispatch(clearViewportSpecificData()); dispatch(clearViewportSpecificData());
} },
}; };
}; };
@@ -19,7 +19,7 @@ const DICOMFileLoader = new (class extends FileLoader {
dicomData.meta dicomData.meta
); );
} catch (e) { } catch (e) {
console.log('Error on getting dicom file dataset. It defaults to empty'); console.error('Error reading dicom file', e);
} }
// Set imageId on dataset to be consumed later on // Set imageId on dataset to be consumed later on
dataset.imageId = imageId; dataset.imageId = imageId;
+7 -5
View File
@@ -2,14 +2,16 @@ import React from 'react';
import './NotFound.css'; import './NotFound.css';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
export default function NotFound() { export default function NotFound({ message = 'Sorry, this page does not exist.', showGoBackButton = true }) {
return ( return (
<div className={'not-found'}> <div className={'not-found'}>
<div> <div>
<h4>Sorry, this page does not exist.</h4> <h4>{message}</h4>
<h5> {showGoBackButton && (
<Link to={'/'}>Go back to the Study List</Link> <h5>
</h5> <Link to={'/'}>Go back to the Study List</Link>
</h5>
)}
</div> </div>
</div> </div>
); );
+93 -64
View File
@@ -1,29 +1,33 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { log, metadata, studies, utils } from '@ohif/core'; import { log, metadata, utils } from '@ohif/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ConnectedViewer from '../connectedComponents/ConnectedViewer';
import { extensionManager } from './../App.js';
import qs from 'querystring'; import qs from 'querystring';
const { OHIFStudyMetadata } = metadata; import { extensionManager } from './../App.js';
const { retrieveStudiesMetadata } = studies; import ConnectedViewer from '../connectedComponents/ConnectedViewer';
import ConnectedViewerRetrieveStudyData from '../connectedComponents/ConnectedViewerRetrieveStudyData';
import NotFound from '../routes/NotFound';
const { studyMetadataManager, updateMetaDataManager } = utils; const { studyMetadataManager, updateMetaDataManager } = utils;
const { OHIFStudyMetadata } = metadata;
class StandaloneRouting extends Component { class StandaloneRouting extends Component {
state = { state = {
studies: null, studies: null,
server: null,
studyInstanceUids: null,
seriesInstanceUids: null,
error: null, error: null,
loading: true,
}; };
studyInstanceUids = [];
static propTypes = { static propTypes = {
location: PropTypes.object, location: PropTypes.object,
store: PropTypes.object, store: PropTypes.object,
setServers: PropTypes.func,
}; };
static parseQueryAndFetchStudies(query) { parseQueryAndRetrieveDICOMWebData(query) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const url = query.url; const url = query.url;
@@ -43,7 +47,11 @@ class StandaloneRouting extends Component {
// When the JSON has been returned, parse it into a JavaScript Object // When the JSON has been returned, parse it into a JavaScript Object
// and render the OHIF Viewer with this data // and render the OHIF Viewer with this data
oReq.addEventListener('load', () => { oReq.addEventListener('load', event => {
if (event.target.status === 404) {
reject(new Error('No JSON data found'));
}
// Parse the response content // Parse the response content
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText
if (!oReq.responseText) { if (!oReq.responseText) {
@@ -54,27 +62,24 @@ class StandaloneRouting extends Component {
log.info(JSON.stringify(oReq.responseText, null, 2)); log.info(JSON.stringify(oReq.responseText, null, 2));
const data = JSON.parse(oReq.responseText); const data = JSON.parse(oReq.responseText);
if (data.servers && query.studyInstanceUids) { if (data.servers) {
if (!query.studyInstanceUids) {
log.warn('No study instance uids specified');
reject(new Error('No study instance uids specified'));
}
const server = data.servers.dicomWeb[0]; const server = data.servers.dicomWeb[0];
server.type = 'dicomWeb'; server.type = 'dicomWeb';
const studyInstanceUids = query.studyInstanceUids.split(';'); log.warn('Activating server', server);
const seriesInstanceUids = []; this.props.activateServer(server);
retrieveStudiesMetadata( const studyInstanceUids = query.studyInstanceUids.split(';');
server, const seriesInstanceUids = query.seriesInstanceUids ? query.seriesInstanceUids.split(';') : [];
studyInstanceUids,
seriesInstanceUids resolve({ server, studyInstanceUids, seriesInstanceUids });
).then(
studies => {
resolve(studies);
},
error => {
reject(error);
}
);
} else { } else {
resolve(data.studies); resolve({ studies: data.studies, studyInstanceUids: [] });
} }
}); });
@@ -96,56 +101,80 @@ class StandaloneRouting extends Component {
// Remove ? prefix which is included for some reason // Remove ? prefix which is included for some reason
search = search.slice(1, search.length); search = search.slice(1, search.length);
const query = qs.parse(search); const query = qs.parse(search);
const studies = await StandaloneRouting.parseQueryAndFetchStudies(query);
studyMetadataManager.purge(); let {
server,
studies,
studyInstanceUids,
seriesInstanceUids,
} = await this.parseQueryAndRetrieveDICOMWebData(query);
// Map studies to new format, update metadata manager? if (studies) {
const uniqueStudyUids = new Set(); const {
const updatedStudies = studies.map(study => { studies: updatedStudies,
const studyMetadata = new OHIFStudyMetadata( studyInstanceUids: updatedStudiesInstanceUids,
study, } = _mapStudiesToNewFormat(studies);
study.studyInstanceUid studies = updatedStudies;
); studyInstanceUids = updatedStudiesInstanceUids;
const sopClassHandlerModules = }
extensionManager.modules['sopClassHandlerModule'];
study.displaySets = this.setState({
study.displaySets || studies,
studyMetadata.createDisplaySets(sopClassHandlerModules); server,
studyMetadata.setDisplaySets(study.displaySets); studyInstanceUids,
seriesInstanceUids,
// Updates WADO-RS metaDataManager loading: false,
updateMetaDataManager(study);
studyMetadataManager.add(studyMetadata);
uniqueStudyUids.add(study.studyInstanceUid);
return study;
}); });
this.studyInstanceUids = Array.from(uniqueStudyUids);
this.setState({ studies: updatedStudies });
} catch (error) { } catch (error) {
this.setState({ error }); this.setState({ error: error.message, loading: false });
} }
} }
render() { render() {
if (this.state.error) { const message = this.state.error ? `Error: ${JSON.stringify(this.state.error)}` : 'Loading...';
return <div>Error: {JSON.stringify(this.state.error)}</div>; if (this.state.error || this.state.loading) {
} else if (!this.state.studies) { return <NotFound message={message} showGoBackButton={this.state.error} />;
return <div>Loading...</div>;
} }
return (
<ConnectedViewer return this.state.studies ? (
studies={this.state.studies} <ConnectedViewer studies={this.state.studies} />
studyInstanceUids={this.studyInstanceUids} ) : (
/> <ConnectedViewerRetrieveStudyData
); studyInstanceUids={this.state.studyInstanceUids}
seriesInstanceUids={this.state.seriesInstanceUids}
server={this.state.server}
/>
);
} }
} }
const _mapStudiesToNewFormat = studies => {
studyMetadataManager.purge();
/* Map studies to new format, update metadata manager? */
const uniqueStudyUids = new Set();
const updatedStudies = studies.map(study => {
const studyMetadata = new OHIFStudyMetadata(study, study.studyInstanceUid);
const sopClassHandlerModules = extensionManager.modules['sopClassHandlerModule'];
study.displaySets = study.displaySets ||
studyMetadata.createDisplaySets(sopClassHandlerModules);
studyMetadata.setDisplaySets(study.displaySets);
/* Updates WADO-RS metaDataManager */
updateMetaDataManager(study);
studyMetadataManager.add(studyMetadata);
uniqueStudyUids.add(study.studyInstanceUid);
return study;
});
return {
studies: updatedStudies,
studyInstanceUids: Array.from(uniqueStudyUids),
};
};
export default StandaloneRouting; export default StandaloneRouting;
+1 -1
View File
@@ -19,7 +19,7 @@ const StudyListRouting = asyncComponent(() =>
) )
); );
const StandaloneRouting = asyncComponent(() => const StandaloneRouting = asyncComponent(() =>
import(/* webpackChunkName: "StandaloneRouting" */ './StandaloneRouting.js') import(/* webpackChunkName: "ConnectedStandaloneRouting" */ '../connectedComponents/ConnectedStandaloneRouting.js')
); );
const ViewerLocalFileData = asyncComponent(() => const ViewerLocalFileData = asyncComponent(() =>
import( import(
+113 -854
View File
File diff suppressed because it is too large. Load diff